diff --git a/vlib/time/operator.v b/vlib/time/operator.v index 46f1035dfa..fe89f77991 100644 --- a/vlib/time/operator.v +++ b/vlib/time/operator.v @@ -1,6 +1,7 @@ module time // eq returns true if provided time is equal to time +[inline] pub fn (t1 Time) eq(t2 Time) bool { if t1.unix == t2.unix && t1.microsecond == t2.microsecond { return true diff --git a/vlib/time/parse.v b/vlib/time/parse.v index bb0265057b..8b40847007 100644 --- a/vlib/time/parse.v +++ b/vlib/time/parse.v @@ -48,11 +48,12 @@ pub fn parse_rfc2822(s string) ?Time { return parse(tos(tmstr, count)) } -// parse_rfc8601 parses rfc8601 time format yyyy-MM-ddTHH:mm:ss.dddddd+dd:dd as local time +// parse_iso8601 parses rfc8601 time format yyyy-MM-ddTHH:mm:ss.dddddd+dd:dd as local time // the fraction part is difference in milli seconds and the last part is offset // from UTC time and can be both +/- HH:mm -// Remarks: Not all rfc8601 is supported only the 'yyyy-MM-ddTHH:mm:ss.dddddd+dd:dd' -pub fn parse_rfc8601(s string) ?Time { +// remarks: not all iso8601 is supported only the 'yyyy-MM-ddTHH:mm:ss.dddddd+dd:dd' +// also checks and support for leapseconds should be added in future PR +pub fn parse_iso8601(s string) ?Time { mut year := 0 mut month := 0 @@ -82,7 +83,8 @@ pub fn parse_rfc8601(s string) ?Time { return error('Invalid 8601 format, expected `+` or `-` as time separator' ) } - t := new_time(Time{ + + mut t := new_time(Time{ year: year month: month day: day @@ -91,6 +93,8 @@ pub fn parse_rfc8601(s string) ?Time { second: second microsecond: mic_second }) + + mut unix_time := t.unix mut unix_offset := int(0) if offset_hour > 0 { @@ -102,10 +106,14 @@ pub fn parse_rfc8601(s string) ?Time { if unix_offset != 0 { if plus_min == `+` { - return unix2(int(t.unix) + unix_offset, t.microsecond) + unix_time -= u64(unix_offset) } else { - return unix2(int(t.unix) - unix_offset, t.microsecond) + unix_time += u64(unix_offset) } + t = unix2(int(unix_time), t.microsecond) } - return t + + // Convert the time to local time + + return to_local_time(t) } diff --git a/vlib/time/parse_test.v b/vlib/time/parse_test.v index e0df77f6aa..7c438502d3 100644 --- a/vlib/time/parse_test.v +++ b/vlib/time/parse_test.v @@ -45,30 +45,27 @@ fn test_parse_rfc2822_invalid() { assert false } -fn test_rfc8601_parse_utc() { - ok_format := '2020-06-02T15:38:06.015959+00:00' +fn test_iso8601_parse_utc_diff() { + format_utc := '2020-06-05T15:38:06.015959+00:00' + format_cest := '2020-06-05T15:38:06.015959+02:00' - t := time.parse_rfc8601(ok_format) or {panic(err)} + t_utc := time.parse_iso8601(format_utc) or {panic(err)} + t_cest := time.parse_iso8601(format_cest) or {panic(err)} - assert t.year == 2020 - assert t.month == 6 - assert t.day == 2 - assert t.hour == 15 - assert t.minute == 38 - assert t.second == 6 - assert t.microsecond == 15959 -} - -fn test_rfc8601_parse_cest() { - ok_format := '2020-06-02T15:38:06.015959+02:00' - - t := time.parse_rfc8601(ok_format) or {panic(err)} - - assert t.year == 2020 - assert t.month == 6 - assert t.day == 2 - assert t.hour == 17 - assert t.minute == 38 - assert t.second == 6 - assert t.microsecond == 15959 -} + assert t_utc.year == 2020 + assert t_cest.year == 2020 + assert t_utc.month == 6 + assert t_cest.month == 6 + assert t_utc.day == 5 + assert t_cest.day == 5 + // if it was formatted in utc it should be + // two hours before if it was formatted in + // cest time + assert t_utc.hour == (t_cest.hour + 2) + assert t_utc.minute == 38 + assert t_cest.minute == 38 + assert t_utc.second == 6 + assert t_cest.second == 6 + assert t_utc.microsecond == 15959 + assert t_cest.microsecond == 15959 +} \ No newline at end of file diff --git a/vlib/time/time.v b/vlib/time/time.v index eea68d4b9c..d10bca4161 100644 --- a/vlib/time/time.v +++ b/vlib/time/time.v @@ -85,6 +85,7 @@ pub struct C.timeval { fn C.localtime(t &C.time_t) &C.tm fn C.time(t &C.time_t) C.time_t + // now returns current local time. pub fn now() Time { $if macos { @@ -106,6 +107,27 @@ pub fn now() Time { return convert_ctime(now, 0) } +// utc returns the current time in utc +pub fn utc() Time { + $if macos { + return darwin_utc() + } + $if windows { + return win_utc() + } + $if solaris { + return solaris_utc() + } + $if linux { + return linux_utc() + } + // defaults to most common feature, the microsecond precision is not available + // in this API call + t := C.time(0) + _ = C.time(&t) + return unix2(int(t), 0) +} + // smonth returns month name. pub fn (t Time) smonth() string { i := t.month - 1 diff --git a/vlib/time/time_darwin.c.v b/vlib/time/time_darwin.c.v index 7bbaa3f2df..d516961c39 100644 --- a/vlib/time/time_darwin.c.v +++ b/vlib/time/time_darwin.c.v @@ -56,25 +56,30 @@ fn vpc_now_darwin() u64 { // darwin_now returns a better precision current time for Darwin based operating system // this should be implemented with native system calls eventually // but for now a bit tweaky. It uses the deprecated gettimeofday clock to get -// the microseconds seconds part and normal local time to get correct local time -// if the time has shifted on a second level between calls it uses -// zero as microsecond. Not perfect but better that unix time only us a second +// the microseconds seconds part and converts to local time +[inline] fn darwin_now() Time { // get the high precision time as UTC clock - // and use the nanoseconds part tv := C.timeval{} C.gettimeofday(&tv, 0) - t := C.time(0) - tm := C.localtime(&t) + loc_tm := C.tm{} + C.localtime_r(&tv.tv_sec, &loc_tm) - // if the second part (very rare) is different - // microseconds is set to zero since it passed the second - // also avoid divide by zero if nsec is zero - if int(t) != tv.tv_sec { - return convert_ctime(tm, 0) - } - - return convert_ctime(tm, int(tv.tv_usec)) + return convert_ctime(loc_tm, int(tv.tv_usec)) +} + +// darwin_utc returns a better precision current time for Darwin based operating system +// this should be implemented with native system calls eventually +// but for now a bit tweaky. It uses the deprecated gettimeofday clock to get +// the microseconds seconds part and normal local time to get correct local time +[inline] +fn darwin_utc() Time { + + // get the high precision time as UTC clock + tv := C.timeval{} + C.gettimeofday(&tv, 0) + + return unix2(int(tv.tv_sec), int(tv.tv_usec)) } diff --git a/vlib/time/time_linux.c.v b/vlib/time/time_linux.c.v index 86c8bd9dc2..7d17497b5c 100644 --- a/vlib/time/time_linux.c.v +++ b/vlib/time/time_linux.c.v @@ -1,5 +1,32 @@ module time +// linux_now returns the local time with high precision for most os:es +// this should be implemented properly with support for leap seconds. +// It uses the realtime clock to get and converts it to local time +[inline] +fn linux_now() Time { + + // get the high precision time as UTC realtime clock + // and use the nanoseconds part + mut ts := C.timespec{} + C.clock_gettime(C.CLOCK_REALTIME, &ts) + + loc_tm := C.tm{} + C.localtime_r(&ts.tv_sec, &loc_tm) + + return convert_ctime(loc_tm, int(ts.tv_nsec/1000)) +} + +[inline] +fn linux_utc() Time { + // get the high precision time as UTC realtime clock + // and use the nanoseconds part + mut ts := C.timespec{} + C.clock_gettime(C.CLOCK_REALTIME, &ts) + + return unix2(int(ts.tv_sec), int(ts.tv_nsec/1000)) +} + fn sys_mono_now_darwin() u64 { return 0 } @@ -14,28 +41,12 @@ pub fn solaris_now() Time { return Time{} } -// linux_now returns the local time with high precision for most os:es -// this should be implemented with native system calls eventually -// but for now a bit tweaky. It uses the realtime clock to get -// the nano seconds part and normal local time to get correct local time -// if the time has shifted on a second level between calls it uses -// zero as microsecond. Not perfect but better that unix time only -fn linux_now() Time { - - // get the high precision time as UTC realtime clock - // and use the nanoseconds part - mut ts := C.timespec{} - C.clock_gettime(C.CLOCK_REALTIME, &ts) - - t := C.time(0) - tm := C.localtime(&t) - - // if the second part (very rare) is different - // microseconds is set to zero since it passed the second - // also avoid divide by zero if nsec is zero - if int(t) != ts.tv_sec || ts.tv_nsec == 0 { - return convert_ctime(tm, 0) - } - - return convert_ctime(tm, int(ts.tv_nsec/1000)) +// dummy to compile with all compilers +pub fn darwin_utc() Time { + return Time{} } + +// dummy to compile with all compilers +pub fn solaris_utc() Time { + return Time{} +} \ No newline at end of file diff --git a/vlib/time/time_nix.c.v b/vlib/time/time_nix.c.v index 6a3e7b00cb..567210d855 100644 --- a/vlib/time/time_nix.c.v +++ b/vlib/time/time_nix.c.v @@ -12,14 +12,25 @@ struct C.tm { tm_mday int tm_mon int tm_year int + tm_wday int + tm_yday int + tm_isdst int } fn C.timegm(&tm) time_t +fn C.localtime_r(t &C.time_t, tm &C.tm ) fn make_unix_time(t C.tm) int { return int(C.timegm(&t)) } +fn to_local_time(t Time) Time { + loc_tm := C.tm{} + C.localtime_r(&t.unix, &loc_tm) + + return convert_ctime(loc_tm, t.microsecond) +} + type time_t voidptr // in most systems, these are __quad_t, which is an i64 @@ -50,14 +61,20 @@ fn vpc_now() u64 { return u64(ts.tv_sec) * 1_000_000_000 + u64(ts.tv_nsec) } + + // dummy to compile with all compilers pub fn win_now() Time { return Time{} } +// dummy to compile with all compilers +pub fn win_utc() Time { + return Time{} +} // dummy to compile with all compilers pub struct C.timeval { tv_sec u64 tv_usec u64 -} \ No newline at end of file +} diff --git a/vlib/time/time_solaris.c.v b/vlib/time/time_solaris.c.v index 611adbe559..a002b87c9e 100644 --- a/vlib/time/time_solaris.c.v +++ b/vlib/time/time_solaris.c.v @@ -1,26 +1,48 @@ module time +// solaris_now returns the local time with high precision for most os:es +// this should be implemented properly with support for leap seconds. +// It uses the realtime clock to get and converts it to local time +[inline] +fn solaris_now() Time { + + // get the high precision time as UTC realtime clock + // and use the nanoseconds part + mut ts := C.timespec{} + C.clock_gettime(C.CLOCK_REALTIME, &ts) + + loc_tm := C.tm{} + C.localtime_r(&ts.tv_sec, &loc_tm) + + return convert_ctime(loc_tm, int(ts.tv_nsec/1000)) +} + +[inline] +fn solaris_utc() Time { + // get the high precision time as UTC realtime clock + // and use the nanoseconds part + mut ts := C.timespec{} + C.clock_gettime(C.CLOCK_REALTIME, &ts) + + return unix2(int(ts.tv_sec), int(ts.tv_nsec/1000)) +} + // dummy to compile with all compilers pub fn linux_now() Time { return Time{} } +// dummy to compile with all compilers pub fn darwin_now() Time { return Time{} } -fn solaris_now() Time { - // get the high precision time as UTC realtime clock - // and use the nanoseconds part - mut ts := C.timespec{} - C.clock_gettime(C.CLOCK_REALTIME, &ts) - t := C.time(0) - tm := C.localtime(&t) - // if the second part (very rare) is different - // microseconds is set to zero since it passed the second - // also avoid divide by zero if nsec is zero - if int(t) != ts.tv_sec || ts.tv_nsec == 0 { - return convert_ctime(tm, 0) - } - return convert_ctime(tm, int(ts.tv_nsec/1000)) +// dummy to compile with all compilers +pub fn linux_utc() Time { + return Time{} } + +// dummy to compile with all compilers +pub fn darwin_utc() Time { + return Time{} +} \ No newline at end of file diff --git a/vlib/time/time_test.v b/vlib/time/time_test.v index 71fd8a2f40..1b32664be8 100644 --- a/vlib/time/time_test.v +++ b/vlib/time/time_test.v @@ -161,8 +161,23 @@ fn test_now() { assert now.minute >= 0 assert now.minute < 60 assert now.second >=0 - assert now.second < 60 + assert now.second <= 60 // <= 60 cause of leap seconds assert now.microsecond >= 0 assert now.microsecond < 1000000 } + +fn test_utc() { + now := time.utc() + + // The year the test was built + assert now.year >= 2020 + assert now.month > 0 + assert now.month <= 12 + assert now.minute >= 0 + assert now.minute < 60 + assert now.second >=0 + assert now.second <= 60 // <= 60 cause of leap seconds + assert now.microsecond >= 0 + assert now.microsecond < 1000000 +} diff --git a/vlib/time/time_windows.c.v b/vlib/time/time_windows.c.v index 1400f4e2f0..5a1cceb48b 100644 --- a/vlib/time/time_windows.c.v +++ b/vlib/time/time_windows.c.v @@ -31,6 +31,7 @@ struct SystemTime { fn C.GetSystemTimeAsFileTime(lpSystemTimeAsFileTime C._FILETIME) fn C.FileTimeToSystemTime() fn C.SystemTimeToTzSpecificLocalTime() +fn C.localtime_s(t &C.time_t, tm &C.tm ) const ( // start_time is needed on Darwin and Windows because of potential overflows @@ -91,9 +92,36 @@ fn local_as_unix_time() int { return make_unix_time(tm) } +fn to_local_time(t Time) Time { + st_utc := SystemTime{ + year: u16(t.year) + month: u16(t.month) + day: u16(t.day) + hour: u16(t.hour) + minute: u16(t.minute) + second: u16(t.second) + } + st_local := SystemTime{} + C.SystemTimeToTzSpecificLocalTime(voidptr(0), &st_utc, &st_local) + + t_local := Time { + year: st_local.year + month: st_local.month + day: st_local.day + hour: st_local.hour + minute: st_local.minute + second: st_local.second + // These are the same + microsecond: t.microsecond + unix: t.unix + } + return t_local +} + // win_now calculates current time using winapi to get higher resolution on windows -// GetSystemTimeAsFileTime is used. It can resolve time down to millisecond -// other more precice methods can be implemented in the future +// GetSystemTimeAsFileTime is used and converted to local time. It can resolve time +// down to millisecond. Other more precice methods can be implemented in the future +[inline] fn win_now() Time { ft_utc := C._FILETIME{} @@ -119,6 +147,32 @@ fn win_now() Time { return t } +// win_utc calculates current time using winapi to get higher resolution on windows +// GetSystemTimeAsFileTime is used. It can resolve time down to millisecond +// other more precice methods can be implemented in the future +[inline] +fn win_utc() Time { + + ft_utc := C._FILETIME{} + C.GetSystemTimeAsFileTime(&ft_utc) + + st_utc := SystemTime{} + C.FileTimeToSystemTime(&ft_utc, &st_utc) + + t := Time { + year: st_utc.year + month: st_utc.month + day: st_utc.day + hour: st_utc.hour + minute: st_utc.minute + second: st_utc.second + microsecond: st_utc.millisecond*1000 + unix: u64(st_utc.unix_time()) + } + + return t +} + // unix_time returns Unix time. pub fn (st SystemTime) unix_time() int { tt := C.tm{ @@ -147,6 +201,21 @@ pub fn solaris_now() Time { return Time{} } +pub fn darwin_utc() Time { + return Time{} +} + +// dummy to compile with all compilers +pub fn linux_utc() Time { + return Time{} +} + +// dummy to compile with all compilers +pub fn solaris_utc() Time { + return Time{} +} + + // dummy to compile with all compilers pub struct C.timeval { tv_sec u64