time: fix iso8601 parser and utc time

pull/5095/head
Tomas Hellström 2020-06-10 11:14:55 +02:00 committed by GitHub
parent 8f9f426479
commit 2dc547a45c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 255 additions and 88 deletions

View File

@ -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

View File

@ -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)
}

View File

@ -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
}

View File

@ -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

View File

@ -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))
}

View File

@ -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{}
}

View File

@ -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
}
}

View File

@ -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{}
}

View File

@ -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
}

View File

@ -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