time: fix iso8601 parser and utc time
							parent
							
								
									8f9f426479
								
							
						
					
					
						commit
						2dc547a45c
					
				|  | @ -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 | ||||
|  |  | |||
|  | @ -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) | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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)) | ||||
| } | ||||
|  |  | |||
|  | @ -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{} | ||||
| } | ||||
|  | @ -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 | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -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{} | ||||
| } | ||||
|  | @ -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 | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue