aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/vlib/time
diff options
context:
space:
mode:
authorIndrajith K L2022-12-03 17:00:20 +0530
committerIndrajith K L2022-12-03 17:00:20 +0530
commitf5c4671bfbad96bf346bd7e9a21fc4317b4959df (patch)
tree2764fc62da58f2ba8da7ed341643fc359873142f /v_windows/v/vlib/time
downloadcli-tools-windows-master.tar.gz
cli-tools-windows-master.tar.bz2
cli-tools-windows-master.zip
Adds most of the toolsHEADmaster
Diffstat (limited to 'v_windows/v/vlib/time')
-rw-r--r--v_windows/v/vlib/time/Y2K38_test.v13
-rw-r--r--v_windows/v/vlib/time/chrono.c.v18
-rw-r--r--v_windows/v/vlib/time/chrono.v14
-rw-r--r--v_windows/v/vlib/time/format.v174
-rw-r--r--v_windows/v/vlib/time/misc/misc.v13
-rw-r--r--v_windows/v/vlib/time/misc/misc_test.v17
-rw-r--r--v_windows/v/vlib/time/operator.v21
-rw-r--r--v_windows/v/vlib/time/operator_test.v391
-rw-r--r--v_windows/v/vlib/time/parse.c.v140
-rw-r--r--v_windows/v/vlib/time/parse.js.v24
-rw-r--r--v_windows/v/vlib/time/parse_test.v138
-rw-r--r--v_windows/v/vlib/time/private_test.v13
-rw-r--r--v_windows/v/vlib/time/stopwatch.v72
-rw-r--r--v_windows/v/vlib/time/stopwatch_test.v36
-rw-r--r--v_windows/v/vlib/time/time.c.v125
-rw-r--r--v_windows/v/vlib/time/time.js.v38
-rw-r--r--v_windows/v/vlib/time/time.v315
-rw-r--r--v_windows/v/vlib/time/time_addition_test.v33
-rw-r--r--v_windows/v/vlib/time/time_darwin.c.v84
-rw-r--r--v_windows/v/vlib/time/time_format_test.v90
-rw-r--r--v_windows/v/vlib/time/time_js.js.v31
-rw-r--r--v_windows/v/vlib/time/time_linux.c.v26
-rw-r--r--v_windows/v/vlib/time/time_nix.c.v156
-rw-r--r--v_windows/v/vlib/time/time_solaris.c.v32
-rw-r--r--v_windows/v/vlib/time/time_test.v247
-rw-r--r--v_windows/v/vlib/time/time_windows.c.v231
-rw-r--r--v_windows/v/vlib/time/unix.v124
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_,
+ &microsecond_, &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)
+}