From efa8dcf4d2e21ffee5738cbf7afabaa495e063b6 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Wed, 4 Aug 2021 13:12:02 +0300 Subject: [PATCH] time: turn Time.unix to i64, so it can represent times before 1970-01-01, fix time operators, add more tests (#11050) --- vlib/net/websocket/websocket_client.v | 2 +- vlib/os/os_test.v | 2 +- vlib/rand/rand.v | 2 +- vlib/rand/random_identifiers_test.v | 2 +- vlib/time/chrono.v | 31 +++++++++++++++++++++++++ vlib/time/misc/misc.v | 2 +- vlib/time/operator.v | 6 ++--- vlib/time/parse.v | 4 ++-- vlib/time/time.v | 25 ++++++++++++-------- vlib/time/time_addition_test.v | 33 +++++++++++++++++++++++++++ vlib/time/time_nix.c.v | 4 ++-- vlib/time/time_test.v | 10 ++++---- vlib/time/time_windows.c.v | 16 ++++++------- vlib/time/unix.v | 4 ++-- vlib/vweb/assets/assets.v | 4 ++-- 15 files changed, 107 insertions(+), 40 deletions(-) create mode 100644 vlib/time/chrono.v create mode 100644 vlib/time/time_addition_test.v diff --git a/vlib/net/websocket/websocket_client.v b/vlib/net/websocket/websocket_client.v index 4408c7e4d1..48f8c5c8b1 100644 --- a/vlib/net/websocket/websocket_client.v +++ b/vlib/net/websocket/websocket_client.v @@ -38,7 +38,7 @@ pub mut: state State // current state of connection logger &log.Log // logger used to log messages resource_name string // name of current resource - last_pong_ut u64 // last time in unix time we got a pong message + last_pong_ut i64 // last time in unix time we got a pong message } // Flag represents different types of headers in websocket handshake diff --git a/vlib/os/os_test.v b/vlib/os/os_test.v index 04c14e738a..7107a34f2b 100644 --- a/vlib/os/os_test.v +++ b/vlib/os/os_test.v @@ -747,6 +747,6 @@ fn test_utime() { f.write_string(hello) or { panic(err) } atime := time.now().add_days(2).unix_time() mtime := time.now().add_days(4).unix_time() - os.utime(filename, atime, mtime) or { panic(err) } + os.utime(filename, int(atime), int(mtime)) or { panic(err) } assert os.file_last_mod_unix(filename) == mtime } diff --git a/vlib/rand/rand.v b/vlib/rand/rand.v index 0136ec1a96..e43a7abef4 100644 --- a/vlib/rand/rand.v +++ b/vlib/rand/rand.v @@ -275,7 +275,7 @@ const ( // users or business transactions. // (https://news.ycombinator.com/item?id=14526173) pub fn ulid() string { - return ulid_at_millisecond(time.utc().unix_time_milli()) + return ulid_at_millisecond(u64(time.utc().unix_time_milli())) } // ulid_at_millisecond does the same as `ulid` but takes a custom Unix millisecond timestamp via `unix_time_milli`. diff --git a/vlib/rand/random_identifiers_test.v b/vlib/rand/random_identifiers_test.v index 3daee4d464..d54fa84acf 100644 --- a/vlib/rand/random_identifiers_test.v +++ b/vlib/rand/random_identifiers_test.v @@ -37,7 +37,7 @@ fn test_ulids_max_start_character_is_ok() { } fn test_ulids_generated_in_the_same_millisecond_have_the_same_prefix() { - t := time.utc().unix_time_milli() + t := u64(time.utc().unix_time_milli()) mut ulid1 := '' mut ulid2 := '' mut ulid3 := '' diff --git a/vlib/time/chrono.v b/vlib/time/chrono.v new file mode 100644 index 0000000000..209d3dfe6c --- /dev/null +++ b/vlib/time/chrono.v @@ -0,0 +1,31 @@ +module time + +// days_from_civil - return the number of days since the +// Unix epoch 1970-01-01. A detailed description of the algorithm here +// is in: http://howardhinnant.github.io/date_algorithms.html +// Note that it will return negative values for days before 1970-01-01. +pub fn days_from_civil(oy int, m int, d int) int { + y := if m <= 2 { oy - 1 } else { oy } + era := y / 400 + yoe := y - era * 400 // [0, 399] + doy := (153 * (m + (if m > 2 { -3 } else { 9 })) + 2) / 5 + d - 1 // [0, 365] + doe := yoe * 365 + yoe / 4 - yoe / 100 + doy // [0, 146096] + return era * 146097 + doe - 719468 +} + +// portable_timegm does the same as C._mkgmtime, but unlike it, +// can work with dates before the Unix epoch of 1970-01-01 . +pub fn portable_timegm(t &C.tm) i64 { + mut year := t.tm_year + 1900 + mut month := t.tm_mon // 0-11 + if month > 11 { + year += month / 12 + month %= 12 + } else if month < 0 { + years_diff := (11 - month) / 12 + year -= years_diff + month += 12 * years_diff + } + days_since_1970 := i64(days_from_civil(year, month + 1, t.tm_mday)) + return 60 * (60 * (24 * days_since_1970 + t.tm_hour) + t.tm_min) + t.tm_sec +} diff --git a/vlib/time/misc/misc.v b/vlib/time/misc/misc.v index 0ae7b94d1d..b0be1269a7 100644 --- a/vlib/time/misc/misc.v +++ b/vlib/time/misc/misc.v @@ -9,5 +9,5 @@ const ( // random returns a random time struct in *the past*. pub fn random() time.Time { - return time.unix(int(rand.u64n(misc.start_time_unix))) + return time.unix(int(rand.i64n(misc.start_time_unix))) } diff --git a/vlib/time/operator.v b/vlib/time/operator.v index 7c6e616035..f62854983d 100644 --- a/vlib/time/operator.v +++ b/vlib/time/operator.v @@ -15,7 +15,7 @@ pub fn (t1 Time) < (t2 Time) bool { // Time subtract using operator overloading. [inline] pub fn (lhs Time) - (rhs Time) Duration { - lhs_micro := lhs.unix * 1000 * 1000 + u64(lhs.microsecond) - rhs_micro := rhs.unix * 1000 * 1000 + u64(rhs.microsecond) - return (i64(lhs_micro) - i64(rhs_micro)) * microsecond + lhs_micro := lhs.unix * 1_000_000 + lhs.microsecond + rhs_micro := rhs.unix * 1_000_000 + rhs.microsecond + return (lhs_micro - rhs_micro) * microsecond } diff --git a/vlib/time/parse.v b/vlib/time/parse.v index 95c5352429..b74cd412c7 100644 --- a/vlib/time/parse.v +++ b/vlib/time/parse.v @@ -131,9 +131,9 @@ pub fn parse_iso8601(s string) ?Time { } mut unix_time := t.unix if unix_offset < 0 { - unix_time -= u64(-unix_offset) + unix_time -= (-unix_offset) } else if unix_offset > 0 { - unix_time += u64(unix_offset) + unix_time += unix_offset } t = unix2(i64(unix_time), t.microsecond) return t diff --git a/vlib/time/time.v b/vlib/time/time.v index 14f0c7afd7..dbb012bf89 100644 --- a/vlib/time/time.v +++ b/vlib/time/time.v @@ -50,7 +50,7 @@ pub: minute int second int microsecond int - unix u64 + unix i64 } // FormatDelimiter contains different time formats. @@ -162,7 +162,7 @@ pub fn new_time(t Time) Time { tm_mon: t.month - 1 tm_year: t.year - 1900 } - utime := u64(make_unix_time(tt)) + utime := make_unix_time(tt) return Time{ ...t unix: utime @@ -171,21 +171,21 @@ pub fn new_time(t Time) Time { // unix_time returns Unix time. [inline] -pub fn (t Time) unix_time() int { - return int(t.unix) +pub fn (t Time) unix_time() i64 { + return t.unix } // unix_time_milli returns Unix time with millisecond resolution. [inline] -pub fn (t Time) unix_time_milli() u64 { - return t.unix * 1000 + u64(t.microsecond / 1000) +pub fn (t Time) unix_time_milli() i64 { + return t.unix * 1000 + (t.microsecond / 1000) } // add returns a new time that duration is added pub fn (t Time) add(d Duration) Time { - microseconds := i64(t.unix) * 1000 * 1000 + t.microsecond + d.microseconds() - unix := microseconds / (1000 * 1000) - micro := microseconds % (1000 * 1000) + microseconds := i64(t.unix) * 1_000_000 + t.microsecond + d.microseconds() + unix := microseconds / 1_000_000 + micro := microseconds % 1_000_000 return unix2(unix, int(micro)) } @@ -358,6 +358,11 @@ pub fn (t Time) str() string { return t.format_ss() } +// str returns time in the same format as `parse` expects ("YYYY-MM-DD HH:MM:SS"). +pub fn (t Time) debug() string { + return 'Time{ year: ${t.year:04} month: ${t.month:02} day: ${t.day:02} hour: ${t.hour:02} minute: ${t.minute:02} second: ${t.second:02} microsecond: ${t.microsecond:06} unix: ${t.unix:07} }' +} + // convert_ctime converts a C time to V time. fn convert_ctime(t C.tm, microsecond int) Time { return Time{ @@ -368,7 +373,7 @@ fn convert_ctime(t C.tm, microsecond int) Time { minute: t.tm_min second: t.tm_sec microsecond: time.microsecond - unix: u64(make_unix_time(t)) + unix: make_unix_time(t) } } diff --git a/vlib/time/time_addition_test.v b/vlib/time/time_addition_test.v new file mode 100644 index 0000000000..8fde8744c1 --- /dev/null +++ b/vlib/time/time_addition_test.v @@ -0,0 +1,33 @@ +import time + +fn test_add_to_day_in_the_previous_century() ? { + a := time.parse_iso8601('1900-01-01') ? + aa := a.add_days(180) + dump(a.debug()) + dump(aa.debug()) + assert aa.ymmdd() == '1900-06-29' +} + +fn test_add_to_day_in_the_past() ? { + a := time.parse_iso8601('1990-03-01') ? + aa := a.add_days(180) + assert aa.ymmdd() == '1990-08-27' +} + +fn test_add_to_day_in_the_recent_past() ? { + a := time.parse_iso8601('2021-03-01') ? + aa := a.add_days(180) + assert aa.ymmdd() == '2021-08-28' +} + +fn test_add_to_day_in_the_future_1() ? { + a := time.parse_iso8601('3000-11-01') ? + aa := a.add_days(180) + assert aa.ymmdd() == '3001-04-30' +} + +fn test_add_to_day_in_the_future_2() ? { + a := time.parse_iso8601('3000-12-30') ? + aa := a.add_days(180) + assert aa.ymmdd() == '3001-06-28' +} diff --git a/vlib/time/time_nix.c.v b/vlib/time/time_nix.c.v index 82ff5485af..29e0299809 100644 --- a/vlib/time/time_nix.c.v +++ b/vlib/time/time_nix.c.v @@ -23,8 +23,8 @@ fn C.timegm(&C.tm) C.time_t // fn C.gmtime_r(&tm, &gbuf) fn C.localtime_r(t &time_t, tm &C.tm) -fn make_unix_time(t C.tm) int { - return int(C.timegm(&t)) +fn make_unix_time(t C.tm) i64 { + return i64(C.timegm(&t)) } // local returns t with the location set to local time. diff --git a/vlib/time/time_test.v b/vlib/time/time_test.v index 0511916436..dbd3c48d65 100644 --- a/vlib/time/time_test.v +++ b/vlib/time/time_test.v @@ -160,18 +160,18 @@ fn test_add() { t2 := time_to_test.add(duration) assert t2.second == t1.second + d_seconds assert t2.microsecond == t1.microsecond + d_microseconds - assert t2.unix == t1.unix + u64(d_seconds) + assert t2.unix == t1.unix + d_seconds t3 := time_to_test.add(-duration) assert t3.second == t1.second - d_seconds assert t3.microsecond == t1.microsecond - d_microseconds - assert t3.unix == t1.unix - u64(d_seconds) + assert t3.unix == t1.unix - d_seconds } fn test_add_days() { num_of_days := 3 t := time_to_test.add_days(num_of_days) assert t.day == time_to_test.day + num_of_days - assert t.unix == time_to_test.unix + 86400 * u64(num_of_days) + assert t.unix == time_to_test.unix + 86400 * num_of_days } fn test_str() { @@ -217,8 +217,8 @@ fn test_unix_time() { // utm1 := t1.unix_time_milli() utm2 := t2.unix_time_milli() - assert (utm1 - u64(ut1) * 1000) < 1000 - assert (utm2 - u64(ut2) * 1000) < 1000 + assert (utm1 - ut1 * 1000) < 1000 + assert (utm2 - ut2 * 1000) < 1000 // // println('utm1: $utm1 | utm2: $utm2') assert utm2 - utm1 > 2 diff --git a/vlib/time/time_windows.c.v b/vlib/time/time_windows.c.v index d44e6c9413..a810cb97d7 100644 --- a/vlib/time/time_windows.c.v +++ b/vlib/time/time_windows.c.v @@ -52,14 +52,12 @@ struct C.timespec { tv_nsec i64 } -fn C._mkgmtime(&C.tm) C.time_t - fn C.QueryPerformanceCounter(&u64) C.BOOL fn C.QueryPerformanceFrequency(&u64) C.BOOL -fn make_unix_time(t C.tm) int { - return int(C._mkgmtime(&t)) +fn make_unix_time(t C.tm) i64 { + return portable_timegm(&t) } fn init_win_time_freq() u64 { @@ -91,7 +89,7 @@ fn vpc_now() u64 { } // local_as_unix_time returns the current local time as unix time -fn local_as_unix_time() int { +fn local_as_unix_time() i64 { t := C.time(0) tm := C.localtime(&t) return make_unix_time(tm) @@ -117,7 +115,7 @@ pub fn (t Time) local() Time { minute: st_local.minute second: st_local.second // These are the same microsecond: st_local.millisecond * 1000 - unix: u64(st_local.unix_time()) + unix: st_local.unix_time() } return t_local } @@ -140,7 +138,7 @@ fn win_now() Time { minute: st_local.minute second: st_local.second microsecond: st_local.millisecond * 1000 - unix: u64(st_local.unix_time()) + unix: st_local.unix_time() } return t } @@ -161,13 +159,13 @@ fn win_utc() Time { minute: st_utc.minute second: st_utc.second microsecond: st_utc.millisecond * 1000 - unix: u64(st_utc.unix_time()) + unix: st_utc.unix_time() } return t } // unix_time returns Unix time. -pub fn (st SystemTime) unix_time() int { +pub fn (st SystemTime) unix_time() i64 { tt := C.tm{ tm_sec: st.second tm_min: st.minute diff --git a/vlib/time/unix.v b/vlib/time/unix.v index ac9d0100b6..7b6ec873b5 100644 --- a/vlib/time/unix.v +++ b/vlib/time/unix.v @@ -20,7 +20,7 @@ pub fn unix(abs int) Time { hour: hr minute: min second: sec - unix: u64(abs) + unix: abs } } @@ -42,7 +42,7 @@ pub fn unix2(abs i64, microsecond int) Time { minute: min second: sec microsecond: microsecond - unix: u64(abs) + unix: abs } } diff --git a/vlib/vweb/assets/assets.v b/vlib/vweb/assets/assets.v index 1240e4123c..09a4ab97b7 100644 --- a/vlib/vweb/assets/assets.v +++ b/vlib/vweb/assets/assets.v @@ -106,7 +106,7 @@ fn (am AssetManager) combine(asset_type string, to_file bool) string { fn (am AssetManager) get_cache_key(asset_type string) string { mut files_salt := '' - mut latest_modified := u64(0) + mut latest_modified := i64(0) for asset in am.get_assets(asset_type) { files_salt += asset.file_path if asset.last_modified.unix > latest_modified { @@ -151,7 +151,7 @@ fn (mut am AssetManager) add(asset_type string, file string) bool { asset := Asset{ file_path: file last_modified: time.Time{ - unix: u64(os.file_last_mod_unix(file)) + unix: os.file_last_mod_unix(file) } } if asset_type == 'css' {