diff options
author | Indrajith K L | 2022-12-03 17:00:20 +0530 |
---|---|---|
committer | Indrajith K L | 2022-12-03 17:00:20 +0530 |
commit | f5c4671bfbad96bf346bd7e9a21fc4317b4959df (patch) | |
tree | 2764fc62da58f2ba8da7ed341643fc359873142f /v_windows/v/vlib/time | |
download | cli-tools-windows-f5c4671bfbad96bf346bd7e9a21fc4317b4959df.tar.gz cli-tools-windows-f5c4671bfbad96bf346bd7e9a21fc4317b4959df.tar.bz2 cli-tools-windows-f5c4671bfbad96bf346bd7e9a21fc4317b4959df.zip |
Diffstat (limited to 'v_windows/v/vlib/time')
27 files changed, 2616 insertions, 0 deletions
diff --git a/v_windows/v/vlib/time/Y2K38_test.v b/v_windows/v/vlib/time/Y2K38_test.v new file mode 100644 index 0000000..0ebc0ef --- /dev/null +++ b/v_windows/v/vlib/time/Y2K38_test.v @@ -0,0 +1,13 @@ +import time + +fn test_time_after_2038_works() { + after_time := time.parse_iso8601('2037-07-23') or { time.now() } + dump(after_time) + error_time := after_time.add_days(180) + dump(error_time) + assert error_time.str() == '2038-01-19 00:00:00' + // NB: the next date is after Y2K38, it should NOT wrap: + error_time2 := after_time.add_days(181) + dump(error_time2) + assert error_time2.str() == '2038-01-20 00:00:00' +} diff --git a/v_windows/v/vlib/time/chrono.c.v b/v_windows/v/vlib/time/chrono.c.v new file mode 100644 index 0000000..4d6de7d --- /dev/null +++ b/v_windows/v/vlib/time/chrono.c.v @@ -0,0 +1,18 @@ +module time + +// 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/v_windows/v/vlib/time/chrono.v b/v_windows/v/vlib/time/chrono.v new file mode 100644 index 0000000..58f1ef8 --- /dev/null +++ b/v_windows/v/vlib/time/chrono.v @@ -0,0 +1,14 @@ +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 +} diff --git a/v_windows/v/vlib/time/format.v b/v_windows/v/vlib/time/format.v new file mode 100644 index 0000000..6a77bb3 --- /dev/null +++ b/v_windows/v/vlib/time/format.v @@ -0,0 +1,174 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module time + +// format returns a date string in "YYYY-MM-DD HH:MM" format (24h). +pub fn (t Time) format() string { + return t.get_fmt_str(.hyphen, .hhmm24, .yyyymmdd) +} + +// format_ss returns a date string in "YYYY-MM-DD HH:MM:SS" format (24h). +pub fn (t Time) format_ss() string { + return t.get_fmt_str(.hyphen, .hhmmss24, .yyyymmdd) +} + +// format_ss_milli returns a date string in "YYYY-MM-DD HH:MM:SS.123" format (24h). +pub fn (t Time) format_ss_milli() string { + return t.get_fmt_str(.hyphen, .hhmmss24_milli, .yyyymmdd) +} + +// format_ss_micro returns a date string in "YYYY-MM-DD HH:MM:SS.123456" format (24h). +pub fn (t Time) format_ss_micro() string { + return t.get_fmt_str(.hyphen, .hhmmss24_micro, .yyyymmdd) +} + +// hhmm returns a date string in "HH:MM" format (24h). +pub fn (t Time) hhmm() string { + return t.get_fmt_time_str(.hhmm24) +} + +// hhmmss returns a date string in "HH:MM:SS" format (24h). +pub fn (t Time) hhmmss() string { + return t.get_fmt_time_str(.hhmmss24) +} + +// hhmm12 returns a date string in "HH:MM" format (12h). +pub fn (t Time) hhmm12() string { + return t.get_fmt_time_str(.hhmm12) +} + +// ymmdd returns a date string in "YYYY-MM-DD" format. +pub fn (t Time) ymmdd() string { + return t.get_fmt_date_str(.hyphen, .yyyymmdd) +} + +// ddmmy returns a date string in "DD.MM.YYYY" format. +pub fn (t Time) ddmmy() string { + return t.get_fmt_date_str(.dot, .ddmmyyyy) +} + +// md returns a date string in "MMM D" format. +pub fn (t Time) md() string { + return t.get_fmt_date_str(.space, .mmmd) +} + +// clean returns a date string in a following format: +// - a date string in "HH:MM" format (24h) for current day +// - a date string in "MMM D HH:MM" format (24h) for date of current year +// - a date string formatted with format function for other dates +pub fn (t Time) clean() string { + znow := now() + // Today + if t.month == znow.month && t.year == znow.year && t.day == znow.day { + return t.get_fmt_time_str(.hhmm24) + } + // This year + if t.year == znow.year { + return t.get_fmt_str(.space, .hhmm24, .mmmd) + } + return t.format() +} + +// clean12 returns a date string in a following format: +// - a date string in "HH:MM" format (12h) for current day +// - a date string in "MMM D HH:MM" format (12h) for date of current year +// - a date string formatted with format function for other dates +pub fn (t Time) clean12() string { + znow := now() + // Today + if t.month == znow.month && t.year == znow.year && t.day == znow.day { + return t.get_fmt_time_str(.hhmm12) + } + // This year + if t.year == znow.year { + return t.get_fmt_str(.space, .hhmm12, .mmmd) + } + return t.format() +} + +// get_fmt_time_str returns a date string with specified FormatTime type. +pub fn (t Time) get_fmt_time_str(fmt_time FormatTime) string { + if fmt_time == .no_time { + return '' + } + tp := if t.hour > 11 { 'p.m.' } else { 'a.m.' } + hour_ := if t.hour > 12 { + t.hour - 12 + } else if t.hour == 0 { + 12 + } else { + t.hour + } + return match fmt_time { + .hhmm12 { '$hour_:${t.minute:02d} $tp' } + .hhmm24 { '${t.hour:02d}:${t.minute:02d}' } + .hhmmss12 { '$hour_:${t.minute:02d}:${t.second:02d} $tp' } + .hhmmss24 { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}' } + .hhmmss24_milli { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.microsecond / 1000):03d}' } + .hhmmss24_micro { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${t.microsecond:06d}' } + else { 'unknown enumeration $fmt_time' } + } +} + +// get_fmt_time_str returns a date string with specified +// FormatDelimiter and FormatDate type. +pub fn (t Time) get_fmt_date_str(fmt_dlmtr FormatDelimiter, fmt_date FormatDate) string { + if fmt_date == .no_date { + return '' + } + month := t.smonth() + year := '${(t.year % 100):02d}' + mut res := match fmt_date { + .ddmmyy { '${t.day:02d}|${t.month:02d}|$year' } + .ddmmyyyy { '${t.day:02d}|${t.month:02d}|${t.year:04d}' } + .mmddyy { '${t.month:02d}|${t.day:02d}|$year' } + .mmddyyyy { '${t.month:02d}|${t.day:02d}|${t.year:04d}' } + .mmmd { '$month|$t.day' } + .mmmdd { '$month|${t.day:02d}' } + .mmmddyy { '$month|${t.day:02d}|$year' } + .mmmddyyyy { '$month|${t.day:02d}|${t.year:04d}' } + .yyyymmdd { '${t.year:04d}|${t.month:02d}|${t.day:02d}' } + .yymmdd { '$year|${t.month:02d}|${t.day:02d}' } + else { 'unknown enumeration $fmt_date' } + } + del := match fmt_dlmtr { + .dot { '.' } + .hyphen { '-' } + .slash { '/' } + .space { ' ' } + .no_delimiter { '' } + } + res = res.replace('|', del) + return res +} + +// get_fmt_str returns a date string with specified FormatDelimiter, +// FormatTime type, and FormatDate type. +pub fn (t Time) get_fmt_str(fmt_dlmtr FormatDelimiter, fmt_time FormatTime, fmt_date FormatDate) string { + if fmt_date == .no_date { + if fmt_time == .no_time { + // saving one function call although it's checked in + // t.get_fmt_time_str(fmt_time) in the beginning + return '' + } else { + return t.get_fmt_time_str(fmt_time) + } + } else { + if fmt_time != .no_time { + dstr := t.get_fmt_date_str(fmt_dlmtr, fmt_date) + tstr := t.get_fmt_time_str(fmt_time) + return '$dstr $tstr' + } else { + return t.get_fmt_date_str(fmt_dlmtr, fmt_date) + } + } +} + +// This is just a TEMPORARY function for cookies and their expire dates +pub fn (t Time) utc_string() string { + day_str := t.weekday_str() + month_str := t.smonth() + utc_string := '$day_str, $t.day $month_str $t.year ${t.hour:02d}:${t.minute:02d}:${t.second:02d} UTC' + return utc_string +} diff --git a/v_windows/v/vlib/time/misc/misc.v b/v_windows/v/vlib/time/misc/misc.v new file mode 100644 index 0000000..b0be126 --- /dev/null +++ b/v_windows/v/vlib/time/misc/misc.v @@ -0,0 +1,13 @@ +module misc + +import rand +import time + +const ( + start_time_unix = time.now().unix // start_time_unix is set when the program is started. +) + +// random returns a random time struct in *the past*. +pub fn random() time.Time { + return time.unix(int(rand.i64n(misc.start_time_unix))) +} diff --git a/v_windows/v/vlib/time/misc/misc_test.v b/v_windows/v/vlib/time/misc/misc_test.v new file mode 100644 index 0000000..9bcc8ad --- /dev/null +++ b/v_windows/v/vlib/time/misc/misc_test.v @@ -0,0 +1,17 @@ +import time.misc as tmisc +import rand + +fn test_random() { + // guarantee CI test stability, by seeding the random number generator with a known seed + rand.seed([u32(0), 0]) + t1 := tmisc.random() + t2 := tmisc.random() + t3 := tmisc.random() + t4 := tmisc.random() + assert t1.unix != t2.unix + assert t1.unix != t3.unix + assert t1.unix != t4.unix + assert t2.unix != t3.unix + assert t2.unix != t4.unix + assert t3.unix != t4.unix +} diff --git a/v_windows/v/vlib/time/operator.v b/v_windows/v/vlib/time/operator.v new file mode 100644 index 0000000..f628549 --- /dev/null +++ b/v_windows/v/vlib/time/operator.v @@ -0,0 +1,21 @@ +module time + +// operator `==` returns true if provided time is equal to time +[inline] +pub fn (t1 Time) == (t2 Time) bool { + return t1.unix == t2.unix && t1.microsecond == t2.microsecond +} + +// operator `<` returns true if provided time is less than time +[inline] +pub fn (t1 Time) < (t2 Time) bool { + return t1.unix < t2.unix || (t1.unix == t2.unix && t1.microsecond < t2.microsecond) +} + +// Time subtract using operator overloading. +[inline] +pub fn (lhs Time) - (rhs Time) Duration { + 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/v_windows/v/vlib/time/operator_test.v b/v_windows/v/vlib/time/operator_test.v new file mode 100644 index 0000000..5f3e1b7 --- /dev/null +++ b/v_windows/v/vlib/time/operator_test.v @@ -0,0 +1,391 @@ +module time + +fn assert_greater_time(ms int, t1 Time) { + sleep(ms * millisecond) + t2 := now() + assert t2 > t1 +} + +// Tests the now in all platform and the gt operator function with at least ms resolution +fn test_now_always_results_in_greater_time() { + t1 := now() + $if macos { + assert_greater_time(1, t1) + return + } + $if windows { + // Lower resolution of time for windows + assert_greater_time(15, t1) + return + } + $if linux { + assert_greater_time(1, t1) + return + } + $if solaris { + assert_greater_time(1, t1) + return + } + // other platforms may have more accurate resolution, + // but we do not know that ... so wait at least 1s: + assert_greater_time(1001, t1) +} + +fn test_time1_should_be_same_as_time2() { + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + t2 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + assert t1 == t2 +} + +fn test_time1_should_not_be_same_as_time2() { + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + // Difference is one microsecond + t2 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 101 + }) + t3 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 0 + }) + // Difference is one second + t4 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 4 + microsecond: 0 + }) + assert t1 != t2 + assert t3 != t4 +} + +fn test_time1_should_be_greater_than_time2() { + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 102 + }) + // Difference is one microsecond + t2 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 101 + }) + t3 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 5 + microsecond: 0 + }) + // Difference is one second + t4 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 4 + microsecond: 0 + }) + assert t1 > t2 + assert t3 > t4 +} + +fn test_time2_should_be_less_than_time1() { + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 102 + }) + // Difference is one microsecond + t2 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 101 + }) + t3 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 0 + }) + // Difference is one second + t4 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 2 + microsecond: 0 + }) + assert t2 < t1 + assert t4 < t3 +} + +fn test_time1_should_be_greater_or_equal_to_time2_when_gt() { + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 102 + }) + // Difference is one microsecond + t2 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 101 + }) + t3 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 5 + microsecond: 0 + }) + // Difference is one second + t4 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 4 + microsecond: 0 + }) + assert t1 >= t2 + assert t3 >= t4 +} + +fn test_time1_should_be_greater_or_equal_to_time2_when_eq() { + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + // Difference is one microsecond + t2 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + t3 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 0 + }) + // Difference is one second + t4 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 0 + }) + assert t1 >= t2 + assert t3 >= t4 +} + +fn test_time1_should_be_less_or_equal_to_time2_when_lt() { + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + // Difference is one microsecond + t2 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 101 + }) + t3 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 0 + }) + // Difference is one second + t4 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 4 + microsecond: 0 + }) + assert t1 <= t2 + assert t3 <= t4 +} + +fn test_time1_should_be_less_or_equal_to_time2_when_eq() { + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + // Difference is one microsecond + t2 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + t3 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 0 + }) + // Difference is one second + t4 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 0 + }) + assert t1 <= t2 + assert t3 <= t4 +} + +fn test_time2_copied_from_time1_should_be_equal() { + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + t2 := new_time(t1) + assert t2 == t1 +} + +fn test_subtract() { + d_seconds := 3 + d_microseconds := 13 + duration := d_seconds * second + d_microseconds * microsecond + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + t2 := unix2(i64(t1.unix) + d_seconds, t1.microsecond + d_microseconds) + d1 := t2 - t1 + d2 := t1 - t2 + assert d1 > 0 + assert d1 == duration + assert d2 < 0 + assert d2 == -duration +} diff --git a/v_windows/v/vlib/time/parse.c.v b/v_windows/v/vlib/time/parse.c.v new file mode 100644 index 0000000..b74cd41 --- /dev/null +++ b/v_windows/v/vlib/time/parse.c.v @@ -0,0 +1,140 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module time + +// parse returns time from a date string in "YYYY-MM-DD HH:MM:SS" format. +pub fn parse(s string) ?Time { + pos := s.index(' ') or { return error('Invalid time format: $s') } + symd := s[..pos] + ymd := symd.split('-') + if ymd.len != 3 { + return error('Invalid time format: $s') + } + shms := s[pos..] + hms := shms.split(':') + hour_ := hms[0][1..] + minute_ := hms[1] + second_ := hms[2] + res := new_time(Time{ + year: ymd[0].int() + month: ymd[1].int() + day: ymd[2].int() + hour: hour_.int() + minute: minute_.int() + second: second_.int() + }) + return res +} + +// parse_rfc2822 returns time from a date string in RFC 2822 datetime format. +pub fn parse_rfc2822(s string) ?Time { + fields := s.split(' ') + if fields.len < 5 { + return error('Invalid time format: $s') + } + pos := months_string.index(fields[2]) or { return error('Invalid time format: $s') } + mm := pos / 3 + 1 + unsafe { + tmstr := malloc_noscan(s.len * 2) + count := C.snprintf(&char(tmstr), (s.len * 2), c'%s-%02d-%s %s', fields[3].str, + mm, fields[1].str, fields[4].str) + return parse(tos(tmstr, count)) + } +} + +// ----- iso8601 ----- +const ( + err_invalid_8601 = '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(&char(s.str), c'%4d-%2d-%2d%c', &year, &month, &day, &dummy) } + if count != 3 { + return error(time.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(&char(s.str), c'%2d:%2d:%2d.%6d%c%2d:%2d', &hour_, &minute_, &second_, + µsecond_, &char(&plus_min_z), &offset_hour, &offset_minute) + } + // Missread microsecond ([Sec Hour Minute].len == 3 < 4) + if count < 4 { + count = unsafe { + C.sscanf(&char(s.str), c'%2d:%2d:%2d%c%2d:%2d', &hour_, &minute_, &second_, + &char(&plus_min_z), &offset_hour, &offset_minute) + } + count++ // Increment count because skipped microsecond + } + if count < 4 { + return error(time.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 error(time.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 { + 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 error(time.err_invalid_8601) + } + 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 + month: month + day: day + hour: hour_ + minute: minute_ + second: second_ + microsecond: microsecond_ + }) + if is_local_time { + return t // Time already local time + } + mut unix_time := t.unix + if unix_offset < 0 { + unix_time -= (-unix_offset) + } else if unix_offset > 0 { + unix_time += unix_offset + } + t = unix2(i64(unix_time), t.microsecond) + return t +} diff --git a/v_windows/v/vlib/time/parse.js.v b/v_windows/v/vlib/time/parse.js.v new file mode 100644 index 0000000..1303561 --- /dev/null +++ b/v_windows/v/vlib/time/parse.js.v @@ -0,0 +1,24 @@ +module time + +// parse returns time from a date string. +// +// TODO(playX): JS Date expects iso8061 format of strings and other formats +// are implementation dependant, we probably want to implement parsing in JS. +pub fn parse(s string) Time { + mut res := Time{} + #let date = new Date(s.str) + #res.year.val = date.getFullYear() + #res.month.val = date.getMonth() + #res.day.val = date.getDay() + #res.hour.val = date.getHours() + #res.minute.val = date.getMinutes() + #res.second.val = date.getSeconds() + #res.microsecond.val = date.getMilliseconds() * 1000 + #res.unix.val = (date.getTime() / 1000).toFixed(0) + + return res +} + +pub fn parse_iso8601(s string) ?Time { + return parse(s) +} diff --git a/v_windows/v/vlib/time/parse_test.v b/v_windows/v/vlib/time/parse_test.v new file mode 100644 index 0000000..de7df37 --- /dev/null +++ b/v_windows/v/vlib/time/parse_test.v @@ -0,0 +1,138 @@ +import time + +fn test_parse() { + s := '2018-01-27 12:48:34' + t := time.parse(s) or { + assert false + return + } + assert t.year == 2018 && t.month == 1 && t.day == 27 && t.hour == 12 && t.minute == 48 + && t.second == 34 + assert t.unix == 1517057314 +} + +fn test_parse_invalid() { + s := 'Invalid time string' + time.parse(s) or { + assert true + return + } + assert false +} + +fn test_parse_rfc2822() { + s1 := 'Thu, 12 Dec 2019 06:07:45 GMT' + t1 := time.parse_rfc2822(s1) or { + assert false + return + } + assert t1.year == 2019 && t1.month == 12 && t1.day == 12 && t1.hour == 6 && t1.minute == 7 + && t1.second == 45 + assert t1.unix == 1576130865 + s2 := 'Thu 12 Dec 2019 06:07:45 +0800' + t2 := time.parse_rfc2822(s2) or { + assert false + return + } + assert t2.year == 2019 && t2.month == 12 && t2.day == 12 && t2.hour == 6 && t2.minute == 7 + && t2.second == 45 + assert t2.unix == 1576130865 +} + +fn test_parse_rfc2822_invalid() { + s3 := 'Thu 12 Foo 2019 06:07:45 +0800' + time.parse_rfc2822(s3) or { + assert true + return + } + assert false +} + +fn test_parse_iso8601() { + formats := [ + '2020-06-05T15:38:06Z', + '2020-06-05T15:38:06.015959Z', + '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', + ] + times := [ + [2020, 6, 5, 15, 38, 6, 0], + [2020, 6, 5, 15, 38, 6, 15959], + [2020, 6, 5, 15, 38, 6, 15959], + [2020, 6, 5, 13, 38, 6, 15959], + [2020, 6, 5, 17, 38, 6, 15959], + [2020, 11, 5, 15, 38, 6, 15959], + ] + for i, format in formats { + t := time.parse_iso8601(format) or { + assert false + continue + } + year := times[i][0] + assert t.year == year + month := times[i][1] + assert t.month == month + day := times[i][2] + assert t.day == day + hour := times[i][3] + assert t.hour == hour + minute := times[i][4] + assert t.minute == minute + second := times[i][5] + assert t.second == second + microsecond := times[i][6] + assert t.microsecond == microsecond + } +} + +fn test_parse_iso8601_local() { + format := '2020-06-05T15:38:06.015959' + t := time.parse_iso8601(format) or { + assert false + return + } + assert t.year == 2020 + assert t.month == 6 + assert t.day == 5 + assert t.hour == 15 + assert t.minute == 38 + assert t.second == 6 + assert t.microsecond == 15959 +} + +fn test_parse_iso8601_invalid() { + formats := [ + '', + '2020-06-05X15:38:06.015959Z', + '2020-06-05T15:38:06.015959X', + '2020-06-05T15:38:06.015959+0000', + '2020-06-05T', + '2020-06-05Z', + '2020-06-05+00:00', + '15:38:06', + ] + for format in formats { + time.parse_iso8601(format) or { + assert true + continue + } + 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/v_windows/v/vlib/time/private_test.v b/v_windows/v/vlib/time/private_test.v new file mode 100644 index 0000000..8dde561 --- /dev/null +++ b/v_windows/v/vlib/time/private_test.v @@ -0,0 +1,13 @@ +// tests that use and test private functions +module time + +// test the old behavor is same as new, the unix time should always be local time +fn test_new_is_same_as_old_for_all_platforms() { + t := C.time(0) + tm := C.localtime(&t) + old_time := convert_ctime(tm, 0) + new_time := now() + diff := new_time.unix - old_time.unix + // could in very rare cases be that the second changed between calls + assert (diff >= 0 && diff <= 1) == true +} diff --git a/v_windows/v/vlib/time/stopwatch.v b/v_windows/v/vlib/time/stopwatch.v new file mode 100644 index 0000000..569e10c --- /dev/null +++ b/v_windows/v/vlib/time/stopwatch.v @@ -0,0 +1,72 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module time + +pub struct StopWatchOptions { + auto_start bool = true +} + +// StopWatch is used to measure elapsed time. +pub struct StopWatch { +mut: + elapsed u64 +pub mut: + start u64 + end u64 +} + +// new_stopwatch initializes a new StopWatch with the current time as start. +pub fn new_stopwatch(opts StopWatchOptions) StopWatch { + mut initial := u64(0) + if opts.auto_start { + initial = sys_mono_now() + } + return StopWatch{ + elapsed: 0 + start: initial + end: 0 + } +} + +// start starts the stopwatch. If the timer was paused, restarts counting. +pub fn (mut t StopWatch) start() { + t.start = sys_mono_now() + t.end = 0 +} + +// restart restarts the stopwatch. If the timer was paused, restarts counting. +pub fn (mut t StopWatch) restart() { + t.start = sys_mono_now() + t.end = 0 + t.elapsed = 0 +} + +// stop stops the timer, by setting the end time to the current time. +pub fn (mut t StopWatch) stop() { + t.end = sys_mono_now() +} + +// pause resets the `start` time and adds the current elapsed time to `elapsed`. +pub fn (mut t StopWatch) pause() { + if t.start > 0 { + if t.end == 0 { + t.elapsed += sys_mono_now() - t.start + } else { + t.elapsed += t.end - t.start + } + } + t.start = 0 +} + +// elapsed returns the Duration since the last start call +pub fn (t StopWatch) elapsed() Duration { + if t.start > 0 { + if t.end == 0 { + return Duration(i64(sys_mono_now() - t.start + t.elapsed)) + } else { + return Duration(i64(t.end - t.start + t.elapsed)) + } + } + return Duration(i64(t.elapsed)) +} diff --git a/v_windows/v/vlib/time/stopwatch_test.v b/v_windows/v/vlib/time/stopwatch_test.v new file mode 100644 index 0000000..49d005a --- /dev/null +++ b/v_windows/v/vlib/time/stopwatch_test.v @@ -0,0 +1,36 @@ +import time + +// NB: on CI jobs, especially msvc ones, sleep_ms may sleep for much more +// time than you have specified. To avoid false positives from CI test +// failures, some of the asserts will be run only if you pass `-d stopwatch` +fn test_stopwatch_works_as_intended() { + mut sw := time.new_stopwatch() + // sample code that you want to measure: + println('Hello world') + time.sleep(1 * time.millisecond) + // + println('Greeting the world took: ${sw.elapsed().nanoseconds()}ns') + assert sw.elapsed().nanoseconds() > 0 +} + +fn test_stopwatch_time_between_pause_and_start_should_be_skipped_in_elapsed() { + println('Testing pause function') + mut sw := time.new_stopwatch() + time.sleep(10 * time.millisecond) // A + eprintln('Elapsed after 10ms nap: ${sw.elapsed().milliseconds()}ms') + assert sw.elapsed().milliseconds() >= 8 // sometimes it sleeps for 9ms on windows.. + sw.pause() + time.sleep(10 * time.millisecond) + eprintln('Elapsed after pause and another 10ms nap: ${sw.elapsed().milliseconds()}ms') + assert sw.elapsed().milliseconds() >= 8 + $if stopwatch ? { + assert sw.elapsed().milliseconds() < 20 + } + sw.start() + time.sleep(10 * time.millisecond) // B + eprintln('Elapsed after resume and another 10ms nap: ${sw.elapsed().milliseconds()}ms') + assert sw.elapsed().milliseconds() >= 18 + $if stopwatch ? { + assert sw.elapsed().milliseconds() < 30 + } +} diff --git a/v_windows/v/vlib/time/time.c.v b/v_windows/v/vlib/time/time.c.v new file mode 100644 index 0000000..1bee150 --- /dev/null +++ b/v_windows/v/vlib/time/time.c.v @@ -0,0 +1,125 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module time + +#include <time.h> + +// C.timeval represents a C time value. +pub struct C.timeval { + tv_sec u64 + tv_usec u64 +} + +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 { + return darwin_now() + } + $if windows { + return win_now() + } + $if solaris { + return solaris_now() + } + $if linux || android { + return linux_now() + } + // defaults to most common feature, the microsecond precision is not available + // in this API call + t := C.time(0) + now := C.localtime(&t) + return convert_ctime(*now, 0) +} + +// utc returns the current UTC time. +pub fn utc() Time { + $if macos { + return darwin_utc() + } + $if windows { + return win_utc() + } + $if solaris { + return solaris_utc() + } + $if linux || android { + 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(i64(t), 0) +} + +// new_time returns a time struct with calculated Unix time. +pub fn new_time(t Time) Time { + if t.unix != 0 { + return t + } + tt := C.tm{ + tm_sec: t.second + tm_min: t.minute + tm_hour: t.hour + tm_mday: t.day + tm_mon: t.month - 1 + tm_year: t.year - 1900 + } + utime := make_unix_time(tt) + return Time{ + ...t + unix: utime + } +} + +// ticks returns a number of milliseconds elapsed since system start. +pub fn ticks() i64 { + $if windows { + return C.GetTickCount() + } $else { + ts := C.timeval{} + C.gettimeofday(&ts, 0) + return i64(ts.tv_sec * u64(1000) + (ts.tv_usec / u64(1000))) + } + // t := i64(C.mach_absolute_time()) + // # Nanoseconds elapsedNano = AbsoluteToNanoseconds( *(AbsoluteTime *) &t ); + // # return (double)(* (uint64_t *) &elapsedNano) / 1000000; +} + +/* +// sleep makes the calling thread sleep for a given number of seconds. +[deprecated: 'call time.sleep(n * time.second)'] +pub fn sleep(seconds int) { + wait(seconds * time.second) +} +*/ + +// str returns time in the same format as `parse` expects ("YYYY-MM-DD HH:MM:SS"). +pub fn (t Time) str() string { + // TODO Define common default format for + // `str` and `parse` and use it in both ways + return t.format_ss() +} + +// convert_ctime converts a C time to V time. +fn convert_ctime(t C.tm, microsecond int) Time { + return Time{ + year: t.tm_year + 1900 + month: t.tm_mon + 1 + day: t.tm_mday + hour: t.tm_hour + minute: t.tm_min + second: t.tm_sec + microsecond: microsecond + unix: make_unix_time(t) + } +} + +pub const ( + infinite = Duration(C.INT64_MAX) +) diff --git a/v_windows/v/vlib/time/time.js.v b/v_windows/v/vlib/time/time.js.v new file mode 100644 index 0000000..9d08e08 --- /dev/null +++ b/v_windows/v/vlib/time/time.js.v @@ -0,0 +1,38 @@ +module time + +pub fn now() Time { + mut res := Time{} + #let date = new Date() + #res.year.val = date.getFullYear() + #res.month.val = date.getMonth() + #res.day.val = date.getDay() + #res.hour.val = date.getHours() + #res.minute.val = date.getMinutes() + #res.second.val = date.getSeconds() + #res.microsecond.val = date.getMilliseconds() * 1000 + #res.unix.val = (date.getTime() / 1000).toFixed(0) + + return res +} + +pub fn utc() Time { + mut res := Time{} + #let date = new Date() + #res.year.val = date.getUTCFullYear() + #res.month.val = date.getUTCMonth() + #res.day.val = date.getUTCDay() + #res.hour.val = date.getUTCHours() + #res.minute.val = date.getUTCMinutes() + #res.second.val = date.getUTCSeconds() + #res.microsecond.val = date.getUTCMilliseconds() * 1000 + #res.unix.val = (date.getTime() / 1000).toFixed(0) + + return res +} + +/// Returns local time +pub fn (t Time) local() Time { + // TODO: Does this actually correct? JS clock is always set to timezone or no? + // if it is not we should try to use Intl for getting local time. + return t +} diff --git a/v_windows/v/vlib/time/time.v b/v_windows/v/vlib/time/time.v new file mode 100644 index 0000000..fa50918 --- /dev/null +++ b/v_windows/v/vlib/time/time.v @@ -0,0 +1,315 @@ +module time + +pub const ( + days_string = 'MonTueWedThuFriSatSun' + month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + months_string = 'JanFebMarAprMayJunJulAugSepOctNovDec' + // The unsigned zero year for internal calculations. + // Must be 1 mod 400, and times before it will not compute correctly, + // but otherwise can be changed at will. + absolute_zero_year = i64(-292277022399) // as i64 + seconds_per_minute = 60 + seconds_per_hour = 60 * seconds_per_minute + seconds_per_day = 24 * seconds_per_hour + seconds_per_week = 7 * seconds_per_day + days_per_400_years = 365 * 400 + 97 + days_per_100_years = 365 * 100 + 24 + days_per_4_years = 365 * 4 + 1 + days_before = [ + 0, + 31, + 31 + 28, + 31 + 28 + 31, + 31 + 28 + 31 + 30, + 31 + 28 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, + ] + long_days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', + 'Sunday', + ] +) + +// Time contains various time units for a point in time. +pub struct Time { +pub: + year int + month int + day int + hour int + minute int + second int + microsecond int + unix i64 +} + +// FormatDelimiter contains different time formats. +pub enum FormatTime { + hhmm12 + hhmm24 + hhmmss12 + hhmmss24 + hhmmss24_milli + hhmmss24_micro + no_time +} + +// FormatDelimiter contains different date formats. +pub enum FormatDate { + ddmmyy + ddmmyyyy + mmddyy + mmddyyyy + mmmd + mmmdd + mmmddyy + mmmddyyyy + no_date + yyyymmdd + yymmdd +} + +// FormatDelimiter contains different time/date delimiters. +pub enum FormatDelimiter { + dot + hyphen + slash + space + no_delimiter +} + +// smonth returns month name. +pub fn (t Time) smonth() string { + if t.month <= 0 || t.month > 12 { + return '---' + } + i := t.month - 1 + return time.months_string[i * 3..(i + 1) * 3] +} + +// unix_time returns Unix time. +[inline] +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() 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) * 1_000_000 + t.microsecond + d.microseconds() + unix := microseconds / 1_000_000 + micro := microseconds % 1_000_000 + return unix2(unix, int(micro)) +} + +// add_seconds returns a new time struct with an added number of seconds. +pub fn (t Time) add_seconds(seconds int) Time { + return t.add(seconds * time.second) +} + +// add_days returns a new time struct with an added number of days. +pub fn (t Time) add_days(days int) Time { + return t.add(days * 24 * time.hour) +} + +// since returns a number of seconds elapsed since a given time. +fn since(t Time) int { + // TODO Use time.Duration instead of seconds + return 0 +} + +// relative returns a string representation of the difference between t +// and the current time. +pub fn (t Time) relative() string { + znow := now() + secs := znow.unix - t.unix + if secs <= 30 { + // right now or in the future + // TODO handle time in the future + return 'now' + } + if secs < 60 { + return '1m' + } + if secs < 3600 { + m := secs / 60 + if m == 1 { + return '1 minute ago' + } + return '$m minutes ago' + } + if secs < 3600 * 24 { + h := secs / 3600 + if h == 1 { + return '1 hour ago' + } + return '$h hours ago' + } + if secs < 3600 * 24 * 5 { + d := secs / 3600 / 24 + if d == 1 { + return '1 day ago' + } + return '$d days ago' + } + if secs > 3600 * 24 * 10000 { + return '' + } + return t.md() +} + +// relative_short returns a string saying how long ago a time occured as follows: +// 0-30 seconds: `"now"`; 30-60 seconds: `"1m"`; anything else is rounded to the +// nearest minute, hour or day; anything higher than 10000 days (about 27 years) +// years returns an empty string. +// Some Examples: +// `0s -> 'now'`; +// `20s -> 'now'`; +// `47s -> '1m'`; +// `456s -> '7m'`; +// `1234s -> '20m'`; +// `16834s -> '4h'`; +// `1687440s -> '33d'`; +// `15842354871s -> ''` +pub fn (t Time) relative_short() string { + znow := now() + secs := znow.unix - t.unix + if secs <= 30 { + // right now or in the future + // TODO handle time in the future + return 'now' + } + if secs < 60 { + return '1m' + } + if secs < 3600 { + return '${secs / 60}m' + } + if secs < 3600 * 24 { + return '${secs / 3600}h' + } + if secs < 3600 * 24 * 5 { + return '${secs / 3600 / 24}d' + } + if secs > 3600 * 24 * 10000 { + return '' + } + return t.md() +} + +// day_of_week returns the current day of a given year, month, and day, +// as an integer. +pub fn day_of_week(y int, m int, d int) int { + // Sakomotho's algorithm is explained here: + // https://stackoverflow.com/a/6385934 + t := [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4] + mut sy := y + if m < 3 { + sy = sy - 1 + } + return (sy + sy / 4 - sy / 100 + sy / 400 + t[m - 1] + d - 1) % 7 + 1 +} + +// day_of_week returns the current day as an integer. +pub fn (t Time) day_of_week() int { + return day_of_week(t.year, t.month, t.day) +} + +// weekday_str returns the current day as a string. +pub fn (t Time) weekday_str() string { + i := t.day_of_week() - 1 + return time.days_string[i * 3..(i + 1) * 3] +} + +// weekday_str returns the current day as a string. +pub fn (t Time) long_weekday_str() string { + i := t.day_of_week() - 1 + return time.long_days[i] +} + +// is_leap_year checks if a given a year is a leap year. +pub fn is_leap_year(year int) bool { + return (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0) +} + +// days_in_month returns a number of days in a given month. +pub fn days_in_month(month int, year int) ?int { + if month > 12 || month < 1 { + return error('Invalid month: $month') + } + extra := if month == 2 && is_leap_year(year) { 1 } else { 0 } + res := time.month_days[month - 1] + extra + return res +} + +// 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} }' +} + +// A lot of these are taken from the Go library. +pub type Duration = i64 + +pub const ( + nanosecond = Duration(1) + microsecond = Duration(1000 * nanosecond) + millisecond = Duration(1000 * microsecond) + second = Duration(1000 * millisecond) + minute = Duration(60 * second) + hour = Duration(60 * minute) +) + +// nanoseconds returns the duration as an integer number of nanoseconds. +pub fn (d Duration) nanoseconds() i64 { + return i64(d) +} + +// microseconds returns the duration as an integer number of microseconds. +pub fn (d Duration) microseconds() i64 { + return i64(d) / 1000 +} + +// milliseconds returns the duration as an integer number of milliseconds. +pub fn (d Duration) milliseconds() i64 { + return i64(d) / 1000000 +} + +// The following functions return floating point numbers because it's common to +// consider all of them in sub-one intervals +// seconds returns the duration as a floating point number of seconds. +pub fn (d Duration) seconds() f64 { + sec := d / time.second + nsec := d % time.second + return f64(sec) + f64(nsec) / 1e9 +} + +// minutes returns the duration as a floating point number of minutes. +pub fn (d Duration) minutes() f64 { + min := d / time.minute + nsec := d % time.minute + return f64(min) + f64(nsec) / (60 * 1e9) +} + +// hours returns the duration as a floating point number of hours. +pub fn (d Duration) hours() f64 { + hr := d / time.hour + nsec := d % time.hour + return f64(hr) + f64(nsec) / (60 * 60 * 1e9) +} + +// offset returns time zone UTC offset in seconds. +pub fn offset() int { + t := now() + local := t.local() + return int(local.unix - t.unix) +} diff --git a/v_windows/v/vlib/time/time_addition_test.v b/v_windows/v/vlib/time/time_addition_test.v new file mode 100644 index 0000000..8fde874 --- /dev/null +++ b/v_windows/v/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/v_windows/v/vlib/time/time_darwin.c.v b/v_windows/v/vlib/time/time_darwin.c.v new file mode 100644 index 0000000..d16c66b --- /dev/null +++ b/v_windows/v/vlib/time/time_darwin.c.v @@ -0,0 +1,84 @@ +module time + +#include <mach/mach_time.h> + +const ( + // start_time is needed on Darwin and Windows because of potential overflows + start_time = C.mach_absolute_time() + time_base = init_time_base() +) + +[typedef] +struct C.mach_timebase_info_data_t { + numer u32 + denom u32 +} + +fn C.mach_absolute_time() u64 + +fn C.mach_timebase_info(&C.mach_timebase_info_data_t) + +fn C.clock_gettime_nsec_np(int) u64 + +struct InternalTimeBase { + numer u32 = 1 + denom u32 = 1 +} + +pub struct C.timeval { + tv_sec u64 + tv_usec u64 +} + +fn init_time_base() C.mach_timebase_info_data_t { + tb := C.mach_timebase_info_data_t{} + C.mach_timebase_info(&tb) + return C.mach_timebase_info_data_t{ + numer: tb.numer + denom: tb.denom + } +} + +fn sys_mono_now_darwin() u64 { + tm := C.mach_absolute_time() + if time.time_base.denom == 0 { + C.mach_timebase_info(&time.time_base) + } + return (tm - time.start_time) * time.time_base.numer / time.time_base.denom +} + +// NB: vpc_now_darwin is used by `v -profile` . +// It should NOT call *any other v function*, just C functions and casts. +[inline] +fn vpc_now_darwin() u64 { + tm := C.mach_absolute_time() + if time.time_base.denom == 0 { + C.mach_timebase_info(&time.time_base) + } + return (tm - time.start_time) * time.time_base.numer / time.time_base.denom +} + +// 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 converts to local time +fn darwin_now() Time { + // get the high precision time as UTC clock + tv := C.timeval{} + C.gettimeofday(&tv, 0) + loc_tm := C.tm{} + asec := voidptr(&tv.tv_sec) + C.localtime_r(asec, &loc_tm) + 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 +fn darwin_utc() Time { + // get the high precision time as UTC clock + tv := C.timeval{} + C.gettimeofday(&tv, 0) + return unix2(i64(tv.tv_sec), int(tv.tv_usec)) +} diff --git a/v_windows/v/vlib/time/time_format_test.v b/v_windows/v/vlib/time/time_format_test.v new file mode 100644 index 0000000..6042cf9 --- /dev/null +++ b/v_windows/v/vlib/time/time_format_test.v @@ -0,0 +1,90 @@ +import time + +const ( + time_to_test = time.Time{ + year: 1980 + month: 7 + day: 11 + hour: 21 + minute: 23 + second: 42 + unix: 332198622 + } +) + +fn test_now_format() { + t := time.now() + u := t.unix + assert t.format() == time.unix(int(u)).format() +} + +fn test_format() { + assert '11.07.1980 21:23' == time_to_test.get_fmt_str(.dot, .hhmm24, .ddmmyyyy) +} + +fn test_hhmm() { + assert '21:23' == time_to_test.hhmm() +} + +fn test_hhmm12() { + assert '9:23 p.m.' == time_to_test.hhmm12() +} + +fn test_hhmmss() { + assert '21:23:42' == time_to_test.hhmmss() +} + +fn test_ymmdd() { + assert '1980-07-11' == time_to_test.ymmdd() +} + +fn test_ddmmy() { + assert '11.07.1980' == time_to_test.ddmmy() +} + +fn test_md() { + assert 'Jul 11' == time_to_test.md() +} + +fn test_get_fmt_time_str() { + assert '21:23:42' == time_to_test.get_fmt_time_str(.hhmmss24) + assert '21:23' == time_to_test.get_fmt_time_str(.hhmm24) + assert '9:23:42 p.m.' == time_to_test.get_fmt_time_str(.hhmmss12) + assert '9:23 p.m.' == time_to_test.get_fmt_time_str(.hhmm12) +} + +fn test_get_fmt_date_str() { + assert '11.07.1980' == time_to_test.get_fmt_date_str(.dot, .ddmmyyyy) + assert '11/07/1980' == time_to_test.get_fmt_date_str(.slash, .ddmmyyyy) + assert '11-07-1980' == time_to_test.get_fmt_date_str(.hyphen, .ddmmyyyy) + assert '11 07 1980' == time_to_test.get_fmt_date_str(.space, .ddmmyyyy) + assert '07.11.1980' == time_to_test.get_fmt_date_str(.dot, .mmddyyyy) + assert '07/11/1980' == time_to_test.get_fmt_date_str(.slash, .mmddyyyy) + assert '07-11-1980' == time_to_test.get_fmt_date_str(.hyphen, .mmddyyyy) + assert '07 11 1980' == time_to_test.get_fmt_date_str(.space, .mmddyyyy) + assert '11.07.80' == time_to_test.get_fmt_date_str(.dot, .ddmmyy) + assert '11/07/80' == time_to_test.get_fmt_date_str(.slash, .ddmmyy) + assert '11-07-80' == time_to_test.get_fmt_date_str(.hyphen, .ddmmyy) + assert '11 07 80' == time_to_test.get_fmt_date_str(.space, .ddmmyy) + assert '07.11.80' == time_to_test.get_fmt_date_str(.dot, .mmddyy) + assert '07/11/80' == time_to_test.get_fmt_date_str(.slash, .mmddyy) + assert '07-11-80' == time_to_test.get_fmt_date_str(.hyphen, .mmddyy) + assert '07 11 80' == time_to_test.get_fmt_date_str(.space, .mmddyy) + assert 'Jul 11' == time_to_test.get_fmt_date_str(.space, .mmmd) + assert 'Jul 11' == time_to_test.get_fmt_date_str(.space, .mmmdd) + assert 'Jul 11 80' == time_to_test.get_fmt_date_str(.space, .mmmddyy) + assert 'Jul 11 1980' == time_to_test.get_fmt_date_str(.space, .mmmddyyyy) + assert '1980-07-11' == time_to_test.get_fmt_date_str(.hyphen, .yyyymmdd) + assert '80.07.11' == time_to_test.get_fmt_date_str(.dot, .yymmdd) +} + +fn test_get_fmt_str() { + // Since get_fmt_time_str and get_fmt_date_str do have comprehensive + // tests I don't want to exaggerate here with all possible + // combinations. + assert '11.07.1980 21:23:42' == time_to_test.get_fmt_str(.dot, .hhmmss24, .ddmmyyyy) +} + +fn test_utc_string() { + assert 'Fri, 11 Jul 1980 21:23:42 UTC' == time_to_test.utc_string() +} diff --git a/v_windows/v/vlib/time/time_js.js.v b/v_windows/v/vlib/time/time_js.js.v new file mode 100644 index 0000000..55abac3 --- /dev/null +++ b/v_windows/v/vlib/time/time_js.js.v @@ -0,0 +1,31 @@ +module time + +#var $timeOff = 0; +#var $seen = 0 +#function $sys_mono_new_Date() { +#var t = Date.now() +#if (t < seen) +#timeOff += (seen - t) +# +#seen = t +#return t + timeOff +#} + +pub fn sys_mono_now() u64 { + $if js_browser { + mut res := u64(0) + #res = new u64(window.performance.now() * 1000000) + + return res + } $else $if js_node { + mut res := u64(0) + #res.val = Number($process.hrtime.bigint()) + + return res + } $else { + mut res := u64(0) + #res = new u64($sys_mono_new_Date() * 1000000) + + return res + } +} diff --git a/v_windows/v/vlib/time/time_linux.c.v b/v_windows/v/vlib/time/time_linux.c.v new file mode 100644 index 0000000..b0ab7ad --- /dev/null +++ b/v_windows/v/vlib/time/time_linux.c.v @@ -0,0 +1,26 @@ +module time + +// sys_mono_now_darwin - dummy fn to compile on all platforms/compilers +fn sys_mono_now_darwin() u64 { + return 0 +} + +// darwin_now - dummy fn to compile on all platforms/compilers +pub fn darwin_now() Time { + return Time{} +} + +// solaris_now - dummy fn to compile on all platforms/compilers +pub fn solaris_now() Time { + return Time{} +} + +// darwin_utc - dummy fn to compile on all platforms/compilers +pub fn darwin_utc() Time { + return Time{} +} + +// solaris_utc - dummy fn to compile on all platforms/compilers +pub fn solaris_utc() Time { + return Time{} +} diff --git a/v_windows/v/vlib/time/time_nix.c.v b/v_windows/v/vlib/time/time_nix.c.v new file mode 100644 index 0000000..f4ad2c8 --- /dev/null +++ b/v_windows/v/vlib/time/time_nix.c.v @@ -0,0 +1,156 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module time + +#include <time.h> +#include <errno.h> + +struct C.tm { + tm_sec int + tm_min int + tm_hour int + tm_mday int + tm_mon int + tm_year int + tm_wday int + tm_yday int + tm_isdst int +} + +fn C.timegm(&C.tm) C.time_t + +// fn C.gmtime_r(&tm, &gbuf) +fn C.localtime_r(t &C.time_t, tm &C.tm) + +fn make_unix_time(t C.tm) i64 { + return i64(C.timegm(&t)) +} + +// local returns t with the location set to local time. +pub fn (t Time) local() Time { + loc_tm := C.tm{} + C.localtime_r(voidptr(&t.unix), &loc_tm) + return convert_ctime(loc_tm, t.microsecond) +} + +// in most systems, these are __quad_t, which is an i64 +struct C.timespec { +mut: + tv_sec i64 + tv_nsec i64 +} + +// the first arg is defined in include/bits/types.h as `__S32_TYPE`, which is `int` +fn C.clock_gettime(int, &C.timespec) + +fn C.nanosleep(req &C.timespec, rem &C.timespec) int + +// sys_mono_now returns a *monotonically increasing time*, NOT a time adjusted for daylight savings, location etc. +pub fn sys_mono_now() u64 { + $if macos { + return sys_mono_now_darwin() + } $else { + ts := C.timespec{} + C.clock_gettime(C.CLOCK_MONOTONIC, &ts) + return u64(ts.tv_sec) * 1000000000 + u64(ts.tv_nsec) + } +} + +// NB: vpc_now is used by `v -profile` . +// It should NOT call *any other v function*, just C functions and casts. +[inline] +fn vpc_now() u64 { + ts := C.timespec{} + C.clock_gettime(C.CLOCK_MONOTONIC, &ts) + return u64(ts.tv_sec) * 1000000000 + u64(ts.tv_nsec) +} + +// The linux_* functions are placed here, since they're used on Android as well +// TODO: should `$if linux {}` be parsed on Android as well? (Android runs under the Linux kernel) +// 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 +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(voidptr(&ts.tv_sec), &loc_tm) + return convert_ctime(loc_tm, int(ts.tv_nsec / 1000)) +} + +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(i64(ts.tv_sec), int(ts.tv_nsec / 1000)) +} + +// 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 +} + +// return absolute timespec for now()+d +pub fn (d Duration) timespec() C.timespec { + mut ts := C.timespec{} + C.clock_gettime(C.CLOCK_REALTIME, &ts) + d_sec := d / second + d_nsec := d % second + ts.tv_sec += d_sec + ts.tv_nsec += d_nsec + if ts.tv_nsec > i64(second) { + ts.tv_nsec -= i64(second) + ts.tv_sec++ + } + return ts +} + +// return timespec of 1970/1/1 +pub fn zero_timespec() C.timespec { + ts := C.timespec{ + tv_sec: 0 + tv_nsec: 0 + } + return ts +} + +// sleep makes the calling thread sleep for a given duration (in nanoseconds). +pub fn sleep(duration Duration) { + mut req := C.timespec{duration / second, duration % second} + rem := C.timespec{} + for C.nanosleep(&req, &rem) < 0 { + if C.errno == C.EINTR { + // Interrupted by a signal handler + req = rem + } else { + break + } + } +} + +// some *nix system functions (e.g. `C.poll()`, C.epoll_wait()) accept an `int` +// value as *timeout in milliseconds* with the special value `-1` meaning "infinite" +pub fn (d Duration) sys_milliseconds() int { + if d > C.INT32_MAX * millisecond { // treat 2147483647000001 .. C.INT64_MAX as "infinite" + return -1 + } else if d <= 0 { + return 0 // treat negative timeouts as 0 - consistent with Unix behaviour + } else { + return int(d / millisecond) + } +} diff --git a/v_windows/v/vlib/time/time_solaris.c.v b/v_windows/v/vlib/time/time_solaris.c.v new file mode 100644 index 0000000..b87f1c8 --- /dev/null +++ b/v_windows/v/vlib/time/time_solaris.c.v @@ -0,0 +1,32 @@ +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 +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(voidptr(&ts.tv_sec), &loc_tm) + return convert_ctime(loc_tm, int(ts.tv_nsec / 1000)) +} + +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(i64(ts.tv_sec), int(ts.tv_nsec / 1000)) +} + +// dummy to compile with all compilers +pub fn darwin_now() Time { + return Time{} +} + +// dummy to compile with all compilers +pub fn darwin_utc() Time { + return Time{} +} diff --git a/v_windows/v/vlib/time/time_test.v b/v_windows/v/vlib/time/time_test.v new file mode 100644 index 0000000..dbd3c48 --- /dev/null +++ b/v_windows/v/vlib/time/time_test.v @@ -0,0 +1,247 @@ +import time +import math + +const ( + time_to_test = time.Time{ + year: 1980 + month: 7 + day: 11 + hour: 21 + minute: 23 + second: 42 + microsecond: 123456 + unix: 332198622 + } +) + +fn test_is_leap_year() { + // 1996 % 4 = 0 and 1996 % 100 > 0 + assert time.is_leap_year(1996) == true + // 2000 % 4 = 0 and 2000 % 400 = 0 + assert time.is_leap_year(2000) == true + // 1996 % 4 > 0 + assert time.is_leap_year(1997) == false + // 2000 % 4 = 0 and 2000 % 100 = 0 + assert time.is_leap_year(2100) == false +} + +fn check_days_in_month(month int, year int, expected int) bool { + res := time.days_in_month(month, year) or { return false } + return res == expected +} + +fn test_days_in_month() { + days_in_month := [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + for i, days in days_in_month { + month := i + 1 + assert check_days_in_month(month, 2001, days) + } +} + +fn test_unix() { + t := time.unix(1564366499) + assert t.year == 2019 + assert t.month == 7 + assert t.day == 29 + assert t.hour == 2 + assert t.minute == 14 + assert t.second == 59 + t2 := time.unix(1078058096) + assert t2.year == 2004 + assert t2.month == 2 + assert t2.day == 29 + assert t2.hour == 12 + assert t2.minute == 34 + assert t2.second == 56 + t3 := time.unix(1070236799) + assert t3.year == 2003 + assert t3.month == 11 + assert t3.day == 30 + assert t3.hour == 23 + assert t3.minute == 59 + assert t3.second == 59 + t4 := time.unix(1577783439) + assert t4.year == 2019 + assert t4.month == 12 + assert t4.day == 31 + assert t4.hour == 9 + assert t4.minute == 10 + assert t4.second == 39 + t5 := time.unix(-1824922433) + assert t5.year == 1912 + assert t5.month == 3 + assert t5.day == 4 + assert t5.hour == 5 + assert t5.minute == 6 + assert t5.second == 7 + t6 := time.unix(1577858969) + assert t6.year == 2020 + assert t6.month == 1 + assert t6.day == 1 + assert t6.hour == 6 + assert t6.minute == 9 + assert t6.second == 29 +} + +fn test_format_ss() { + assert '11.07.1980 21:23:42' == time_to_test.get_fmt_str(.dot, .hhmmss24, .ddmmyyyy) +} + +fn test_format_ss_milli() { + assert '11.07.1980 21:23:42.123' == time_to_test.get_fmt_str(.dot, .hhmmss24_milli, + .ddmmyyyy) + assert '1980-07-11 21:23:42.123' == time_to_test.format_ss_milli() +} + +fn test_format_ss_micro() { + assert '11.07.1980 21:23:42.123456' == time_to_test.get_fmt_str(.dot, .hhmmss24_micro, + .ddmmyyyy) + assert '1980-07-11 21:23:42.123456' == time_to_test.format_ss_micro() +} + +fn test_smonth() { + month_names := ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', + 'Dec', + ] + for i, name in month_names { + month_num := i + 1 + t := time.Time{ + year: 1980 + month: month_num + day: 1 + hour: 0 + minute: 0 + second: 0 + unix: 0 + } + assert t.smonth() == name + } +} + +fn test_day_of_week() { + for i in 0 .. 7 { + day_of_week := i + 1 + // 2 Dec 2019 is Monday + t := time.Time{ + year: 2019 + month: 12 + day: 2 + i + hour: 0 + minute: 0 + second: 0 + unix: 0 + } + assert day_of_week == t.day_of_week() + } +} + +fn test_weekday_str() { + day_names := ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] + for i, name in day_names { + // 2 Dec 2019 is Monday + t := time.Time{ + year: 2019 + month: 12 + day: 2 + i + hour: 0 + minute: 0 + second: 0 + unix: 0 + } + assert t.weekday_str() == name + } +} + +fn test_add() { + d_seconds := 3 + d_microseconds := 13 + duration := time.Duration(d_seconds * time.second + d_microseconds * time.microsecond) + t1 := time_to_test + 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 + 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 - 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 * num_of_days +} + +fn test_str() { + assert '1980-07-11 21:23:42' == time_to_test.str() +} + +// not optimal test but will find obvious bugs +fn test_now() { + now := time.now() + // 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 +} + +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 +} + +fn test_unix_time() { + t1 := time.utc() + time.sleep(50 * time.millisecond) + t2 := time.utc() + ut1 := t1.unix_time() + ut2 := t2.unix_time() + assert ut2 - ut1 < 2 + // + utm1 := t1.unix_time_milli() + utm2 := t2.unix_time_milli() + assert (utm1 - ut1 * 1000) < 1000 + assert (utm2 - ut2 * 1000) < 1000 + // + // println('utm1: $utm1 | utm2: $utm2') + assert utm2 - utm1 > 2 + assert utm2 - utm1 < 999 +} + +fn test_offset() { + u := time.utc() + n := time.now() + // + mut diff_seconds := 0 + if u.day != n.day { + if u.day > n.day { + diff_seconds = int(math.abs(((u.hour * 60 + u.minute) - (n.hour * 60 + n.minute)) * 60)) - 86400 + } else { + diff_seconds = 86400 - int(math.abs(((u.hour * 60 + u.minute) - (n.hour * 60 + n.minute)) * 60)) + } + if math.abs(u.day - n.day) > 1 { // different month + diff_seconds = diff_seconds * -1 + } + } else { // same day + diff_seconds = ((n.hour * 60 + n.minute) - (u.hour * 60 + u.minute)) * 60 + } + + assert diff_seconds == time.offset() +} diff --git a/v_windows/v/vlib/time/time_windows.c.v b/v_windows/v/vlib/time/time_windows.c.v new file mode 100644 index 0000000..a810cb9 --- /dev/null +++ b/v_windows/v/vlib/time/time_windows.c.v @@ -0,0 +1,231 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module time + +#include <time.h> +// #include <sysinfoapi.h> + +struct C.tm { + tm_year int + tm_mon int + tm_mday int + tm_hour int + tm_min int + tm_sec int +} + +struct C._FILETIME { + dwLowDateTime u32 + dwHighDateTime u32 +} + +struct SystemTime { + year u16 + month u16 + day_of_week u16 + day u16 + hour u16 + minute u16 + second u16 + millisecond u16 +} + +fn C.GetSystemTimeAsFileTime(lpSystemTimeAsFileTime &C._FILETIME) + +fn C.FileTimeToSystemTime(lpFileTime &C._FILETIME, lpSystemTime &SystemTime) + +fn C.SystemTimeToTzSpecificLocalTime(lpTimeZoneInformation &C.TIME_ZONE_INFORMATION, lpUniversalTime &SystemTime, lpLocalTime &SystemTime) + +fn C.localtime_s(t &C.time_t, tm &C.tm) + +const ( + // start_time is needed on Darwin and Windows because of potential overflows + start_time = init_win_time_start() + freq_time = init_win_time_freq() + start_local_time = local_as_unix_time() +) + +// in most systems, these are __quad_t, which is an i64 +struct C.timespec { + tv_sec i64 + tv_nsec i64 +} + +fn C.QueryPerformanceCounter(&u64) C.BOOL + +fn C.QueryPerformanceFrequency(&u64) C.BOOL + +fn make_unix_time(t C.tm) i64 { + return portable_timegm(&t) +} + +fn init_win_time_freq() u64 { + f := u64(0) + C.QueryPerformanceFrequency(&f) + return f +} + +fn init_win_time_start() u64 { + s := u64(0) + C.QueryPerformanceCounter(&s) + return s +} + +// sys_mono_now returns a *monotonically increasing time*, NOT a time adjusted for daylight savings, location etc. +pub fn sys_mono_now() u64 { + tm := u64(0) + C.QueryPerformanceCounter(&tm) // XP or later never fail + return (tm - time.start_time) * 1000000000 / time.freq_time +} + +// NB: vpc_now is used by `v -profile` . +// It should NOT call *any other v function*, just C functions and casts. +[inline] +fn vpc_now() u64 { + tm := u64(0) + C.QueryPerformanceCounter(&tm) + return tm +} + +// local_as_unix_time returns the current local time as unix time +fn local_as_unix_time() i64 { + t := C.time(0) + tm := C.localtime(&t) + return make_unix_time(tm) +} + +// local - return the time `t`, converted to the currently active local timezone +pub fn (t Time) local() 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: st_local.millisecond * 1000 + unix: st_local.unix_time() + } + return t_local +} + +// 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 +fn win_now() Time { + ft_utc := C._FILETIME{} + C.GetSystemTimeAsFileTime(&ft_utc) + st_utc := SystemTime{} + C.FileTimeToSystemTime(&ft_utc, &st_utc) + st_local := SystemTime{} + C.SystemTimeToTzSpecificLocalTime(voidptr(0), &st_utc, &st_local) + t := 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 + microsecond: st_local.millisecond * 1000 + unix: st_local.unix_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 +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: st_utc.unix_time() + } + return t +} + +// unix_time returns Unix time. +pub fn (st SystemTime) unix_time() i64 { + tt := C.tm{ + tm_sec: st.second + tm_min: st.minute + tm_hour: st.hour + tm_mday: st.day + tm_mon: st.month - 1 + tm_year: st.year - 1900 + } + return make_unix_time(tt) +} + +// dummy to compile with all compilers +pub fn darwin_now() Time { + return Time{} +} + +// dummy to compile with all compilers +pub fn linux_now() Time { + return Time{} +} + +// dummy to compile with all compilers +pub fn solaris_now() Time { + return Time{} +} + +// dummy to compile with all compilers +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 + tv_usec u64 +} + +// sleep makes the calling thread sleep for a given duration (in nanoseconds). +pub fn sleep(duration Duration) { + C.Sleep(int(duration / millisecond)) +} + +// some Windows system functions (e.g. `C.WaitForSingleObject()`) accept an `u32` +// value as *timeout in milliseconds* with the special value `u32(-1)` meaning "infinite" +pub fn (d Duration) sys_milliseconds() u32 { + if d >= u32(-1) * millisecond { // treat 4294967295000000 .. C.INT64_MAX as "infinite" + return u32(-1) + } else if d <= 0 { + return 0 // treat negative timeouts as 0 - consistent with Unix behaviour + } else { + return u32(d / millisecond) + } +} diff --git a/v_windows/v/vlib/time/unix.v b/v_windows/v/vlib/time/unix.v new file mode 100644 index 0000000..30cae42 --- /dev/null +++ b/v_windows/v/vlib/time/unix.v @@ -0,0 +1,124 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module time + +// unix returns a time struct from Unix time. +pub fn unix(abs i64) Time { + // Split into day and time + mut day_offset := abs / seconds_per_day + if abs % seconds_per_day < 0 { + // Compensate for round towards zero on integers as we want floored instead + day_offset-- + } + year, month, day := calculate_date_from_offset(day_offset) + hr, min, sec := calculate_time_from_offset(abs % seconds_per_day) + return Time{ + year: year + month: month + day: day + hour: hr + minute: min + second: sec + unix: abs + } +} + +// unix2 returns a time struct from Unix time and microsecond value +pub fn unix2(abs i64, microsecond int) Time { + // Split into day and time + mut day_offset := abs / seconds_per_day + if abs % seconds_per_day < 0 { + // Compensate for round towards zero on integers as we want floored instead + day_offset-- + } + year, month, day := calculate_date_from_offset(day_offset) + hr, min, sec := calculate_time_from_offset(abs % seconds_per_day) + return Time{ + year: year + month: month + day: day + hour: hr + minute: min + second: sec + microsecond: microsecond + unix: abs + } +} + +fn calculate_date_from_offset(day_offset_ i64) (int, int, int) { + mut day_offset := day_offset_ + // Move offset to year 2001 as it's the start of a new 400-year cycle + // Code below this rely on the fact that the day_offset is lined up with the 400-year cycle + // 1970-2000 (inclusive) has 31 years (8 of which are leap years) + mut year := 2001 + day_offset -= 31 * 365 + 8 + // Account for 400 year cycle + year += int(day_offset / days_per_400_years) * 400 + day_offset %= days_per_400_years + // Account for 100 year cycle + if day_offset == days_per_100_years * 4 { + year += 300 + day_offset -= days_per_100_years * 3 + } else { + year += int(day_offset / days_per_100_years) * 100 + day_offset %= days_per_100_years + } + // Account for 4 year cycle + if day_offset == days_per_4_years * 25 { + year += 96 + day_offset -= days_per_4_years * 24 + } else { + year += int(day_offset / days_per_4_years) * 4 + day_offset %= days_per_4_years + } + // Account for every year + if day_offset == 365 * 4 { + year += 3 + day_offset -= 365 * 3 + } else { + year += int(day_offset / 365) + day_offset %= 365 + } + if day_offset < 0 { + year-- + if is_leap_year(year) { + day_offset += 366 + } else { + day_offset += 365 + } + } + if is_leap_year(year) { + if day_offset > 31 + 29 - 1 { + // After leap day; pretend it wasn't there. + day_offset-- + } else if day_offset == 31 + 29 - 1 { + // Leap day. + return year, 2, 29 + } + } + mut estimated_month := day_offset / 31 + for day_offset >= days_before[estimated_month + 1] { + estimated_month++ + } + for day_offset < days_before[estimated_month] { + if estimated_month == 0 { + break + } + estimated_month-- + } + day_offset -= days_before[estimated_month] + return year, int(estimated_month + 1), int(day_offset + 1) +} + +fn calculate_time_from_offset(second_offset_ i64) (int, int, int) { + mut second_offset := second_offset_ + if second_offset < 0 { + second_offset += seconds_per_day + } + hour_ := second_offset / seconds_per_hour + second_offset %= seconds_per_hour + min := second_offset / seconds_per_minute + second_offset %= seconds_per_minute + return int(hour_), int(min), int(second_offset) +} |