From 6a74058190005eb3696d78b52aa414ac23f226ce Mon Sep 17 00:00:00 2001 From: zakuro Date: Wed, 16 Dec 2020 20:10:02 +0900 Subject: [PATCH] time: make parse_iso8601 support a date only format (#7277) --- vlib/time/operator.v | 5 ++ vlib/time/parse.v | 113 ++++++++++++++++++++++--------------- vlib/time/parse_test.v | 17 +++++- vlib/time/time_darwin.c.v | 2 - vlib/time/time_nix.c.v | 2 - vlib/time/time_solaris.c.v | 2 - vlib/time/time_test.v | 4 +- vlib/time/time_windows.c.v | 2 - vlib/time/unix.v | 2 - 9 files changed, 90 insertions(+), 59 deletions(-) diff --git a/vlib/time/operator.v b/vlib/time/operator.v index 9c381cf5ab..56c5a79b95 100644 --- a/vlib/time/operator.v +++ b/vlib/time/operator.v @@ -10,11 +10,13 @@ pub fn (t1 Time) eq(t2 Time) bool { } // ne returns true if provided time is not equal to time +[inline] pub fn (t1 Time) ne(t2 Time) bool { return !t1.eq(t2) } // lt returns true if provided time is less than time +[inline] pub fn (t1 Time) lt(t2 Time) bool { if t1.unix < t2.unix || (t1.unix == t2.unix && t1.microsecond < t2.microsecond) { return true @@ -23,11 +25,13 @@ pub fn (t1 Time) lt(t2 Time) bool { } // le returns true if provided time is less or equal to time +[inline] pub fn (t1 Time) le(t2 Time) bool { return t1.lt(t2) || t1.eq(t2) } // gt returns true if provided time is greater than time +[inline] pub fn (t1 Time) gt(t2 Time) bool { if t1.unix > t2.unix || (t1.unix == t2.unix && t1.microsecond > t2.microsecond) { return true @@ -36,6 +40,7 @@ pub fn (t1 Time) gt(t2 Time) bool { } // ge returns true if provided time is greater or equal to time +[inline] pub fn (t1 Time) ge(t2 Time) bool { return t1.gt(t2) || t1.eq(t2) } diff --git a/vlib/time/parse.v b/vlib/time/parse.v index 16bbedc535..4b6fa04f0d 100644 --- a/vlib/time/parse.v +++ b/vlib/time/parse.v @@ -44,43 +44,75 @@ pub fn parse_rfc2822(s string) ?Time { return parse(tos(tmstr, count)) } +// ----- iso8601 ----- +const ( + err_invalid_8601 = error('Invalid 8601 Format') +) + +fn parse_iso8601_date(s string) ?(int, int, int) { + year, month, day, dummy := 0, 0, 0, byte(0) + count := unsafe {C.sscanf(charptr(s.str), '%4d-%2d-%2d%c', &year, &month, &day, &dummy)} + if count != 3 { + return err_invalid_8601 + } + return year, month, day +} + +fn parse_iso8601_time(s string) ?(int, int, int, int, i64, bool) { + hour := 0 + minute := 0 + second := 0 + microsecond := 0 + plus_min_z := `a` + offset_hour := 0 + offset_minute := 0 + mut count := unsafe {C.sscanf(charptr(s.str), '%2d:%2d:%2d.%6d%c%2d:%2d', &hour, &minute, + &second, µsecond, charptr(&plus_min_z), &offset_hour, &offset_minute)} + // Missread microsecond ([Sec Hour Minute].len == 3 < 4) + if count < 4 { + count = unsafe {C.sscanf(charptr(s.str), '%2d:%2d:%2d%c%2d:%2d', &hour, &minute, + &second, charptr(&plus_min_z), &offset_hour, &offset_minute)} + count++ // Increment count because skipped microsecond + } + if count < 4 { + return err_invalid_8601 + } + is_local_time := plus_min_z == `a` && count == 4 + is_utc := plus_min_z == `Z` && count == 5 + if !(count == 7 || is_local_time || is_utc) { + return err_invalid_8601 + } + if plus_min_z != `+` && plus_min_z != `-` && !is_utc && !is_local_time { + return error('Invalid 8601 format, expected `Z` or `+` or `-` as time separator') + } + mut unix_offset := 0 + if offset_hour > 0 { + unix_offset += 3600 * offset_hour + } + if offset_minute > 0 { + unix_offset += 60 * offset_minute + } + if plus_min_z == `+` { + unix_offset *= -1 + } + return hour, minute, second, microsecond, unix_offset, is_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 iso8601 is supported // also checks and support for leapseconds should be added in future PR pub fn parse_iso8601(s string) ?Time { - year := 0 - month := 0 - day := 0 - hour := 0 - minute := 0 - second := 0 - mic_second := 0 - time_char := `a` - plus_min_z := `a` - offset_hour := 0 - offset_min := 0 - mut count := unsafe {C.sscanf(charptr(s.str), '%4d-%2d-%2d%c%2d:%2d:%2d.%6d%c%2d:%2d', - &year, &month, &day, charptr(&time_char), &hour, &minute, &second, &mic_second, charptr(&plus_min_z), - &offset_hour, &offset_min)} - // Missread microsec ([Year Month Day T Sec Hour Minute].len == 7 < 8) - if count < 8 { - count = unsafe {C.sscanf(charptr(s.str), '%4d-%2d-%2d%c%2d:%2d:%2d%c%2d:%2d', - &year, &month, &day, charptr(&time_char), &hour, &minute, &second, charptr(&plus_min_z), - &offset_hour, &offset_min)} - count++ // Increment count because skipped microsec + t_i := s.index('T') or { -1 } + parts := if t_i != -1 { [s[..t_i], s[t_i + 1..]] } else { s.split(' ') } + if !(parts.len == 1 || parts.len == 2) { + return err_invalid_8601 } - is_local_time := plus_min_z == `a` && count == 8 - is_utc := plus_min_z == `Z` && count == 9 - if count != 11 && !is_local_time && !is_utc { - return error('Invalid 8601 format') - } - if time_char != `T` && time_char != ` ` { - return error('Invalid 8601 format, expected space or `T` as time separator') - } - if plus_min_z != `+` && plus_min_z != `-` && plus_min_z != `Z` && !is_local_time { - return error('Invalid 8601 format, expected `Z` or `+` or `-` as time separator') + year, month, day := parse_iso8601_date(parts[0]) ? + mut hour, mut minute, mut second, mut microsecond, mut unix_offset, mut is_local_time := 0, 0, 0, 0, i64(0), true + if parts.len == 2 { + hour, minute, second, microsecond, unix_offset, is_local_time = parse_iso8601_time(parts[1]) ? } mut t := new_time(Time{ year: year @@ -89,27 +121,18 @@ pub fn parse_iso8601(s string) ?Time { hour: hour minute: minute second: second - microsecond: mic_second + microsecond: microsecond }) if is_local_time { return t // Time already local time } mut unix_time := t.unix - mut unix_offset := int(0) - if offset_hour > 0 { - unix_offset += 3600 * offset_hour - } - if offset_min > 0 { - unix_offset += 60 * offset_min - } - if unix_offset != 0 { - if plus_min_z == `+` { - unix_time -= u64(unix_offset) - } else { - unix_time += u64(unix_offset) - } - t = unix2(int(unix_time), t.microsecond) + if unix_offset < 0 { + unix_time -= u64(-unix_offset) + } else if unix_offset > 0 { + unix_time += u64(unix_offset) } + t = unix2(int(unix_time), t.microsecond) // Convert the time to local time return t.to_local_time() } diff --git a/vlib/time/parse_test.v b/vlib/time/parse_test.v index 9cb580f6d4..b294236c10 100644 --- a/vlib/time/parse_test.v +++ b/vlib/time/parse_test.v @@ -62,7 +62,7 @@ fn test_parse_iso8601() { '2020-06-05T15:38:06.015959+00:00', '2020-06-05T15:38:06.015959+02:00', '2020-06-05T15:38:06.015959-02:00', - '2020-11-05T15:38:06.015959Z' + '2020-11-05T15:38:06.015959Z', ] times := [ [2020, 6, 5, 15, 38, 6, 0], @@ -131,3 +131,18 @@ fn test_parse_iso8601_invalid() { assert false } } + +fn test_parse_iso8601_date_only() { + format := '2020-06-05' + t := time.parse_iso8601(format) or { + assert false + return + } + assert t.year == 2020 + assert t.month == 6 + assert t.day == 5 + assert t.hour == 0 + assert t.minute == 0 + assert t.second == 0 + assert t.microsecond == 0 +} diff --git a/vlib/time/time_darwin.c.v b/vlib/time/time_darwin.c.v index c8fc6ff600..b2036075eb 100644 --- a/vlib/time/time_darwin.c.v +++ b/vlib/time/time_darwin.c.v @@ -61,7 +61,6 @@ fn vpc_now_darwin() u64 { // 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 converts to local time -[inline] fn darwin_now() Time { // get the high precision time as UTC clock tv := C.timeval{} @@ -75,7 +74,6 @@ fn darwin_now() Time { // 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{} diff --git a/vlib/time/time_nix.c.v b/vlib/time/time_nix.c.v index 008bfa1bb6..49efe0ab46 100644 --- a/vlib/time/time_nix.c.v +++ b/vlib/time/time_nix.c.v @@ -66,7 +66,6 @@ fn vpc_now() u64 { // 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 @@ -77,7 +76,6 @@ fn linux_now() Time { 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 diff --git a/vlib/time/time_solaris.c.v b/vlib/time/time_solaris.c.v index 181f75b06b..e3e24b1fda 100644 --- a/vlib/time/time_solaris.c.v +++ b/vlib/time/time_solaris.c.v @@ -3,7 +3,6 @@ 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 @@ -14,7 +13,6 @@ fn solaris_now() Time { 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 diff --git a/vlib/time/time_test.v b/vlib/time/time_test.v index 77c1c0b7ee..7a7b2ee71b 100644 --- a/vlib/time/time_test.v +++ b/vlib/time/time_test.v @@ -25,9 +25,7 @@ fn test_is_leap_year() { } fn check_days_in_month(month int, year int, expected int) bool { - res := time.days_in_month(month, year) or { - return false - } + res := time.days_in_month(month, year) or { return false } return res == expected } diff --git a/vlib/time/time_windows.c.v b/vlib/time/time_windows.c.v index b02a532b4a..41eb46130e 100644 --- a/vlib/time/time_windows.c.v +++ b/vlib/time/time_windows.c.v @@ -120,7 +120,6 @@ pub fn (t Time) to_local_time() Time { // win_now calculates current time using winapi to get higher resolution on windows // 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{} C.GetSystemTimeAsFileTime(&ft_utc) @@ -144,7 +143,6 @@ fn win_now() Time { // 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) diff --git a/vlib/time/unix.v b/vlib/time/unix.v index 7a647d7016..214b54dab0 100644 --- a/vlib/time/unix.v +++ b/vlib/time/unix.v @@ -46,7 +46,6 @@ pub fn unix2(abs int, microsecond int) Time { } } -[inline] fn calculate_date_from_offset(day_offset_ int) (int, int, int) { mut day_offset := day_offset_ // Move offset to year 2001 as it's the start of a new 400-year cycle @@ -112,7 +111,6 @@ fn calculate_date_from_offset(day_offset_ int) (int, int, int) { return year, estimated_month + 1, day_offset + 1 } -[inline] fn calculate_time_from_offset(second_offset_ int) (int, int, int) { mut second_offset := second_offset_ if second_offset < 0 {