1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

time: make parse_iso8601 support a date only format (#7277)

This commit is contained in:
zakuro 2020-12-16 20:10:02 +09:00 committed by GitHub
parent 88a8507dd8
commit 6a74058190
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 90 additions and 59 deletions

View File

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

View File

@ -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, &microsecond, 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()
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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