Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,88 @@
|
||||
## Description
|
||||
|
||||
V's `time` module, provides utilities for working with time and dates:
|
||||
|
||||
- parsing of time values expressed in one of the commonly used standard time/date formats
|
||||
- formatting of time values
|
||||
- arithmetic over times/durations
|
||||
- converting between local time and UTC (timezone support)
|
||||
- stop watches for accurately measuring time durations
|
||||
- sleeping for a period of time
|
||||
|
||||
## Examples
|
||||
|
||||
You can see the current time. [See](https://play.vlang.io/?query=c121a6dda7):
|
||||
|
||||
```v
|
||||
import time
|
||||
|
||||
println(time.now())
|
||||
```
|
||||
|
||||
`time.Time` values can be compared, [see](https://play.vlang.io/?query=133d1a0ce5):
|
||||
|
||||
```v
|
||||
import time
|
||||
|
||||
const time_to_test = time.Time{
|
||||
year: 1980
|
||||
month: 7
|
||||
day: 11
|
||||
hour: 21
|
||||
minute: 23
|
||||
second: 42
|
||||
nanosecond: 123456789
|
||||
}
|
||||
|
||||
println(time_to_test.format())
|
||||
|
||||
assert '1980-07-11 21:23' == time_to_test.format()
|
||||
assert '1980-07-11 21:23:42' == time_to_test.format_ss()
|
||||
assert '1980-07-11 21:23:42.123' == time_to_test.format_ss_milli()
|
||||
assert '1980-07-11 21:23:42.123456' == time_to_test.format_ss_micro()
|
||||
assert '1980-07-11 21:23:42.123456789' == time_to_test.format_ss_nano()
|
||||
```
|
||||
|
||||
You can also parse strings to produce time.Time values,
|
||||
[see](https://play.vlang.io/p/b02ca6027f):
|
||||
|
||||
```v
|
||||
import time
|
||||
|
||||
s := '2018-01-27 12:48:34'
|
||||
t := time.parse(s) or { panic('failing format: ${s} | err: ${err}') }
|
||||
println(t)
|
||||
println(t.unix())
|
||||
```
|
||||
|
||||
V's time module also has these parse methods:
|
||||
|
||||
```v ignore
|
||||
fn parse(s string) !Time
|
||||
fn parse_iso8601(s string) !Time
|
||||
fn parse_rfc2822(s string) !Time
|
||||
fn parse_rfc3339(s string) !Time
|
||||
```
|
||||
|
||||
`time.new(...)` validates the provided fields before calculating the Unix timestamp.
|
||||
Omitted `month` and `day` values default to `1`, and out-of-range values panic.
|
||||
Use `t.is_zero()` to check whether a `time.Time` is still its zero value before formatting or
|
||||
serializing it.
|
||||
|
||||
Another very useful feature of the `time` module is the stop watch,
|
||||
for when you want to measure short time periods, elapsed while you
|
||||
executed other tasks. [See](https://play.vlang.io/?query=f6c008bc34):
|
||||
|
||||
```v
|
||||
import time
|
||||
|
||||
fn do_something() {
|
||||
time.sleep(510 * time.millisecond)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
sw := time.new_stopwatch()
|
||||
do_something()
|
||||
println('Note: do_something() took: ${sw.elapsed().milliseconds()} ms')
|
||||
}
|
||||
```
|
||||
@@ -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'
|
||||
// Note: 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'
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import time
|
||||
|
||||
fn main() {
|
||||
t := time.now()
|
||||
println(t)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
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_unix_epoch(year, month + 1, t.tm_mday))
|
||||
return 60 * (60 * (24 * days_since_1970 + t.tm_hour) + t.tm_min) + t.tm_sec
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
module time
|
||||
|
||||
// days_from_unix_epoch - 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 function will return negative values for days before 1970-01-01.
|
||||
pub fn days_from_unix_epoch(year int, month int, day int) int {
|
||||
y := if month <= 2 { year - 1 } else { year }
|
||||
era := y / 400
|
||||
year_of_the_era := y - era * 400 // [0, 399]
|
||||
day_of_year := (153 * (month + (if month > 2 { -3 } else { 9 })) + 2) / 5 + day - 1 // [0, 365]
|
||||
day_of_the_era := year_of_the_era * 365 + year_of_the_era / 4 - year_of_the_era / 100 +
|
||||
day_of_year // [0, 146096]
|
||||
return era * 146097 + day_of_the_era - 719468
|
||||
}
|
||||
|
||||
// days_from_unix_epoch - 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 method will return negative values for days before 1970-01-01.
|
||||
@[inline]
|
||||
pub fn (t Time) days_from_unix_epoch() int {
|
||||
return days_from_unix_epoch(t.year, t.month, t.day)
|
||||
}
|
||||
|
||||
// date_from_days_after_unix_epoch - convert number of `days` after the unix epoch 1970-01-01, to a Time.
|
||||
// Only the year, month and day of the returned Time will be set, everything else will be 0.
|
||||
pub fn date_from_days_after_unix_epoch(days int) Time {
|
||||
year, month, day := calculate_date_from_day_offset(i64(days))
|
||||
return Time{
|
||||
year: year
|
||||
month: month
|
||||
day: day
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
module time
|
||||
|
||||
fn test_days_from_unix_epoch() {
|
||||
s := '2000-05-10 22:11:03'
|
||||
time_test := parse(s) or {
|
||||
eprintln('> failing format: ${s} | err: ${err}')
|
||||
assert false
|
||||
return
|
||||
}
|
||||
one_day_in_seconds := 86400
|
||||
|
||||
assert time_test.days_from_unix_epoch() == 11087
|
||||
assert time_test.days_from_unix_epoch() == int(time_test.unix / one_day_in_seconds)
|
||||
assert days_from_unix_epoch(1970, 1, 1) == 0
|
||||
assert days_from_unix_epoch(1970, 2, 1) == 31
|
||||
assert days_from_unix_epoch(1970, 3, 1) == 59
|
||||
assert days_from_unix_epoch(2022, 11, 10) == 19306
|
||||
}
|
||||
|
||||
fn test_date_from_days_after_unix_epoch() {
|
||||
assert date_from_days_after_unix_epoch(11087).year == 2000
|
||||
assert date_from_days_after_unix_epoch(11087).month == 5
|
||||
assert date_from_days_after_unix_epoch(11087).day == 10
|
||||
assert date_from_days_after_unix_epoch(1).year == 1970
|
||||
assert date_from_days_after_unix_epoch(1).month == 1
|
||||
assert date_from_days_after_unix_epoch(1).day == 2
|
||||
|
||||
assert date_from_days_after_unix_epoch(31).year == 1970
|
||||
assert date_from_days_after_unix_epoch(31).month == 2
|
||||
assert date_from_days_after_unix_epoch(31).day == 1
|
||||
|
||||
assert date_from_days_after_unix_epoch(59).year == 1970
|
||||
assert date_from_days_after_unix_epoch(59).month == 3
|
||||
assert date_from_days_after_unix_epoch(59).day == 1
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import time
|
||||
|
||||
fn test_custom_format() {
|
||||
date := time.now()
|
||||
assert date.custom_format('YYYY-MM-DD HH:mm') == date.format()
|
||||
assert date.custom_format('MMM') == date.smonth()
|
||||
|
||||
test_str := 'M MM Mo MMM MMMM\nD DD DDD DDDD\nd dd ddd dddd\nYY YYYY a A\nH HH h hh k kk i ii e\nm mm s ss Z ZZ ZZZ\nDo DDDo Q Qo QQ\nN NN w wo ww\nM/D/YYYY N-HH:mm:ss Qo?a'
|
||||
|
||||
println(date.custom_format(test_str))
|
||||
}
|
||||
|
||||
fn test_hours() {
|
||||
assert time.parse('2023-08-04 00:00:45')!.custom_format('ii A i a hh A h a') == '00 AM 0 am 12 AM 12 am'
|
||||
assert time.parse('2023-08-04 01:00:45')!.custom_format('ii A i a hh A h a') == '01 AM 1 am 01 AM 1 am'
|
||||
assert time.parse('2023-08-04 02:00:45')!.custom_format('ii A i a hh A h a') == '02 AM 2 am 02 AM 2 am'
|
||||
assert time.parse('2023-08-04 03:00:45')!.custom_format('ii A i a hh A h a') == '03 AM 3 am 03 AM 3 am'
|
||||
assert time.parse('2023-08-04 04:00:45')!.custom_format('ii A i a hh A h a') == '04 AM 4 am 04 AM 4 am'
|
||||
assert time.parse('2023-08-04 05:00:45')!.custom_format('ii A i a hh A h a') == '05 AM 5 am 05 AM 5 am'
|
||||
assert time.parse('2023-08-04 06:00:45')!.custom_format('ii A i a hh A h a') == '06 AM 6 am 06 AM 6 am'
|
||||
assert time.parse('2023-08-04 07:00:45')!.custom_format('ii A i a hh A h a') == '07 AM 7 am 07 AM 7 am'
|
||||
assert time.parse('2023-08-04 08:00:45')!.custom_format('ii A i a hh A h a') == '08 AM 8 am 08 AM 8 am'
|
||||
assert time.parse('2023-08-04 09:00:45')!.custom_format('ii A i a hh A h a') == '09 AM 9 am 09 AM 9 am'
|
||||
assert time.parse('2023-08-04 10:00:45')!.custom_format('ii A i a hh A h a') == '10 AM 10 am 10 AM 10 am'
|
||||
assert time.parse('2023-08-04 11:00:45')!.custom_format('ii A i a hh A h a') == '11 AM 11 am 11 AM 11 am'
|
||||
assert time.parse('2023-08-04 12:00:45')!.custom_format('ii A i a hh A h a') == '12 PM 12 pm 12 PM 12 pm'
|
||||
assert time.parse('2023-08-04 13:00:45')!.custom_format('ii A i a hh A h a') == '01 PM 1 pm 01 PM 1 pm'
|
||||
assert time.parse('2023-08-04 14:00:45')!.custom_format('ii A i a hh A h a') == '02 PM 2 pm 02 PM 2 pm'
|
||||
assert time.parse('2023-08-04 15:00:45')!.custom_format('ii A i a hh A h a') == '03 PM 3 pm 03 PM 3 pm'
|
||||
assert time.parse('2023-08-04 16:00:45')!.custom_format('ii A i a hh A h a') == '04 PM 4 pm 04 PM 4 pm'
|
||||
assert time.parse('2023-08-04 17:00:45')!.custom_format('ii A i a hh A h a') == '05 PM 5 pm 05 PM 5 pm'
|
||||
assert time.parse('2023-08-04 18:00:45')!.custom_format('ii A i a hh A h a') == '06 PM 6 pm 06 PM 6 pm'
|
||||
assert time.parse('2023-08-04 19:00:45')!.custom_format('ii A i a hh A h a') == '07 PM 7 pm 07 PM 7 pm'
|
||||
assert time.parse('2023-08-04 20:00:45')!.custom_format('ii A i a hh A h a') == '08 PM 8 pm 08 PM 8 pm'
|
||||
assert time.parse('2023-08-04 21:00:45')!.custom_format('ii A i a hh A h a') == '09 PM 9 pm 09 PM 9 pm'
|
||||
assert time.parse('2023-08-04 22:00:45')!.custom_format('ii A i a hh A h a') == '10 PM 10 pm 10 PM 10 pm'
|
||||
assert time.parse('2023-08-04 23:00:45')!.custom_format('ii A i a hh A h a') == '11 PM 11 pm 11 PM 11 pm'
|
||||
}
|
||||
|
||||
fn test_hours_k_and_kk() {
|
||||
test_time := time.Time{
|
||||
year: 2024
|
||||
month: 7
|
||||
day: 15
|
||||
hour: 14
|
||||
minute: 30
|
||||
second: 45
|
||||
nanosecond: 123456789
|
||||
}
|
||||
assert test_time.custom_format('MM.DD.YYYY k:mm:ss') == '07.15.2024 14:30:45'
|
||||
assert test_time.custom_format('MM.DD.YYYY kk:mm:ss') == '07.15.2024 14:30:45'
|
||||
}
|
||||
|
||||
fn test_zero_date() {
|
||||
zero_date := time.Time{}
|
||||
res :=
|
||||
zero_date.custom_format('M MM Mo MMM MMMM |1| D DD DDD DDDD |2| d dd ddd dddd |3| YY YYYY a A |4| H HH h hh k kk i ii e |5| m mm s ss |6| Do DDDo Q Qo QQ |7| N NN |8| M/D/YYYY N-HH:mm:ss Qo?a')
|
||||
assert res == '0 00 0th Jan January |1| 0 00 0 000 |2| -1 Mo Mon Monday |3| 0 am AM |4| 0 00 12 12 24 24 0 00 e |5| 0 00 0 00 |6| 0th 0th 1 1st 01 |7| AD Anno Domini |8| 0/0/0 AD-00:00:00 1st?am'
|
||||
}
|
||||
|
||||
fn test_quarter() {
|
||||
pattern := '2025-xx-27t12:34:56z'
|
||||
expect := [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]
|
||||
mut want := []int{len: 12}
|
||||
for n in 0 .. 12 {
|
||||
tt := time.parse_rfc3339(pattern.replace('xx', '${n + 1:02}'))!
|
||||
want[n] = tt.custom_format('Q').int()
|
||||
}
|
||||
assert want == expect
|
||||
|
||||
turing := time.parse_format('1912-06-23', 'YYYY-MM-DD')! // Alan Turing was born
|
||||
assert turing.custom_format('Qo') == '2nd'
|
||||
mccarthy := time.parse_format('1927-09-04', 'YYYY-MM-DD')! // John McCarthy was born
|
||||
assert mccarthy.custom_format('QQ') == '03'
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
module time
|
||||
|
||||
struct DateTimeParser {
|
||||
datetime string
|
||||
format string
|
||||
mut:
|
||||
current_pos_datetime int
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn (p &DateTimeParser) matches_at(chars string) bool {
|
||||
end := p.current_pos_datetime + chars.len
|
||||
if end > p.datetime.len {
|
||||
return false
|
||||
}
|
||||
for i in 0 .. chars.len {
|
||||
if p.datetime[p.current_pos_datetime + i] != chars[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fn (mut p DateTimeParser) next(length int) !string {
|
||||
if p.current_pos_datetime + length > p.datetime.len {
|
||||
return error('end of string')
|
||||
}
|
||||
val := p.datetime[p.current_pos_datetime..p.current_pos_datetime + length]
|
||||
p.current_pos_datetime += length
|
||||
return val
|
||||
}
|
||||
|
||||
fn (mut p DateTimeParser) peek(length int) !string {
|
||||
if p.current_pos_datetime + length > p.datetime.len {
|
||||
return error('end of string')
|
||||
}
|
||||
return p.datetime[p.current_pos_datetime..p.current_pos_datetime + length]
|
||||
}
|
||||
|
||||
fn (mut p DateTimeParser) must_be_int(length int) !int {
|
||||
end := p.current_pos_datetime + length
|
||||
if end > p.datetime.len {
|
||||
return error('end of string')
|
||||
}
|
||||
mut val := 0
|
||||
for i in p.current_pos_datetime .. end {
|
||||
ch := p.datetime[i]
|
||||
if ch < `0` || ch > `9` {
|
||||
return error('expected int, found: ${p.datetime[p.current_pos_datetime..end]}')
|
||||
}
|
||||
val = val * 10 + int(ch - `0`)
|
||||
}
|
||||
p.current_pos_datetime = end
|
||||
return val
|
||||
}
|
||||
|
||||
fn (mut p DateTimeParser) must_be_int_with_minimum_length(min int, max int, allow_leading_zero bool) !int {
|
||||
max_len := max + 1 - min
|
||||
start := p.current_pos_datetime
|
||||
mut end := start
|
||||
for _ in 0 .. max_len {
|
||||
if end >= p.datetime.len {
|
||||
break
|
||||
}
|
||||
ch := p.datetime[end]
|
||||
if ch < `0` || ch > `9` {
|
||||
break
|
||||
}
|
||||
end++
|
||||
}
|
||||
if end - start < min {
|
||||
return error('expected int with a minimum length of ${min}, found: ${end - start}')
|
||||
}
|
||||
if !allow_leading_zero && p.datetime[start] == `0` {
|
||||
return error('0 is not allowed for this format')
|
||||
}
|
||||
mut val := 0
|
||||
for i in start .. end {
|
||||
val = val * 10 + int(p.datetime[i] - `0`)
|
||||
}
|
||||
p.current_pos_datetime = end
|
||||
return val
|
||||
}
|
||||
|
||||
fn (mut p DateTimeParser) must_be_string(must string) ! {
|
||||
start := p.current_pos_datetime
|
||||
end := p.current_pos_datetime + must.len
|
||||
if end > p.datetime.len {
|
||||
return error('end of string')
|
||||
}
|
||||
if !p.matches_at(must) {
|
||||
p.current_pos_datetime = end
|
||||
return error('invalid string: "${p.datetime[start..end]}"!="${must}" at: ${p.current_pos_datetime}')
|
||||
}
|
||||
p.current_pos_datetime = end
|
||||
}
|
||||
|
||||
fn (mut p DateTimeParser) must_be_string_one_of(oneof []string) !string {
|
||||
for must in oneof {
|
||||
val := p.peek(must.len) or { continue }
|
||||
if val == must {
|
||||
return must
|
||||
}
|
||||
}
|
||||
return error('invalid string: must be one of ${oneof}, at: ${p.current_pos_datetime}')
|
||||
}
|
||||
|
||||
fn (mut p DateTimeParser) must_be_valid_month() !int {
|
||||
for v in long_months {
|
||||
if p.current_pos_datetime + v.len < p.datetime.len && p.matches_at(v) {
|
||||
p.current_pos_datetime += v.len
|
||||
return long_months.index(v) + 1
|
||||
}
|
||||
}
|
||||
return error_invalid_time(0, 'invalid month name, at: ${p.current_pos_datetime}')
|
||||
}
|
||||
|
||||
fn (mut p DateTimeParser) must_be_valid_three_letter_month() !int {
|
||||
if p.current_pos_datetime + 3 < p.datetime.len {
|
||||
for m := 1; m <= long_months.len; m++ {
|
||||
token := months_string[(m - 1) * 3..m * 3]
|
||||
if p.matches_at(token) {
|
||||
p.current_pos_datetime += 3
|
||||
return m
|
||||
}
|
||||
}
|
||||
}
|
||||
return error_invalid_time(0, 'invalid three letter month, at: ${p.current_pos_datetime}')
|
||||
}
|
||||
|
||||
fn (mut p DateTimeParser) must_be_valid_week_day() !string {
|
||||
for v in long_days {
|
||||
if p.current_pos_datetime + v.len < p.datetime.len && p.matches_at(v) {
|
||||
p.current_pos_datetime += v.len
|
||||
return v
|
||||
}
|
||||
}
|
||||
return error_invalid_time(0, 'invalid weekday, at: ${p.current_pos_datetime}')
|
||||
}
|
||||
|
||||
fn (mut p DateTimeParser) must_be_valid_two_letter_week_day() !int {
|
||||
if p.current_pos_datetime + 2 < p.datetime.len {
|
||||
for d := 1; d <= long_days.len; d++ {
|
||||
token := days_string[(d - 1) * 3..d * 3 - 1]
|
||||
if p.matches_at(token) {
|
||||
p.current_pos_datetime += 2
|
||||
return d
|
||||
}
|
||||
}
|
||||
}
|
||||
return error_invalid_time(0, 'invalid two letter weekday, at: ${p.current_pos_datetime}')
|
||||
}
|
||||
|
||||
fn (mut p DateTimeParser) must_be_valid_three_letter_week_day() !int {
|
||||
if p.current_pos_datetime + 3 < p.datetime.len {
|
||||
for d := 1; d <= long_days.len; d++ {
|
||||
token := days_string[(d - 1) * 3..d * 3]
|
||||
if p.matches_at(token) {
|
||||
p.current_pos_datetime += 3
|
||||
return d
|
||||
}
|
||||
}
|
||||
}
|
||||
return error_invalid_time(0, 'invalid three letter weekday, at: ${p.current_pos_datetime}')
|
||||
}
|
||||
|
||||
fn extract_tokens(s string) ![]string {
|
||||
mut tokens := []string{}
|
||||
if s.len == 0 {
|
||||
return tokens
|
||||
}
|
||||
mut start := 0
|
||||
for i := 1; i < s.len; i++ {
|
||||
if s[i] != s[i - 1] {
|
||||
tokens << s[start..i]
|
||||
start = i
|
||||
}
|
||||
}
|
||||
tokens << s[start..s.len]
|
||||
return tokens
|
||||
}
|
||||
|
||||
// parse_format parses the string `s`, as a custom `format`, containing the following specifiers:
|
||||
// YYYY - 4 digit year, 0000..9999
|
||||
// YY - 2 digit year, 00..99
|
||||
// M - month, 1..12
|
||||
// MM - month, 2 digits, 01..12
|
||||
// MMM - month, three letters, Jan..Dec
|
||||
// MMMM - name of month
|
||||
// D - day of the month, 1..31
|
||||
// DD - day of the month, 01..31
|
||||
// d - day of week, 0..6
|
||||
// c - day of week, 1..7
|
||||
// dd - day of week, Su..Sa
|
||||
// ddd - day of week, Sun..Sat
|
||||
// dddd - day of week, Sunday..Saturday
|
||||
// H - hour, 0..23
|
||||
// HH - hour, 00..23
|
||||
// h - hour, 0..23
|
||||
// hh - hour, 0..23
|
||||
// k - hour, 0..23
|
||||
// kk - hour, 0..23
|
||||
// m - minute, 0..59
|
||||
// mm - minute, 0..59
|
||||
// s - second, 0..59
|
||||
// ss - second, 0..59
|
||||
fn (mut p DateTimeParser) parse() !Time {
|
||||
mut year_ := 0
|
||||
mut month_ := 0
|
||||
mut day_in_month := 0
|
||||
mut hour_ := 0
|
||||
mut minute_ := 0
|
||||
mut second_ := 0
|
||||
tokens := extract_tokens(p.format) or {
|
||||
return error_invalid_time(0, 'malformed format string: ${err}')
|
||||
}
|
||||
for token in tokens {
|
||||
match token {
|
||||
'YYYY' {
|
||||
year_ = p.must_be_int(4) or {
|
||||
return error_invalid_time(0,
|
||||
'end of string reached before the full year was specified')
|
||||
}
|
||||
}
|
||||
'YY' {
|
||||
year_ = now().year / 100 * 100 + p.must_be_int(2) or {
|
||||
return error_invalid_time(0, 'end of string reached before the full year was specified')
|
||||
}
|
||||
}
|
||||
'M' {
|
||||
month_ = p.must_be_int_with_minimum_length(1, 2, false) or {
|
||||
return error_invalid_time(0,
|
||||
'end of string reached before the month was specified')
|
||||
}
|
||||
if month_ < 1 || month_ > 12 {
|
||||
return error_invalid_time(0, 'month must be between 1 and 12')
|
||||
}
|
||||
}
|
||||
'MM' {
|
||||
month_ = p.must_be_int(2) or {
|
||||
return error_invalid_time(0,
|
||||
'end of string reached before the month was specified')
|
||||
}
|
||||
if month_ < 1 || month_ > 12 {
|
||||
return error_invalid_time(0, 'month must be between 01 and 12')
|
||||
}
|
||||
}
|
||||
'MMM' {
|
||||
month_ = p.must_be_valid_three_letter_month() or { return err }
|
||||
}
|
||||
'MMMM' {
|
||||
month_ = p.must_be_valid_month() or { return err }
|
||||
}
|
||||
'D' {
|
||||
day_in_month = p.must_be_int_with_minimum_length(1, 2, false) or {
|
||||
return error_invalid_time(0,
|
||||
'end of string reached before the day was specified')
|
||||
}
|
||||
if day_in_month < 1 || day_in_month > 31 {
|
||||
return error_invalid_time(0, 'day must be between 1 and 31')
|
||||
}
|
||||
}
|
||||
'DD' {
|
||||
day_in_month = p.must_be_int(2) or {
|
||||
return error_invalid_time(0,
|
||||
'end of string reached before the month was specified')
|
||||
}
|
||||
if day_in_month < 1 || day_in_month > 31 {
|
||||
return error_invalid_time(0, 'day must be between 01 and 31')
|
||||
}
|
||||
}
|
||||
'd' {
|
||||
p.must_be_int(1) or { return err }
|
||||
}
|
||||
'c' {
|
||||
p.must_be_int(1) or { return err }
|
||||
}
|
||||
'dd' {
|
||||
p.must_be_valid_two_letter_week_day() or { return err }
|
||||
}
|
||||
'ddd' {
|
||||
p.must_be_valid_three_letter_week_day() or { return err }
|
||||
}
|
||||
'dddd' {
|
||||
p.must_be_valid_week_day() or { return err }
|
||||
}
|
||||
'H' {
|
||||
hour_ = p.must_be_int_with_minimum_length(1, 2, true) or {
|
||||
return error_invalid_time(0,
|
||||
'end of string reached before hours where specified')
|
||||
}
|
||||
if hour_ < 0 || hour_ > 23 {
|
||||
return error_invalid_time(0, 'hour must be between 0 and 23')
|
||||
}
|
||||
}
|
||||
'HH' {
|
||||
hour_ = p.must_be_int(2) or {
|
||||
return error_invalid_time(0,
|
||||
'end of string reached before hours where specified')
|
||||
}
|
||||
if hour_ < 0 || hour_ > 23 {
|
||||
return error_invalid_time(0, 'hour must be between 00 and 23')
|
||||
}
|
||||
}
|
||||
'h' {
|
||||
hour_ = p.must_be_int_with_minimum_length(1, 2, true) or {
|
||||
return error_invalid_time(0,
|
||||
'end of string reached before hours where specified')
|
||||
}
|
||||
if hour_ < 0 || hour_ > 23 {
|
||||
return error_invalid_time(0, 'hour must be between 0 and 23')
|
||||
}
|
||||
}
|
||||
'hh' {
|
||||
hour_ = p.must_be_int(2) or {
|
||||
return error_invalid_time(0,
|
||||
'end of string reached before hours where specified')
|
||||
}
|
||||
if hour_ < 0 || hour_ > 23 {
|
||||
return error_invalid_time(0, 'hour must be between 00 and 23')
|
||||
}
|
||||
}
|
||||
'k' {
|
||||
hour_ = p.must_be_int(1) or {
|
||||
return error_invalid_time(0,
|
||||
'end of string reached before hours where specified')
|
||||
}
|
||||
if hour_ < 0 || hour_ > 23 {
|
||||
return error_invalid_time(0, 'hour must be between 0 and 23')
|
||||
}
|
||||
}
|
||||
'kk' {
|
||||
hour_ = p.must_be_int(2) or {
|
||||
return error_invalid_time(0,
|
||||
'end of string reached before hours where specified')
|
||||
}
|
||||
if hour_ < 0 || hour_ > 23 {
|
||||
return error_invalid_time(0, 'hour must be between 00 and 23')
|
||||
}
|
||||
}
|
||||
'm' {
|
||||
minute_ = p.must_be_int(1) or {
|
||||
return error_invalid_time(0,
|
||||
'end of string reached before minutes where specified')
|
||||
}
|
||||
if minute_ < 0 || minute_ > 59 {
|
||||
return error_invalid_time(0, 'minute must be between 0 and 59')
|
||||
}
|
||||
}
|
||||
'mm' {
|
||||
minute_ = p.must_be_int(2) or {
|
||||
return error_invalid_time(0,
|
||||
'end of string reached before minutes where specified')
|
||||
}
|
||||
if minute_ < 0 || minute_ > 59 {
|
||||
return error_invalid_time(0, 'minute must be between 00 and 59')
|
||||
}
|
||||
}
|
||||
's' {
|
||||
second_ = p.must_be_int(1) or {
|
||||
return error_invalid_time(0,
|
||||
'end of string reached before seconds where specified')
|
||||
}
|
||||
if second_ < 0 || second_ > 59 {
|
||||
return error_invalid_time(0, 'second must be between 0 and 59')
|
||||
}
|
||||
}
|
||||
'ss' {
|
||||
second_ = p.must_be_int(2) or {
|
||||
return error_invalid_time(0,
|
||||
'end of string reached before seconds where specified')
|
||||
}
|
||||
if second_ < 0 || second_ > 59 {
|
||||
return error_invalid_time(0, 'second must be between 00 and 59')
|
||||
}
|
||||
}
|
||||
else {
|
||||
p.must_be_string(token) or { return error_invalid_time(0, '${err}') }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if month_ == 2 {
|
||||
feb_days_in_year := if is_leap_year(year_) { 29 } else { 28 }
|
||||
if day_in_month > feb_days_in_year {
|
||||
return error_invalid_time(0, 'February has only 28 days in the given year')
|
||||
}
|
||||
} else if day_in_month == 31 && month_ !in [1, 3, 5, 7, 8, 10, 12] {
|
||||
month_name := Time{
|
||||
month: month_
|
||||
}.custom_format('MMMM')
|
||||
return error_invalid_time(0, '${month_name} has only 30 days')
|
||||
}
|
||||
|
||||
return new(
|
||||
year: year_
|
||||
month: month_
|
||||
day: day_in_month
|
||||
hour: hour_
|
||||
minute: minute_
|
||||
second: second_
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
module time
|
||||
|
||||
// A lot of these are taken from the Go library.
|
||||
pub type Duration = i64
|
||||
|
||||
@[markused]
|
||||
pub const nanosecond = Duration(1)
|
||||
pub const microsecond = Duration(1000 * nanosecond)
|
||||
pub const millisecond = Duration(1000 * microsecond)
|
||||
pub const second = Duration(1000 * millisecond)
|
||||
pub const minute = Duration(60 * second)
|
||||
pub const hour = Duration(60 * minute)
|
||||
// day = Duration(24 * hour)
|
||||
pub const infinite = Duration(i64(9223372036854775807))
|
||||
|
||||
// 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) / microsecond
|
||||
}
|
||||
|
||||
// milliseconds returns the duration as an integer number of milliseconds.
|
||||
pub fn (d Duration) milliseconds() i64 {
|
||||
return i64(d) / millisecond
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return f64(d) / f64(second)
|
||||
}
|
||||
|
||||
// minutes returns the duration as a floating point number of minutes.
|
||||
pub fn (d Duration) minutes() f64 {
|
||||
return f64(d) / f64(minute)
|
||||
}
|
||||
|
||||
// hours returns the duration as a floating point number of hours.
|
||||
pub fn (d Duration) hours() f64 {
|
||||
return f64(d) / f64(hour)
|
||||
}
|
||||
|
||||
// days returns the duration as a floating point number of days.
|
||||
pub fn (d Duration) days() f64 {
|
||||
return f64(d) / f64(hour * 24)
|
||||
}
|
||||
|
||||
fn duration_pad2(n i64) string {
|
||||
if n < 10 {
|
||||
return '0' + n.str()
|
||||
}
|
||||
return n.str()
|
||||
}
|
||||
|
||||
fn duration_pad3(n i64) string {
|
||||
if n < 10 {
|
||||
return '00' + n.str()
|
||||
}
|
||||
if n < 100 {
|
||||
return '0' + n.str()
|
||||
}
|
||||
return n.str()
|
||||
}
|
||||
|
||||
// str pretty prints the duration
|
||||
//
|
||||
// ```
|
||||
// h:m:s // 5:02:33
|
||||
// m:s.mi<s> // 2:33.015
|
||||
// s.mi<s> // 33.015s
|
||||
// mi.mc<ms> // 15.007ms
|
||||
// mc.ns<ns> // 7.234us
|
||||
// ns<ns> // 234ns
|
||||
// ```
|
||||
pub fn (d Duration) str() string {
|
||||
if d == infinite {
|
||||
return 'inf'
|
||||
}
|
||||
mut sign := ''
|
||||
mut t := i64(d)
|
||||
if t < 0 {
|
||||
sign = '-'
|
||||
t = -t
|
||||
}
|
||||
hr := t / hour
|
||||
t -= hr * hour
|
||||
min := t / minute
|
||||
t -= min * minute
|
||||
sec := t / second
|
||||
t -= sec * second
|
||||
ms := t / millisecond
|
||||
t -= ms * millisecond
|
||||
us := t / microsecond
|
||||
t -= us * microsecond
|
||||
ns := t
|
||||
|
||||
if hr > 0 {
|
||||
return sign + hr.str() + ':' + duration_pad2(min) + ':' + duration_pad2(sec)
|
||||
}
|
||||
if min > 0 {
|
||||
return sign + min.str() + ':' + duration_pad2(sec) + '.' + duration_pad3(ms)
|
||||
}
|
||||
if sec > 0 {
|
||||
return sign + sec.str() + '.' + duration_pad3(ms) + 's'
|
||||
}
|
||||
if ms > 0 {
|
||||
return sign + ms.str() + '.' + duration_pad3(us) + 'ms'
|
||||
}
|
||||
if us > 0 {
|
||||
return sign + us.str() + '.' + duration_pad3(ns) + 'us'
|
||||
}
|
||||
return sign + ns.str() + 'ns'
|
||||
}
|
||||
|
||||
// debug returns a detailed breakdown of the Duration, as: 'Duration: - 50days, 4h, 3m, 7s, 541ms, 78us, 9ns'.
|
||||
pub fn (d Duration) debug() string {
|
||||
mut res := []string{}
|
||||
mut x := i64(d)
|
||||
mut sign := ''
|
||||
if x < 0 {
|
||||
sign = '- '
|
||||
x = -x
|
||||
}
|
||||
for label, v in {
|
||||
'days': 24 * hour
|
||||
'h': hour
|
||||
'm': minute
|
||||
's': second
|
||||
'ms': millisecond
|
||||
'us': microsecond
|
||||
} {
|
||||
if x > v {
|
||||
xx := x / v
|
||||
x = x % v
|
||||
res << xx.str() + label
|
||||
}
|
||||
}
|
||||
if x > 0 {
|
||||
res << x.str() + 'ns'
|
||||
}
|
||||
return 'Duration: ' + sign + res.join(', ')
|
||||
}
|
||||
|
||||
// times allows you to return fractional unit durations, based on an existing duration.
|
||||
// For example, you can: `half_an_hour := time.hour.times(0.5)` .
|
||||
pub fn (d Duration) times(x f64) Duration {
|
||||
return f64(d) * x
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import time
|
||||
|
||||
fn test_duration_str() {
|
||||
assert time.Duration(1 * time.nanosecond).str() == '1ns'
|
||||
assert time.Duration(999 * time.nanosecond).str() == '999ns'
|
||||
assert time.Duration(1000 * time.nanosecond).str() == '1.000us'
|
||||
|
||||
assert time.Duration(1 * time.microsecond).str() == '1.000us'
|
||||
assert time.Duration(999 * time.microsecond).str() == '999.000us'
|
||||
assert time.Duration(1000 * time.microsecond).str() == '1.000ms'
|
||||
|
||||
assert time.Duration(1 * time.second).str() == '1.000s'
|
||||
assert time.Duration(999 * time.second).str() == '16:39.000'
|
||||
assert time.Duration(1000 * time.second).str() == '16:40.000'
|
||||
|
||||
assert time.Duration(1 * time.minute).str() == '1:00.000'
|
||||
assert time.Duration(999 * time.minute).str() == '16:39:00'
|
||||
assert time.Duration(1000 * time.minute).str() == '16:40:00'
|
||||
|
||||
assert time.Duration(1 * time.hour).str() == '1:00:00'
|
||||
assert time.Duration(999 * time.hour).str() == '999:00:00'
|
||||
assert time.Duration(1000 * time.hour).str() == '1000:00:00'
|
||||
|
||||
assert time.Duration(1 * time.microsecond + 7 * time.nanosecond).str() == '1.007us'
|
||||
|
||||
assert time.Duration(1 * time.second + 5 * time.nanosecond).str() == '1.000s'
|
||||
assert time.Duration(1 * time.second + 5 * time.microsecond).str() == '1.000s'
|
||||
assert time.Duration(1 * time.second + 5 * time.millisecond).str() == '1.005s'
|
||||
|
||||
assert time.Duration(1 * time.hour + 5 * time.millisecond).str() == '1:00:00'
|
||||
assert time.Duration(1 * time.hour + 5 * time.second).str() == '1:00:05'
|
||||
assert time.Duration(168 * time.hour + 5 * time.minute + 7 * time.second).str() == '168:05:07'
|
||||
}
|
||||
|
||||
fn test_negative_duration() {
|
||||
now := time.parse('2000-01-01 10:00:00')!
|
||||
later := time.parse('2000-01-01 11:00:00')!
|
||||
duration := now - later
|
||||
assert time.Duration(-1 * time.hour) == duration
|
||||
assert duration.str() == '-1:00:00'
|
||||
}
|
||||
|
||||
fn test_duration_debug() {
|
||||
assert time.Duration(1 * time.nanosecond).debug() == 'Duration: 1ns'
|
||||
assert time.Duration(169 * time.hour + 5 * time.minute + 7 * time.second).debug() == 'Duration: 7days, 1h, 5m, 7s'
|
||||
assert (-time.Duration(169 * time.hour + 5 * time.minute + 7 * time.second)).debug() == 'Duration: - 7days, 1h, 5m, 7s'
|
||||
}
|
||||
|
||||
fn test_duration_times() {
|
||||
assert time.second.times(2) == 2 * time.second
|
||||
assert time.minute.times(0.5) == 30 * time.second
|
||||
assert time.minute.times(-1.5) == -90 * time.second
|
||||
}
|
||||
@@ -0,0 +1,769 @@
|
||||
// Copyright (c) 2019-2024 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
|
||||
|
||||
import strings
|
||||
|
||||
fn iclamp(x int, a int, b int) int {
|
||||
if x < a {
|
||||
return a
|
||||
}
|
||||
if x > b {
|
||||
return b
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// int_to_byte_array_no_pad fulfill buffer by part
|
||||
// it doesn't pad with leading zeros for performance reasons
|
||||
@[direct_array_access]
|
||||
fn int_to_byte_array_no_pad(value int, mut arr []u8, size int) {
|
||||
mut num := value
|
||||
if size <= 0 || num < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Start from the end of the array
|
||||
mut i := size - 1
|
||||
|
||||
// Convert each digit to a character and store it in the array
|
||||
for num > 0 && i >= 0 {
|
||||
arr[i] = (num % 10) + `0`
|
||||
num /= 10
|
||||
i--
|
||||
}
|
||||
}
|
||||
|
||||
// int_to_byte_array_no_pad fulfill buffer by part
|
||||
// it doesn't pad with leading zeros for performance reasons
|
||||
@[direct_array_access; unsafe]
|
||||
fn int_to_ptr_byte_array_no_pad(value int, arr_prt &u8, arr_len int) {
|
||||
mut num := value
|
||||
if arr_len <= 0 || num < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Start from the end of the array
|
||||
mut i := arr_len - 1
|
||||
|
||||
// Convert each digit to a character and store it in the array
|
||||
for num > 0 && i >= 0 {
|
||||
unsafe {
|
||||
*(arr_prt + i) = (num % 10) + `0`
|
||||
}
|
||||
num /= 10
|
||||
i--
|
||||
}
|
||||
}
|
||||
|
||||
// format returns a date string in "YYYY-MM-DD HH:mm" format (24h).
|
||||
@[manualfree]
|
||||
pub fn (t Time) format() string {
|
||||
mut buf := [u8(`0`), `0`, `0`, `0`, `-`, `0`, `0`, `-`, `0`, `0`, ` `, `0`, `0`, `:`, `0`,
|
||||
`0`]
|
||||
|
||||
defer {
|
||||
unsafe { buf.free() }
|
||||
}
|
||||
|
||||
int_to_byte_array_no_pad(t.year, mut buf, 4)
|
||||
int_to_byte_array_no_pad(t.month, mut buf, 7)
|
||||
int_to_byte_array_no_pad(t.day, mut buf, 10)
|
||||
|
||||
int_to_byte_array_no_pad(t.hour, mut buf, 13)
|
||||
int_to_byte_array_no_pad(t.minute, mut buf, 16)
|
||||
|
||||
return buf.bytestr()
|
||||
}
|
||||
|
||||
// format_ss returns a date string in "YYYY-MM-DD HH:mm:ss" format (24h).
|
||||
@[manualfree]
|
||||
pub fn (t Time) format_ss() string {
|
||||
mut buf := [u8(`0`), `0`, `0`, `0`, `-`, `0`, `0`, `-`, `0`, `0`, ` `, `0`, `0`, `:`, `0`,
|
||||
`0`, `:`, `0`, `0`]
|
||||
|
||||
defer {
|
||||
unsafe { buf.free() }
|
||||
}
|
||||
|
||||
int_to_byte_array_no_pad(t.year, mut buf, 4)
|
||||
int_to_byte_array_no_pad(t.month, mut buf, 7)
|
||||
int_to_byte_array_no_pad(t.day, mut buf, 10)
|
||||
|
||||
int_to_byte_array_no_pad(t.hour, mut buf, 13)
|
||||
int_to_byte_array_no_pad(t.minute, mut buf, 16)
|
||||
int_to_byte_array_no_pad(t.second, mut buf, 19)
|
||||
|
||||
return buf.bytestr()
|
||||
}
|
||||
|
||||
// format_ss_milli returns a date string in "YYYY-MM-DD HH:mm:ss.123" format (24h).
|
||||
@[manualfree]
|
||||
pub fn (t Time) format_ss_milli() string {
|
||||
mut buf := [u8(`0`), `0`, `0`, `0`, `-`, `0`, `0`, `-`, `0`, `0`, ` `, `0`, `0`, `:`, `0`,
|
||||
`0`, `:`, `0`, `0`, `.`, `0`, `0`, `0`]
|
||||
|
||||
defer {
|
||||
unsafe { buf.free() }
|
||||
}
|
||||
|
||||
int_to_byte_array_no_pad(t.year, mut buf, 4)
|
||||
int_to_byte_array_no_pad(t.month, mut buf, 7)
|
||||
int_to_byte_array_no_pad(t.day, mut buf, 10)
|
||||
|
||||
int_to_byte_array_no_pad(t.hour, mut buf, 13)
|
||||
int_to_byte_array_no_pad(t.minute, mut buf, 16)
|
||||
int_to_byte_array_no_pad(t.second, mut buf, 19)
|
||||
|
||||
// Extract and format milliseconds
|
||||
millis := t.nanosecond / 1_000_000
|
||||
int_to_byte_array_no_pad(millis, mut buf, 23)
|
||||
|
||||
return buf.bytestr()
|
||||
}
|
||||
|
||||
// format_ss_micro returns a date string in "YYYY-MM-DD HH:mm:ss.123456" format (24h).
|
||||
@[manualfree]
|
||||
pub fn (t Time) format_ss_micro() string {
|
||||
mut buf := [u8(`0`), `0`, `0`, `0`, `-`, `0`, `0`, `-`, `0`, `0`, ` `, `0`, `0`, `:`, `0`,
|
||||
`0`, `:`, `0`, `0`, `.`, `0`, `0`, `0`, `0`, `0`, `0`]
|
||||
|
||||
defer {
|
||||
unsafe { buf.free() }
|
||||
}
|
||||
|
||||
int_to_byte_array_no_pad(t.year, mut buf, 4)
|
||||
int_to_byte_array_no_pad(t.month, mut buf, 7)
|
||||
int_to_byte_array_no_pad(t.day, mut buf, 10)
|
||||
|
||||
int_to_byte_array_no_pad(t.hour, mut buf, 13)
|
||||
int_to_byte_array_no_pad(t.minute, mut buf, 16)
|
||||
int_to_byte_array_no_pad(t.second, mut buf, 19)
|
||||
|
||||
// Extract and format microseconds
|
||||
micros := t.nanosecond / 1_000
|
||||
int_to_byte_array_no_pad(micros, mut buf, 26)
|
||||
|
||||
return buf.bytestr()
|
||||
}
|
||||
|
||||
// format_ss_nano returns a date string in "YYYY-MM-DD HH:mm:ss.123456789" format (24h).
|
||||
@[manualfree]
|
||||
pub fn (t Time) format_ss_nano() string {
|
||||
mut buf := [u8(`0`), `0`, `0`, `0`, `-`, `0`, `0`, `-`, `0`, `0`, ` `, `0`, `0`, `:`, `0`,
|
||||
`0`, `:`, `0`, `0`, `.`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`]
|
||||
|
||||
defer {
|
||||
unsafe { buf.free() }
|
||||
}
|
||||
|
||||
int_to_byte_array_no_pad(t.year, mut buf, 4)
|
||||
int_to_byte_array_no_pad(t.month, mut buf, 7)
|
||||
int_to_byte_array_no_pad(t.day, mut buf, 10)
|
||||
|
||||
int_to_byte_array_no_pad(t.hour, mut buf, 13)
|
||||
int_to_byte_array_no_pad(t.minute, mut buf, 16)
|
||||
int_to_byte_array_no_pad(t.second, mut buf, 19)
|
||||
|
||||
int_to_byte_array_no_pad(t.nanosecond, mut buf, 29) // Adjusted index for 9 digits
|
||||
|
||||
return buf.bytestr()
|
||||
}
|
||||
|
||||
// format_rfc3339 returns a date string in "YYYY-MM-DDTHH:mm:ss.123Z" format (24 hours, see https://www.rfc-editor.org/rfc/rfc3339.html)
|
||||
// RFC3339 is an Internet profile, based on the ISO 8601 standard for for representation of dates and times using the Gregorian calendar.
|
||||
// It is intended to improve consistency and interoperability, when representing and using date and time in Internet protocols.
|
||||
@[manualfree]
|
||||
pub fn (t Time) format_rfc3339() string {
|
||||
mut buf := [u8(`0`), `0`, `0`, `0`, `-`, `0`, `0`, `-`, `0`, `0`, `T`, `0`, `0`, `:`, `0`,
|
||||
`0`, `:`, `0`, `0`, `.`, `0`, `0`, `0`, `Z`]
|
||||
|
||||
defer {
|
||||
unsafe { buf.free() }
|
||||
}
|
||||
|
||||
t_ := time_with_unix(t)
|
||||
if t_.is_local {
|
||||
utc_time := t_.local_to_utc()
|
||||
int_to_byte_array_no_pad(utc_time.year, mut buf, 4)
|
||||
int_to_byte_array_no_pad(utc_time.month, mut buf, 7)
|
||||
int_to_byte_array_no_pad(utc_time.day, mut buf, 10)
|
||||
int_to_byte_array_no_pad(utc_time.hour, mut buf, 13)
|
||||
int_to_byte_array_no_pad(utc_time.minute, mut buf, 16)
|
||||
int_to_byte_array_no_pad(utc_time.second, mut buf, 19)
|
||||
int_to_byte_array_no_pad(utc_time.nanosecond / 1_000_000, mut buf, 23)
|
||||
} else {
|
||||
int_to_byte_array_no_pad(t_.year, mut buf, 4)
|
||||
int_to_byte_array_no_pad(t_.month, mut buf, 7)
|
||||
int_to_byte_array_no_pad(t_.day, mut buf, 10)
|
||||
int_to_byte_array_no_pad(t_.hour, mut buf, 13)
|
||||
int_to_byte_array_no_pad(t_.minute, mut buf, 16)
|
||||
int_to_byte_array_no_pad(t_.second, mut buf, 19)
|
||||
int_to_byte_array_no_pad(t_.nanosecond / 1_000_000, mut buf, 23)
|
||||
}
|
||||
|
||||
return buf.bytestr()
|
||||
}
|
||||
|
||||
// format_rfc3339_micro returns a date string in "YYYY-MM-DDTHH:mm:ss.123456Z" format (24 hours, see https://www.rfc-editor.org/rfc/rfc3339.html)
|
||||
@[manualfree]
|
||||
pub fn (t Time) format_rfc3339_micro() string {
|
||||
mut buf := [u8(`0`), `0`, `0`, `0`, `-`, `0`, `0`, `-`, `0`, `0`, `T`, `0`, `0`, `:`, `0`,
|
||||
`0`, `:`, `0`, `0`, `.`, `0`, `0`, `0`, `0`, `0`, `0`, `Z`]
|
||||
|
||||
defer {
|
||||
unsafe { buf.free() }
|
||||
}
|
||||
|
||||
t_ := time_with_unix(t)
|
||||
if t_.is_local {
|
||||
utc_time := t_.local_to_utc()
|
||||
int_to_byte_array_no_pad(utc_time.year, mut buf, 4)
|
||||
int_to_byte_array_no_pad(utc_time.month, mut buf, 7)
|
||||
int_to_byte_array_no_pad(utc_time.day, mut buf, 10)
|
||||
int_to_byte_array_no_pad(utc_time.hour, mut buf, 13)
|
||||
int_to_byte_array_no_pad(utc_time.minute, mut buf, 16)
|
||||
int_to_byte_array_no_pad(utc_time.second, mut buf, 19)
|
||||
int_to_byte_array_no_pad(utc_time.nanosecond / 1000, mut buf, 26)
|
||||
} else {
|
||||
int_to_byte_array_no_pad(t_.year, mut buf, 4)
|
||||
int_to_byte_array_no_pad(t_.month, mut buf, 7)
|
||||
int_to_byte_array_no_pad(t_.day, mut buf, 10)
|
||||
int_to_byte_array_no_pad(t_.hour, mut buf, 13)
|
||||
int_to_byte_array_no_pad(t_.minute, mut buf, 16)
|
||||
int_to_byte_array_no_pad(t_.second, mut buf, 19)
|
||||
int_to_byte_array_no_pad(t_.nanosecond / 1000, mut buf, 26)
|
||||
}
|
||||
|
||||
return buf.bytestr()
|
||||
}
|
||||
|
||||
// format_rfc3339_nano returns a date string in "YYYY-MM-DDTHH:mm:ss.123456789Z" format (24 hours, see https://www.rfc-editor.org/rfc/rfc3339.html)
|
||||
@[manualfree]
|
||||
pub fn (t Time) format_rfc3339_nano() string {
|
||||
mut buf := [u8(`0`), `0`, `0`, `0`, `-`, `0`, `0`, `-`, `0`, `0`, `T`, `0`, `0`, `:`, `0`,
|
||||
`0`, `:`, `0`, `0`, `.`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `Z`]
|
||||
|
||||
defer {
|
||||
unsafe { buf.free() }
|
||||
}
|
||||
|
||||
t_ := time_with_unix(t)
|
||||
if t_.is_local {
|
||||
utc_time := t_.local_to_utc()
|
||||
int_to_byte_array_no_pad(utc_time.year, mut buf, 4)
|
||||
int_to_byte_array_no_pad(utc_time.month, mut buf, 7)
|
||||
int_to_byte_array_no_pad(utc_time.day, mut buf, 10)
|
||||
int_to_byte_array_no_pad(utc_time.hour, mut buf, 13)
|
||||
int_to_byte_array_no_pad(utc_time.minute, mut buf, 16)
|
||||
int_to_byte_array_no_pad(utc_time.second, mut buf, 19)
|
||||
int_to_byte_array_no_pad(utc_time.nanosecond, mut buf, 29)
|
||||
} else {
|
||||
int_to_byte_array_no_pad(t_.year, mut buf, 4)
|
||||
int_to_byte_array_no_pad(t_.month, mut buf, 7)
|
||||
int_to_byte_array_no_pad(t_.day, mut buf, 10)
|
||||
int_to_byte_array_no_pad(t_.hour, mut buf, 13)
|
||||
int_to_byte_array_no_pad(t_.minute, mut buf, 16)
|
||||
int_to_byte_array_no_pad(t_.second, mut buf, 19)
|
||||
int_to_byte_array_no_pad(t_.nanosecond, mut buf, 29)
|
||||
}
|
||||
|
||||
return buf.bytestr()
|
||||
}
|
||||
|
||||
// hhmm returns a date string in "HH:mm" format (24h).
|
||||
@[manualfree]
|
||||
pub fn (t Time) hhmm() string {
|
||||
mut buf := [u8(`0`), `0`, `:`, `0`, `0`]
|
||||
|
||||
defer {
|
||||
unsafe { buf.free() }
|
||||
}
|
||||
|
||||
int_to_byte_array_no_pad(t.hour, mut buf, 2)
|
||||
int_to_byte_array_no_pad(t.minute, mut buf, 5)
|
||||
|
||||
return buf.bytestr()
|
||||
}
|
||||
|
||||
// hhmmss returns a date string in "HH:mm:ss" format (24h).
|
||||
@[manualfree]
|
||||
pub fn (t Time) hhmmss() string {
|
||||
mut buf := [u8(`0`), `0`, `:`, `0`, `0`, `:`, `0`, `0`]
|
||||
|
||||
defer {
|
||||
unsafe { buf.free() }
|
||||
}
|
||||
|
||||
int_to_byte_array_no_pad(t.hour, mut buf, 2)
|
||||
int_to_byte_array_no_pad(t.minute, mut buf, 5)
|
||||
int_to_byte_array_no_pad(t.second, mut buf, 8)
|
||||
|
||||
return buf.bytestr()
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// TODO: test, improve performance
|
||||
// appends ordinal suffix to a number
|
||||
fn ordinal_suffix(n int) string {
|
||||
if n > 3 && n < 21 {
|
||||
return '${n}th'
|
||||
}
|
||||
match n % 10 {
|
||||
1 {
|
||||
return '${n}st'
|
||||
}
|
||||
2 {
|
||||
return '${n}nd'
|
||||
}
|
||||
3 {
|
||||
return '${n}rd'
|
||||
}
|
||||
else {
|
||||
return '${n}th'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const tokens_2 = ['MM', 'Mo', 'DD', 'Do', 'YY', 'ss', 'kk', 'NN', 'mm', 'hh', 'HH', 'ii', 'ZZ',
|
||||
'dd', 'Qo', 'QQ', 'wo', 'ww']
|
||||
const tokens_3 = ['MMM', 'DDD', 'ZZZ', 'ddd']
|
||||
const tokens_4 = ['MMMM', 'DDDD', 'DDDo', 'dddd', 'YYYY']
|
||||
|
||||
// custom_format returns a date with custom format
|
||||
//
|
||||
// | Category | Token | Output |
|
||||
// |:-----------------|:------|:---------------------------------------|
|
||||
// | Era | N | BC AD |
|
||||
// | | NN | Before Christ, Anno Domini |
|
||||
// | Year | YY | 70 71 ... 29 30 |
|
||||
// | | YYYY | 1970 1971 ... 2029 2030 |
|
||||
// | Quarter | Q | 1 2 3 4 |
|
||||
// | | QQ | 01 02 03 04 |
|
||||
// | | Qo | 1st 2nd 3rd 4th |
|
||||
// | Month | M | 1 2 ... 11 12 |
|
||||
// | | Mo | 1st 2nd ... 11th 12th |
|
||||
// | | MM | 01 02 ... 11 12 |
|
||||
// | | MMM | Jan Feb ... Nov Dec |
|
||||
// | | MMMM | January February ... November December |
|
||||
// | Week of Year | w | 1 2 ... 52 53 |
|
||||
// | | wo | 1st 2nd ... 52nd 53rd |
|
||||
// | | ww | 01 02 ... 52 53 |
|
||||
// | Day of Month | D | 1 2 ... 30 31 |
|
||||
// | | Do | 1st 2nd ... 30th 31st |
|
||||
// | | DD | 01 02 ... 30 31 |
|
||||
// | Day of Year | DDD | 1 2 ... 364 365 |
|
||||
// | | DDDo | 1st 2nd ... 364th 365th |
|
||||
// | | DDDD | 001 002 ... 364 365 |
|
||||
// | Day of Week | d | 0 1 ... 5 6 (Sun-Sat) |
|
||||
// | | c | 1 2 ... 6 7 (Mon-Sun) |
|
||||
// | | dd | Su Mo ... Fr Sa |
|
||||
// | | ddd | Sun Mon ... Fri Sat |
|
||||
// | | dddd | Sunday Monday ... Friday Saturday |
|
||||
// | AM/PM | A | AM PM |
|
||||
// | | a | am pm |
|
||||
// | Hour | H | 0 1 ... 22 23 |
|
||||
// | | HH | 00 01 ... 22 23 |
|
||||
// | | h | 1 2 ... 11 12 |
|
||||
// | | hh | 01 02 ... 11 12 |
|
||||
// | | i | 0 1 ... 11 12 1 ... 11 |
|
||||
// | | ii | 00 01 ... 11 12 01 ... 11 |
|
||||
// | | k | 1 2 ... 23 24 |
|
||||
// | | kk | 01 02 ... 23 24 |
|
||||
// | Minute | m | 0 1 ... 58 59 |
|
||||
// | | mm | 00 01 ... 58 59 |
|
||||
// | Second | s | 0 1 ... 58 59 |
|
||||
// | | ss | 00 01 ... 58 59 |
|
||||
// | Offset | Z | -7 -6 ... +5 +6 |
|
||||
// | | ZZ | -0700 -0600 ... +0500 +0600 |
|
||||
// | | ZZZ | -07:00 -06:00 ... +05:00 +06:00 |
|
||||
//
|
||||
// Usage:
|
||||
// ```v
|
||||
// println(time.now().custom_format('MMMM Mo YY N kk:mm:ss A')) // output like: January 1st 22 AD 13:45:33 PM
|
||||
// ```
|
||||
pub fn (t Time) custom_format(s string) string {
|
||||
mut tokens := []string{}
|
||||
for i := 0; i < s.len; {
|
||||
for j := 4; j > 0; j-- {
|
||||
if i > s.len - j {
|
||||
continue
|
||||
}
|
||||
if j == 1 || (j == 2 && s[i..i + j] in tokens_2)
|
||||
|| (j == 3 && s[i..i + j] in tokens_3)
|
||||
|| (j == 4 && s[i..i + j] in tokens_4) {
|
||||
tokens << s[i..i + j]
|
||||
i += (j - 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
mut sb := strings.new_builder(128)
|
||||
|
||||
for token in tokens {
|
||||
match token {
|
||||
'M' {
|
||||
sb.write_string(t.month.str())
|
||||
}
|
||||
'MM' {
|
||||
sb.write_string('${t.month:02}')
|
||||
}
|
||||
'Mo' {
|
||||
sb.write_string(ordinal_suffix(t.month))
|
||||
}
|
||||
'MMM' {
|
||||
sb.write_string(long_months[iclamp(0, t.month - 1, 11)][0..3])
|
||||
}
|
||||
'MMMM' {
|
||||
sb.write_string(long_months[iclamp(0, t.month - 1, 11)])
|
||||
}
|
||||
'D' {
|
||||
sb.write_string(t.day.str())
|
||||
}
|
||||
'DD' {
|
||||
sb.write_string('${t.day:02}')
|
||||
}
|
||||
'Do' {
|
||||
sb.write_string(ordinal_suffix(t.day))
|
||||
}
|
||||
'DDD' {
|
||||
sb.write_string((t.year_day()).str())
|
||||
}
|
||||
'DDDD' {
|
||||
sb.write_string('${t.year_day():03}')
|
||||
}
|
||||
'DDDo' {
|
||||
sb.write_string(ordinal_suffix(t.year_day()))
|
||||
}
|
||||
'd' {
|
||||
sb.write_string('${t.day_of_week() % 7}')
|
||||
}
|
||||
'dd' {
|
||||
sb.write_string(long_days[iclamp(0, t.day_of_week() - 1, 6)][0..2])
|
||||
}
|
||||
'ddd' {
|
||||
sb.write_string(long_days[iclamp(0, t.day_of_week() - 1, 6)][0..3])
|
||||
}
|
||||
'dddd' {
|
||||
sb.write_string(long_days[iclamp(0, t.day_of_week() - 1, 6)])
|
||||
}
|
||||
'YY' {
|
||||
sb.write_string(t.year.str()#[2..4])
|
||||
}
|
||||
'YYYY' {
|
||||
sb.write_string(t.year.str())
|
||||
}
|
||||
'H' {
|
||||
sb.write_string(t.hour.str())
|
||||
}
|
||||
'HH' {
|
||||
sb.write_string('${t.hour:02}')
|
||||
}
|
||||
'h' {
|
||||
h := (t.hour + 11) % 12 + 1
|
||||
sb.write_string(h.str())
|
||||
}
|
||||
'hh' {
|
||||
h := (t.hour + 11) % 12 + 1
|
||||
sb.write_string('${h:02}')
|
||||
}
|
||||
'i' {
|
||||
h := if t.hour > 12 { t.hour - 12 } else { t.hour }
|
||||
sb.write_string(h.str())
|
||||
}
|
||||
'ii' {
|
||||
h := if t.hour > 12 { t.hour - 12 } else { t.hour }
|
||||
sb.write_string('${h:02}')
|
||||
}
|
||||
'm' {
|
||||
sb.write_string(t.minute.str())
|
||||
}
|
||||
'mm' {
|
||||
sb.write_string('${t.minute:02}')
|
||||
}
|
||||
's' {
|
||||
sb.write_string(t.second.str())
|
||||
}
|
||||
'ss' {
|
||||
sb.write_string('${t.second:02}')
|
||||
}
|
||||
'k' {
|
||||
h := if t.hour == 0 { 24 } else { t.hour }
|
||||
sb.write_string(h.str())
|
||||
}
|
||||
'kk' {
|
||||
h := if t.hour == 0 { 24 } else { t.hour }
|
||||
sb.write_string('${h:02}')
|
||||
}
|
||||
'w' {
|
||||
sb.write_string('${t.week_of_year():.0}')
|
||||
}
|
||||
'ww' {
|
||||
sb.write_string('${t.week_of_year():02.0}')
|
||||
}
|
||||
'wo' {
|
||||
sb.write_string(ordinal_suffix(t.week_of_year()))
|
||||
}
|
||||
'Q' {
|
||||
sb.write_string('${(t.month - 1) / 3 + 1}')
|
||||
}
|
||||
'QQ' {
|
||||
sb.write_string('${(t.month - 1) / 3 + 1:02}')
|
||||
}
|
||||
'Qo' {
|
||||
sb.write_string(ordinal_suffix((t.month - 1) / 3 + 1))
|
||||
}
|
||||
'c' {
|
||||
sb.write_string('${t.day_of_week()}')
|
||||
}
|
||||
'N' {
|
||||
// TODO: integrate BC
|
||||
sb.write_string('AD')
|
||||
}
|
||||
'NN' {
|
||||
// TODO: integrate Before Christ
|
||||
sb.write_string('Anno Domini')
|
||||
}
|
||||
'Z' {
|
||||
mut hours := offset() / seconds_per_hour
|
||||
if hours >= 0 {
|
||||
sb.write_string('+${hours}')
|
||||
} else {
|
||||
hours = -hours
|
||||
sb.write_string('-${hours}')
|
||||
}
|
||||
}
|
||||
'ZZ' {
|
||||
// TODO: update if minute differs?
|
||||
mut hours := offset() / seconds_per_hour
|
||||
if hours >= 0 {
|
||||
sb.write_string('+${hours:02}00')
|
||||
} else {
|
||||
hours = -hours
|
||||
sb.write_string('-${hours:02}00')
|
||||
}
|
||||
}
|
||||
'ZZZ' {
|
||||
// TODO: update if minute differs?
|
||||
mut hours := offset() / seconds_per_hour
|
||||
if hours >= 0 {
|
||||
sb.write_string('+${hours:02}:00')
|
||||
} else {
|
||||
hours = -hours
|
||||
sb.write_string('-${hours:02}:00')
|
||||
}
|
||||
}
|
||||
'a' {
|
||||
if t.hour < 12 {
|
||||
sb.write_string('am')
|
||||
} else {
|
||||
sb.write_string('pm')
|
||||
}
|
||||
}
|
||||
'A' {
|
||||
if t.hour < 12 {
|
||||
sb.write_string('AM')
|
||||
} else {
|
||||
sb.write_string('PM')
|
||||
}
|
||||
}
|
||||
else {
|
||||
sb.write_string(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.str()
|
||||
}
|
||||
|
||||
// clean returns a date string in a clean form.
|
||||
// It has the 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
|
||||
@[deprecated: 'use `custom_format()` or `get_fmt_*()` instead']
|
||||
@[deprecated_after: '2026-09-30']
|
||||
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 clean form.
|
||||
// It has the 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
|
||||
@[deprecated: 'use `custom_format()` or `get_fmt_*()` instead']
|
||||
@[deprecated_after: '2026-09-30']
|
||||
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.nanosecond / 1_000_000):03d}' }
|
||||
.hhmmss24_micro { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.nanosecond / 1_000):06d}' }
|
||||
.hhmmss24_nano { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${t.nanosecond: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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// utc_string returns a date string in the legacy UTC cookie/header format.
|
||||
@[deprecated: 'use `http_header_string()` instead']
|
||||
@[deprecated_after: '2026-09-30']
|
||||
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
|
||||
}
|
||||
|
||||
// http_header_string returns a date string in the format used in HTTP headers, as defined in RFC 2616.
|
||||
// e.g. "Sun, 06 Nov 1994 08:49:37 GMT"
|
||||
@[manualfree]
|
||||
pub fn (t Time) http_header_string() string {
|
||||
mut buf := []u8{cap: 29}
|
||||
t.push_to_http_header(mut buf)
|
||||
|
||||
return buf.bytestr()
|
||||
}
|
||||
|
||||
// push_to_http_header returns a date string in the format used in HTTP headers, as defined in RFC 2616.
|
||||
// e.g. "Sun, 06 Nov 1994 08:49:37 GMT"
|
||||
pub fn (t Time) push_to_http_header(mut buffer []u8) {
|
||||
day_str := t.weekday_str()
|
||||
month_str := t.smonth()
|
||||
|
||||
mut buf := [day_str[0], day_str[1], day_str[2], `,`, ` `, `0`, `0`, ` `, month_str[0], month_str[1],
|
||||
month_str[2], ` `, `0`, `0`, `0`, `0`, ` `, `0`, `0`, `:`, `0`, `0`, `:`, `0`, `0`, ` `,
|
||||
`G`, `M`, `T`]!
|
||||
unsafe {
|
||||
int_to_ptr_byte_array_no_pad(t.day, &buf[5], 2)
|
||||
int_to_ptr_byte_array_no_pad(t.year, &buf[12], 4)
|
||||
int_to_ptr_byte_array_no_pad(t.hour, &buf[17], 2)
|
||||
int_to_ptr_byte_array_no_pad(t.minute, &buf[20], 2)
|
||||
int_to_ptr_byte_array_no_pad(t.second, &buf[23], 2)
|
||||
}
|
||||
unsafe {
|
||||
buffer.push_many(&buf[0], buf.len)
|
||||
}
|
||||
}
|
||||
|
||||
// mceil returns the least integer value greater than or equal to x.
|
||||
fn mceil(x f64) f64 {
|
||||
if x > 0 {
|
||||
return 1 + int(x)
|
||||
}
|
||||
if x < 0 {
|
||||
return -int(-x)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
module time
|
||||
|
||||
// from_json_string implements a custom decoder for json2 (unix)
|
||||
pub fn (mut t Time) from_json_number(raw_number string) ! {
|
||||
t = unix(raw_number.i64())
|
||||
}
|
||||
|
||||
// from_json_string implements a custom decoder for json2 (iso8601/rfc3339/unix)
|
||||
pub fn (mut t Time) from_json_string(raw_string string) ! {
|
||||
is_iso8601 := raw_string[4] == `-` && raw_string[7] == `-`
|
||||
if is_iso8601 {
|
||||
t = parse_iso8601(raw_string)!
|
||||
return
|
||||
}
|
||||
|
||||
is_rfc3339 := raw_string.len == 24 && raw_string[23] == `Z` && raw_string[10] == `T`
|
||||
if is_rfc3339 {
|
||||
t = parse_rfc3339(raw_string)!
|
||||
return
|
||||
}
|
||||
|
||||
mut is_unix_timestamp := true
|
||||
for c in raw_string {
|
||||
if c == `-` || (c >= `0` && c <= `9`) {
|
||||
continue
|
||||
}
|
||||
is_unix_timestamp = false
|
||||
break
|
||||
}
|
||||
if is_unix_timestamp {
|
||||
t = unix(raw_string.i64())
|
||||
return
|
||||
}
|
||||
|
||||
return error('Expected iso8601/rfc3339/unix time but got: ${raw_string}')
|
||||
}
|
||||
|
||||
// to_json implements a custom encoder for json2 (rfc3339)
|
||||
pub fn (t Time) to_json() string {
|
||||
return '"' + t.format_rfc3339() + '"'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
module misc
|
||||
|
||||
import rand
|
||||
import time
|
||||
|
||||
const start_time_unix = time.now().unix()
|
||||
|
||||
// random returns a random time struct in *the past*.
|
||||
pub fn random() time.Time {
|
||||
return time.unix(int(rand.i64n(start_time_unix) or { 0 }))
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
module time
|
||||
|
||||
// operator `==` returns true if provided time is equal to time
|
||||
@[inline]
|
||||
pub fn (t1 Time) == (t2 Time) bool {
|
||||
return t1.nanosecond == t2.nanosecond && t1.is_local == t2.is_local
|
||||
&& t1.local_unix() == t2.local_unix()
|
||||
}
|
||||
|
||||
// operator `<` returns true if provided time is less than time
|
||||
@[inline]
|
||||
pub fn (t1 Time) < (t2 Time) bool {
|
||||
t1u := t1.unix()
|
||||
t2u := t2.unix()
|
||||
return t1u < t2u || (t1u == t2u && t1.nanosecond < t2.nanosecond)
|
||||
}
|
||||
|
||||
// Time subtract using operator overloading.
|
||||
@[inline]
|
||||
pub fn (lhs Time) - (rhs Time) Duration {
|
||||
// lhs.unix * 1_000_000_000 + i64(lhs.nanosecond) will overflow i64, for years > 3000 .
|
||||
// Doing the diff first, and *then* multiplying by `second`, is less likely to overflow,
|
||||
// since lhs and rhs will be likely close to each other.
|
||||
unixs := i64(lhs.unix() - rhs.unix()) * second
|
||||
nanos := lhs.nanosecond - rhs.nanosecond
|
||||
return unixs + nanos
|
||||
}
|
||||
@@ -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{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 100
|
||||
})
|
||||
t2 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 100
|
||||
})
|
||||
assert t1 == t2
|
||||
}
|
||||
|
||||
fn test_time1_should_not_be_same_as_time2() {
|
||||
t1 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 100
|
||||
})
|
||||
// Difference is one nanosecond
|
||||
t2 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 101
|
||||
})
|
||||
t3 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 0
|
||||
})
|
||||
// Difference is one second
|
||||
t4 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 4
|
||||
nanosecond: 0
|
||||
})
|
||||
assert t1 != t2
|
||||
assert t3 != t4
|
||||
}
|
||||
|
||||
fn test_time1_should_be_greater_than_time2() {
|
||||
t1 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 102
|
||||
})
|
||||
// Difference is one nanosecond
|
||||
t2 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 101
|
||||
})
|
||||
t3 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 5
|
||||
nanosecond: 0
|
||||
})
|
||||
// Difference is one second
|
||||
t4 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 4
|
||||
nanosecond: 0
|
||||
})
|
||||
assert t1 > t2
|
||||
assert t3 > t4
|
||||
}
|
||||
|
||||
fn test_time2_should_be_less_than_time1() {
|
||||
t1 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 102
|
||||
})
|
||||
// Difference is one nanosecond
|
||||
t2 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 101
|
||||
})
|
||||
t3 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 0
|
||||
})
|
||||
// Difference is one second
|
||||
t4 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 2
|
||||
nanosecond: 0
|
||||
})
|
||||
assert t2 < t1
|
||||
assert t4 < t3
|
||||
}
|
||||
|
||||
fn test_time1_should_be_greater_or_equal_to_time2_when_gt() {
|
||||
t1 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 102
|
||||
})
|
||||
// Difference is one nanosecond
|
||||
t2 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 101
|
||||
})
|
||||
t3 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 5
|
||||
nanosecond: 0
|
||||
})
|
||||
// Difference is one second
|
||||
t4 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 4
|
||||
nanosecond: 0
|
||||
})
|
||||
assert t1 >= t2
|
||||
assert t3 >= t4
|
||||
}
|
||||
|
||||
fn test_time1_should_be_greater_or_equal_to_time2_when_eq() {
|
||||
t1 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 100
|
||||
})
|
||||
// Difference is one nanosecond
|
||||
t2 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 100
|
||||
})
|
||||
t3 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 0
|
||||
})
|
||||
// Difference is one second
|
||||
t4 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 0
|
||||
})
|
||||
assert t1 >= t2
|
||||
assert t3 >= t4
|
||||
}
|
||||
|
||||
fn test_time1_should_be_less_or_equal_to_time2_when_lt() {
|
||||
t1 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 100
|
||||
})
|
||||
// Difference is one nanosecond
|
||||
t2 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 101
|
||||
})
|
||||
t3 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 0
|
||||
})
|
||||
// Difference is one second
|
||||
t4 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 4
|
||||
nanosecond: 0
|
||||
})
|
||||
assert t1 <= t2
|
||||
assert t3 <= t4
|
||||
}
|
||||
|
||||
fn test_time1_should_be_less_or_equal_to_time2_when_eq() {
|
||||
t1 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 100
|
||||
})
|
||||
// Difference is one nanosecond
|
||||
t2 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 100
|
||||
})
|
||||
t3 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 0
|
||||
})
|
||||
// Difference is one second
|
||||
t4 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 0
|
||||
})
|
||||
assert t1 <= t2
|
||||
assert t3 <= t4
|
||||
}
|
||||
|
||||
fn test_time2_copied_from_time1_should_be_equal() {
|
||||
t1 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 100
|
||||
})
|
||||
t2 := new(t1)
|
||||
assert t2 == t1
|
||||
}
|
||||
|
||||
fn test_subtract() {
|
||||
d_seconds := 3
|
||||
d_nanoseconds := 13
|
||||
duration := i64(d_seconds) * second + d_nanoseconds * nanosecond
|
||||
t1 := new(Time{
|
||||
year: 2000
|
||||
month: 5
|
||||
day: 10
|
||||
hour: 22
|
||||
minute: 11
|
||||
second: 3
|
||||
nanosecond: 100
|
||||
})
|
||||
t2 := unix_nanosecond(i64(t1.unix) + d_seconds, t1.nanosecond + d_nanoseconds)
|
||||
d1 := t2 - t1
|
||||
d2 := t1 - t2
|
||||
assert d1 > 0
|
||||
assert d1 == duration
|
||||
assert d2 < 0
|
||||
assert d2 == -duration
|
||||
}
|
||||
@@ -0,0 +1,604 @@
|
||||
// Copyright (c) 2019-2024 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
|
||||
|
||||
import strconv
|
||||
|
||||
const date_format_buffer = [u8(`0`), `0`, `0`, `0`, `-`, `0`, `0`, `-`, `0`, `0`]!
|
||||
const time_format_buffer = [u8(`0`), `0`, `:`, `0`, `0`, `:`, `0`, `0`]!
|
||||
|
||||
fn validate_time_bounds(hour int, minute int, second int, nanosecond int) ! {
|
||||
if hour < 0 || hour > 23 {
|
||||
return error('invalid hour: ${hour}')
|
||||
}
|
||||
if minute < 0 || minute > 59 {
|
||||
return error('invalid minute: ${minute}')
|
||||
}
|
||||
if second < 0 || second > 59 {
|
||||
return error('invalid second: ${second}')
|
||||
}
|
||||
if nanosecond < 0 || nanosecond > 1_000_000_000 {
|
||||
return error('invalid nanosecond: ${nanosecond}')
|
||||
}
|
||||
}
|
||||
|
||||
fn check_and_extract_time(s string) !(int, int, int, int) {
|
||||
mut hour_ := 0
|
||||
mut minute_ := 0
|
||||
mut second_ := 0
|
||||
mut nanosecond_ := 0
|
||||
|
||||
// Check if the string start in the format "HH:MM:SS"
|
||||
for i := 0; i < time_format_buffer.len; i++ {
|
||||
if time_format_buffer[i] == u8(`0`) {
|
||||
if s[i] < u8(`0`) || s[i] > u8(`9`) {
|
||||
return error('`HH:MM:SS` match error: expected digit, not `${s[i]}` in position ${i}')
|
||||
} else {
|
||||
if i < 2 {
|
||||
hour_ = hour_ * 10 + (s[i] - u8(`0`))
|
||||
} else if i < 5 {
|
||||
minute_ = minute_ * 10 + (s[i] - u8(`0`))
|
||||
} else {
|
||||
second_ = second_ * 10 + (s[i] - u8(`0`))
|
||||
}
|
||||
}
|
||||
} else if time_format_buffer[i] != s[i] {
|
||||
return error('time separator error: expected `:`, not `${[s[i]].bytestr()}` in position ${i}')
|
||||
}
|
||||
}
|
||||
|
||||
if s.len == time_format_buffer.len + 1 {
|
||||
if s[time_format_buffer.len] !in [u8(`Z`), `z`] {
|
||||
return error('timezone error: expected "Z" or "z" at the end of the string')
|
||||
}
|
||||
validate_time_bounds(hour_, minute_, second_, nanosecond_)!
|
||||
return hour_, minute_, second_, nanosecond_
|
||||
}
|
||||
|
||||
if s.len < time_format_buffer.len + 1 {
|
||||
return error('datetime string is too short')
|
||||
}
|
||||
|
||||
if s[time_format_buffer.len] == u8(`.`) {
|
||||
// Check if the string contains the nanoseconds part after the time part
|
||||
if s.len < time_format_buffer.len + 1 {
|
||||
return error('datetime string is too short')
|
||||
}
|
||||
// Check if the string start in the format ".NNNNNNNNN"
|
||||
mut nanosecond_digits := 0
|
||||
for i := time_format_buffer.len + 1; i < s.len; i++ {
|
||||
if s[i] < u8(`0`) || s[i] > u8(`9`) {
|
||||
if s[i] in [u8(`Z`), `z`] {
|
||||
if i != s.len - 1 {
|
||||
return error('timezone error: "Z" or "z" can only be at the end of the string')
|
||||
}
|
||||
break
|
||||
} else if s[i] in [u8(`+`), `-`] {
|
||||
break
|
||||
}
|
||||
return error('nanoseconds error: expected digit, not `${s[i]}` in position ${i}')
|
||||
}
|
||||
if !(i >= time_format_buffer.len + 1 + 9) {
|
||||
// nanoseconds limit is 9 digits
|
||||
nanosecond_ = nanosecond_ * 10 + (s[i] - u8(`0`))
|
||||
nanosecond_digits++
|
||||
}
|
||||
}
|
||||
if nanosecond_digits < 9 {
|
||||
for i := 0; i < 9 - nanosecond_digits; i++ {
|
||||
nanosecond_ *= 10
|
||||
}
|
||||
}
|
||||
}
|
||||
validate_time_bounds(hour_, minute_, second_, nanosecond_)!
|
||||
return hour_, minute_, second_, nanosecond_
|
||||
}
|
||||
|
||||
fn check_and_extract_date(s string) !(int, int, int) {
|
||||
mut year := 0
|
||||
mut month := 0
|
||||
mut day := 0
|
||||
// Check if the string start in the format "YYYY-MM-DD"
|
||||
for i := 0; i < date_format_buffer.len; i++ {
|
||||
if date_format_buffer[i] == u8(`0`) {
|
||||
if s[i] < u8(`0`) || s[i] > u8(`9`) {
|
||||
return error('`YYYY-MM-DD` match error: expected digit, not `${s[i]}` in position ${i}')
|
||||
} else {
|
||||
if i < 4 {
|
||||
year = year * 10 + (s[i] - u8(`0`))
|
||||
} else if i < 7 {
|
||||
month = month * 10 + (s[i] - u8(`0`))
|
||||
} else {
|
||||
day = day * 10 + (s[i] - u8(`0`))
|
||||
}
|
||||
}
|
||||
} else if date_format_buffer[i] != s[i] {
|
||||
return error('date separator error:expected "${date_format_buffer[i]}", not `${s[i]}` in position ${i}')
|
||||
}
|
||||
}
|
||||
if month < 1 || month > 12 {
|
||||
return error('date error: invalid month ${month}')
|
||||
}
|
||||
if day < 1 || day > 31 {
|
||||
return error('date error: invalid day ${day}')
|
||||
}
|
||||
return year, month, day
|
||||
}
|
||||
|
||||
// Convert header string formatted as RFC 2616 to Time.
|
||||
pub fn parse_http_header_string(s string) !Time {
|
||||
return parse_rfc2616(s)
|
||||
}
|
||||
|
||||
// parse_rfc2616 returns the time from a date string in RFC 3339 datetime format.
|
||||
// Wed, 06 Nov 2024 08:49:37 GMT ; RFC 822, updated by RFC 1123
|
||||
// Wednesday, 06-Nov-24 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
|
||||
// Wed Nov 6 08:49:37 2024 ; ANSI C's asctime() format
|
||||
pub fn parse_rfc2616(s string) !Time {
|
||||
if s == '' {
|
||||
return error_invalid_time(0, 'datetime string is empty')
|
||||
}
|
||||
|
||||
// Remove or Replace unwanted tokens.
|
||||
rmv := ['GMT', '', 'Monday', '', 'Tuesday', '', 'Wednesday', '', 'Thursday', '', 'Friday',
|
||||
'', 'Saturday', '', 'Sunday', '', 'Mon', '', 'Tue', '', 'Wed', '', 'Thu', '', 'Fri', '',
|
||||
'Sat', '', 'Sun', '', '-', ' ', ',', '']
|
||||
|
||||
mut f := s.replace_each(rmv)
|
||||
f = remove_consecutive_spaces(f)
|
||||
|
||||
if r := parse_format(f, 'DD MMM YYYY HH:mm:ss') {
|
||||
return r
|
||||
}
|
||||
|
||||
// parse_format maps YY to this century (94 maps to 2094, 20 to 2020).
|
||||
// if parsed year > current year, the date belongs to previous century.
|
||||
if r := parse_format(f, 'DD MMM YY HH:mm:ss') {
|
||||
return if r.year > now().year {
|
||||
r.add_days(-(days_per_100_years + 1))
|
||||
} else {
|
||||
r
|
||||
}
|
||||
}
|
||||
if r := parse_format(f, 'MMM D HH:mm:ss YYYY') {
|
||||
return r
|
||||
}
|
||||
return error('unable to parse date: "${f}"')
|
||||
}
|
||||
|
||||
// Remove consecutive spaces, only keep one.
|
||||
fn remove_consecutive_spaces(s string) string {
|
||||
mut t := s.trim_space()
|
||||
mut r := ''
|
||||
mut sp := false
|
||||
|
||||
for c in t {
|
||||
if c == u8(` `) {
|
||||
if !sp {
|
||||
r += ' '
|
||||
sp = true
|
||||
}
|
||||
} else {
|
||||
r += c.ascii_str()
|
||||
sp = false
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// parse_rfc3339 returns the time from a date string in RFC 3339 datetime format.
|
||||
// See also https://ijmacd.github.io/rfc3339-iso8601/ for a visual reference of
|
||||
// the differences between ISO-8601 and RFC 3339.
|
||||
pub fn parse_rfc3339(s string) !Time {
|
||||
if s == '' {
|
||||
return error_invalid_time(0, 'datetime string is empty')
|
||||
}
|
||||
|
||||
if s.len < time_format_buffer.len {
|
||||
return error('string is too short to parse')
|
||||
}
|
||||
|
||||
mut year, mut month, mut day := 0, 0, 0
|
||||
mut hour_, mut minute_, mut second_, mut nanosecond_ := 0, 0, 0, 0
|
||||
|
||||
is_time := if s.len >= time_format_buffer.len {
|
||||
s[2] == u8(`:`) && s[5] == u8(`:`)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
if is_time {
|
||||
return error('missing date part of RFC 3339')
|
||||
}
|
||||
|
||||
is_date := if s.len >= date_format_buffer.len {
|
||||
s[4] == u8(`-`) && s[7] == u8(`-`)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
if is_date {
|
||||
year, month, day = check_and_extract_date(s)!
|
||||
}
|
||||
if s.len <= date_format_buffer.len {
|
||||
return error('date-time too short to parse')
|
||||
}
|
||||
if s[10] !in [u8(`T`), `t`, ` `] {
|
||||
return error('invalid date-time separator:${s[10].ascii_str()}')
|
||||
}
|
||||
|
||||
is_datetime := if s.len >= date_format_buffer.len + 1 + time_format_buffer.len + 1 {
|
||||
is_date
|
||||
} else {
|
||||
false
|
||||
}
|
||||
if is_datetime {
|
||||
// year, month, day := check_and_extract_date(s)!
|
||||
hour_, minute_, second_, nanosecond_ =
|
||||
check_and_extract_time(s[date_format_buffer.len + 1..])!
|
||||
}
|
||||
|
||||
mut timezone_start_position := 0
|
||||
|
||||
if is_datetime || is_time {
|
||||
timezone_start_position = date_format_buffer.len + 1 + time_format_buffer.len
|
||||
if s[timezone_start_position] == u8(`.`) {
|
||||
timezone_start_position++
|
||||
|
||||
for s[timezone_start_position] !in [u8(`Z`), `z`, `+`, `-`] {
|
||||
timezone_start_position++
|
||||
if timezone_start_position == s.len {
|
||||
return error('timezone error: expected "Z" or "z" or "+" or "-" in position ${timezone_start_position}, not "${[
|
||||
s[s.len - 1],
|
||||
].bytestr()}"')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos := date_format_buffer.len + time_format_buffer.len + 1
|
||||
if pos >= s.len {
|
||||
return error('timezone error: datetime string is too short')
|
||||
}
|
||||
if s[date_format_buffer.len + time_format_buffer.len + 1] !in [u8(`Z`), `z`, `+`, `-`, `.`] {
|
||||
// RFC 3339 needs a timezone
|
||||
return error('timezone error: expected "Z" or "z" or "+" or "-" in position ${
|
||||
date_format_buffer.len + time_format_buffer.len + 1}, not "${[
|
||||
s[date_format_buffer.len + time_format_buffer.len + 1],
|
||||
].bytestr()}"')
|
||||
} else {
|
||||
if s[s.len - 1] in [u8(`Z`), `z`] {
|
||||
return new(Time{
|
||||
year: year
|
||||
month: month
|
||||
day: day
|
||||
hour: hour_
|
||||
minute: minute_
|
||||
second: second_
|
||||
nanosecond: nanosecond_
|
||||
is_local: false
|
||||
})
|
||||
} else {
|
||||
// Check if the string contains the timezone part after the time part +00:00
|
||||
if s.len < date_format_buffer.len + 1 + time_format_buffer.len + 6 {
|
||||
return error('datetime string is too short')
|
||||
}
|
||||
if s[s.len - 3] != u8(`:`) {
|
||||
return error('timezone separator error: expected ":", not `${[
|
||||
s[date_format_buffer.len + time_format_buffer.len + 3],
|
||||
].bytestr()}` in position ${date_format_buffer.len + time_format_buffer.len + 3}')
|
||||
}
|
||||
|
||||
// Check if it is UTC time
|
||||
if unsafe { vmemcmp(s.str + s.len - 5, c'00:00', 5) == 0 } {
|
||||
return new(Time{
|
||||
year: year
|
||||
month: month
|
||||
day: day
|
||||
hour: hour_
|
||||
minute: minute_
|
||||
second: second_
|
||||
nanosecond: nanosecond_
|
||||
is_local: false
|
||||
})
|
||||
}
|
||||
|
||||
is_negative := s[s.len - 6] == u8(`-`)
|
||||
|
||||
// To local time using the offset to add_seconds
|
||||
mut offset_in_minutes := 0
|
||||
mut offset_in_hours := 0
|
||||
// offset hours
|
||||
for i := 0; i < 2; i++ {
|
||||
offset_in_hours = offset_in_hours * 10 + (s[s.len - 5 + i] - u8(`0`))
|
||||
}
|
||||
|
||||
// offset minutes
|
||||
for i := 0; i < 2; i++ {
|
||||
offset_in_minutes = offset_in_minutes * 10 + (s[s.len - 2 + i] - u8(`0`))
|
||||
}
|
||||
|
||||
offset_in_minutes += offset_in_hours * 60
|
||||
|
||||
if !is_negative {
|
||||
offset_in_minutes *= -1
|
||||
}
|
||||
|
||||
mut time_to_be_returned := new(Time{
|
||||
year: year
|
||||
month: month
|
||||
day: day
|
||||
hour: hour_
|
||||
minute: minute_
|
||||
second: second_
|
||||
nanosecond: nanosecond_
|
||||
is_local: false
|
||||
})
|
||||
|
||||
time_to_be_returned = time_to_be_returned.add_seconds(offset_in_minutes * 60)
|
||||
|
||||
return time_to_be_returned
|
||||
}
|
||||
}
|
||||
|
||||
return error_invalid_time(9, 'malformed date')
|
||||
}
|
||||
|
||||
// parse returns the time from a date string in "YYYY-MM-DD HH:mm:ss" format.
|
||||
pub fn parse(s string) !Time {
|
||||
if s == '' {
|
||||
return error_invalid_time(0, 'datetime string is empty')
|
||||
}
|
||||
pos := s.index(' ') or {
|
||||
return error_invalid_time(1, 'string has no space between date and time')
|
||||
}
|
||||
symd := s[..pos]
|
||||
ymd := symd.split('-')
|
||||
if ymd.len != 3 {
|
||||
return error_invalid_time(2, 'date must be in the form of y-m-d')
|
||||
}
|
||||
shms := s[pos..]
|
||||
hms := shms.split(':')
|
||||
if hms.len != 3 {
|
||||
return error_invalid_time(9, 'time must be in the form of H:i:s')
|
||||
}
|
||||
hour_ := hms[0][1..]
|
||||
minute_ := hms[1]
|
||||
second_ := hms[2]
|
||||
|
||||
iyear := strconv.atoi(ymd[0]) or {
|
||||
return error_invalid_time(0, 'invalid year format: ${ymd[0]}')
|
||||
}
|
||||
imonth := strconv.atoi(ymd[1]) or {
|
||||
return error_invalid_time(0, 'invalid month format: ${ymd[1]}')
|
||||
}
|
||||
iday := strconv.atoi(ymd[2]) or {
|
||||
return error_invalid_time(0, 'invalid day format: ${ymd[2]}')
|
||||
}
|
||||
ihour := strconv.atoi(hour_) or {
|
||||
return error_invalid_time(0, 'invalid hour format: ${hour_}')
|
||||
}
|
||||
iminute := strconv.atoi(minute_) or {
|
||||
return error_invalid_time(0, 'invalid minute format: ${minute_}')
|
||||
}
|
||||
isecond := strconv.atoi(second_) or {
|
||||
return error_invalid_time(0, 'invalid second format: ${second_}')
|
||||
}
|
||||
|
||||
// eprintln('>> iyear: ${iyear} | imonth: ${imonth} | iday: ${iday} | ihour: ${ihour} | iminute: ${iminute} | isecond: ${isecond}')
|
||||
if iyear > 9999 || iyear < -9999 {
|
||||
return error_invalid_time(3, 'year must be between -10000 and 10000')
|
||||
}
|
||||
if imonth > 12 || imonth < 1 {
|
||||
return error_invalid_time(4, 'month must be between 1 and 12')
|
||||
}
|
||||
if iday > 31 || iday < 1 {
|
||||
return error_invalid_time(5, 'day must be between 1 and 31')
|
||||
}
|
||||
if ihour > 23 || ihour < 0 {
|
||||
return error_invalid_time(6, 'hours must be between 0 and 24')
|
||||
}
|
||||
if iminute > 59 || iminute < 0 {
|
||||
return error_invalid_time(7, 'minutes must be between 0 and 60')
|
||||
}
|
||||
if isecond > 59 || isecond < 0 {
|
||||
return error_invalid_time(8, 'seconds must be between 0 and 60')
|
||||
}
|
||||
res := new(Time{
|
||||
year: iyear
|
||||
month: imonth
|
||||
day: iday
|
||||
hour: ihour
|
||||
minute: iminute
|
||||
second: isecond
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// parse_format parses the string `s`, as a custom `format`, containing the following specifiers:
|
||||
//
|
||||
// |Category| Format | Description |
|
||||
// |:----- | :----- | :---------- |
|
||||
// |Year | YYYY | 4 digit year, 0000..9999 |
|
||||
// | | YY | 2 digit year, 00..99 |
|
||||
// |Month | M | month, 1..12 |
|
||||
// | | MM | month, 2 digits, 01..12 |
|
||||
// | | MMM | month, three letters, Jan..Dec |
|
||||
// | | MMMM | name of month |
|
||||
// |Day | D | day of the month, 1..31 |
|
||||
// | | DD | day of the month, 01..31 |
|
||||
// | | d | day of week, 0..6 |
|
||||
// | | c | day of week, 1..7 |
|
||||
// | | dd | day of week, Su..Sa |
|
||||
// | | ddd | day of week, Sun..Sat |
|
||||
// | | dddd | day of week, Sunday..Saturday |
|
||||
// |Hour | H | hour, 0..23 |
|
||||
// | | HH | hour, 00..23 |
|
||||
// | | h | hour, 0..23 |
|
||||
// | | hh | hour, 0..23 |
|
||||
// | | k | hour, 0..23 |
|
||||
// | | kk | hour, 0..23 |
|
||||
// |Minute | m | minute, 0..59 |
|
||||
// | | mm | minute, 0..59 |
|
||||
// |Second | s | second, 0..59 |
|
||||
// | | ss | second, 0..59 |
|
||||
pub fn parse_format(s string, format string) !Time {
|
||||
if s == '' {
|
||||
return error_invalid_time(0, 'datetime string is empty')
|
||||
}
|
||||
mut p := DateTimeParser{
|
||||
datetime: s
|
||||
format: format
|
||||
}
|
||||
return p.parse()
|
||||
}
|
||||
|
||||
// parse_iso8601 parses the ISO 8601 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.
|
||||
// Both can be +/- HH:mm .
|
||||
// See https://en.wikipedia.org/wiki/ISO_8601 .
|
||||
// Remarks: not all of ISO 8601 is supported; checks and support for leapseconds should be added.
|
||||
pub fn parse_iso8601(s string) !Time {
|
||||
if s == '' {
|
||||
return error_invalid_time(0, 'datetime string is empty')
|
||||
}
|
||||
t_i := s.index_('T')
|
||||
parts := if t_i != -1 { [s[..t_i], s[t_i + 1..]] } else { s.split(' ') }
|
||||
if !(parts.len == 1 || parts.len == 2) {
|
||||
return error_invalid_time(12, 'malformed date')
|
||||
}
|
||||
year, month, day := parse_iso8601_date(parts[0])!
|
||||
mut hour_, mut minute_, mut second_, mut microsecond_, mut nanosecond_, mut unix_offset, mut is_local_time := 0, 0, 0, 0, 0, i64(0), true
|
||||
if parts.len == 2 {
|
||||
hour_, minute_, second_, microsecond_, nanosecond_, unix_offset, is_local_time =
|
||||
parse_iso8601_time(parts[1])!
|
||||
}
|
||||
mut t := new(
|
||||
year: year
|
||||
month: month
|
||||
day: day
|
||||
hour: hour_
|
||||
minute: minute_
|
||||
second: second_
|
||||
nanosecond: nanosecond_
|
||||
)
|
||||
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 = unix_nanosecond(i64(unix_time), t.nanosecond)
|
||||
return t
|
||||
}
|
||||
|
||||
// parse_rfc2822 returns the time from a date string in RFC 2822 datetime format.
|
||||
pub fn parse_rfc2822(s string) !Time {
|
||||
if s == '' {
|
||||
return error_invalid_time(0, 'datetime string is empty')
|
||||
}
|
||||
fields := s.split(' ')
|
||||
if fields.len < 5 {
|
||||
return error_invalid_time(1, 'datetime string must have 5 components, has: ${fields.len}')
|
||||
}
|
||||
pos := months_string.index(fields[2]) or {
|
||||
return error_invalid_time(2, 'invalid month format')
|
||||
}
|
||||
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 -----
|
||||
fn parse_iso8601_date(s string) !(int, int, int) {
|
||||
year, month, day, dummy := 0, 0, 0, u8(0)
|
||||
count := unsafe { C.sscanf(&char(s.str), c'%4d-%2d-%2d%c', &year, &month, &day, &dummy) }
|
||||
if count != 3 {
|
||||
return error_invalid_time(10, 'datetime string must have 3 components, but has ${count}')
|
||||
}
|
||||
if year > 9999 {
|
||||
return error_invalid_time(13, 'year must be smaller than 10000')
|
||||
}
|
||||
if month > 12 {
|
||||
return error_invalid_time(14, 'month must be smaller than 12')
|
||||
}
|
||||
if day > 31 {
|
||||
return error_invalid_time(15, 'day must be smaller than 31')
|
||||
}
|
||||
return year, month, day
|
||||
}
|
||||
|
||||
fn parse_iso8601_time(s string) !(int, int, int, int, int, i64, bool) {
|
||||
hour_ := 0
|
||||
minute_ := 0
|
||||
second_ := 0
|
||||
mut microsecond_ := 0
|
||||
mut nanosecond_ := 0
|
||||
plus_min_z := `a`
|
||||
offset_hour := 0
|
||||
offset_minute := 0
|
||||
mut count := 0
|
||||
count = unsafe {
|
||||
C.sscanf(&char(s.str), c'%2d:%2d:%2d.%9d%c', &hour_, &minute_, &second_, &nanosecond_,
|
||||
&char(&plus_min_z))
|
||||
}
|
||||
if count == 5 && plus_min_z == `Z` {
|
||||
// normalise the nanoseconds:
|
||||
mut ndigits := 0
|
||||
if mut pos := s.index('.') {
|
||||
pos++
|
||||
for ; pos < s.len && s[pos].is_digit(); pos++ {
|
||||
ndigits++
|
||||
}
|
||||
}
|
||||
for ndigits < 9 {
|
||||
nanosecond_ *= 10
|
||||
ndigits++
|
||||
}
|
||||
microsecond_ = nanosecond_ / 1000
|
||||
} else {
|
||||
count = unsafe {
|
||||
C.sscanf(&char(s.str), c'%2d:%2d:%2d.%9d%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_invalid_time(10, 'malformed date')
|
||||
}
|
||||
nanosecond_ = microsecond_ * 1000
|
||||
}
|
||||
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_invalid_time(11, 'malformed date')
|
||||
}
|
||||
if plus_min_z != `+` && plus_min_z != `-` && !is_utc && !is_local_time {
|
||||
return error_invalid_time(12, 'missing timezone')
|
||||
}
|
||||
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
|
||||
}
|
||||
// eprintln('parse_iso8601_time s: ${s} | hour_: ${hour_} | minute_: ${minute_} | second_: ${second_} | microsecond_: ${microsecond_} | nanosecond_: ${nanosecond_} | unix_offset: ${unix_offset} | is_local: ${is_local_time}')
|
||||
return hour_, minute_, second_, microsecond_, nanosecond_, unix_offset, is_local_time
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
module time
|
||||
|
||||
// parse returns the time from a date string.
|
||||
//
|
||||
// TODO(playX): JS Date expects iso8061 format of strings and other formats
|
||||
// are implementation dependent, 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
|
||||
}
|
||||
|
||||
// parse_iso8601 parses the ISO 8601 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.
|
||||
// Both can be +/- HH:mm .
|
||||
// See https://en.wikipedia.org/wiki/ISO_8601 .
|
||||
// Remarks: not all of ISO 8601 is supported; checks and support for leapseconds should be added.
|
||||
pub fn parse_iso8601(s string) !Time {
|
||||
return parse(s)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2019-2024 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
|
||||
|
||||
// TimeParseError represents a time parsing error.
|
||||
pub struct TimeParseError {
|
||||
Error
|
||||
code int
|
||||
message string
|
||||
}
|
||||
|
||||
// msg implements the `IError.msg()` method for `TimeParseError`.
|
||||
pub fn (err TimeParseError) msg() string {
|
||||
return 'Invalid time format code: ${err.code}, error: ${err.message}'
|
||||
}
|
||||
|
||||
fn error_invalid_time(code int, message string) IError {
|
||||
return TimeParseError{
|
||||
code: code
|
||||
message: message
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// vtest build: !msvc-windows && !sanitize-address-gcc && !sanitize-address-clang
|
||||
// vtest vflags: -autofree
|
||||
module time
|
||||
|
||||
fn test_parse_format_regression_21369_autofree() {
|
||||
for _ in 0 .. 1000 {
|
||||
t := parse_format('04 Oct 2023', 'DD MMM YYYY') or {
|
||||
panic('parse_format should succeed under -autofree')
|
||||
}
|
||||
assert t.year == 2023
|
||||
assert t.month == 10
|
||||
assert t.day == 4
|
||||
assert t.hour == 0
|
||||
assert t.minute == 0
|
||||
assert t.second == 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
import time
|
||||
|
||||
fn test_parse() {
|
||||
s := '2018-01-27 12:48:34'
|
||||
t := time.parse(s) or {
|
||||
assert false, '> failing format: ${s} | err: ${err}'
|
||||
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() {
|
||||
if x := time.parse('Invalid time string') {
|
||||
assert false
|
||||
}
|
||||
assert true
|
||||
|
||||
if x := time.parse('2020-02-02 02.20.02') {
|
||||
assert false
|
||||
}
|
||||
assert true
|
||||
}
|
||||
|
||||
fn test_parse_rfc2822() {
|
||||
s1 := 'Thu, 12 Dec 2019 06:07:45 GMT'
|
||||
t1 := time.parse_rfc2822(s1) or {
|
||||
assert false, '> failing format: ${s1} | err: ${err}'
|
||||
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, '> failing format: ${s2} | err: ${err}'
|
||||
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, 15959000],
|
||||
[2020, 6, 5, 15, 38, 6, 15959000],
|
||||
[2020, 6, 5, 13, 38, 6, 15959000],
|
||||
[2020, 6, 5, 17, 38, 6, 15959000],
|
||||
[2020, 11, 5, 15, 38, 6, 15959000],
|
||||
]
|
||||
for i, format in formats {
|
||||
t := time.parse_iso8601(format) or {
|
||||
assert false, '>>> failing format: ${format} | err: ${err}'
|
||||
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
|
||||
nanosecond := times[i][6]
|
||||
assert t.nanosecond == nanosecond
|
||||
}
|
||||
}
|
||||
|
||||
fn test_parse_iso8601_local() {
|
||||
format := '2020-06-05T15:38:06.015959'
|
||||
t := time.parse_iso8601(format) or {
|
||||
assert false, '> failing format: ${format} | err: ${err}'
|
||||
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.nanosecond == 15959_000
|
||||
}
|
||||
|
||||
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',
|
||||
'2020-06-32T15:38:06.015959',
|
||||
'2020-13-13T15:38:06.015959',
|
||||
]
|
||||
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, '> failing format: ${format} | err: ${err}'
|
||||
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.nanosecond == 0
|
||||
}
|
||||
|
||||
fn check_invalid_date(s string) {
|
||||
if date := time.parse(s) {
|
||||
assert false, 'invalid date: "${s}" => "${date}"'
|
||||
}
|
||||
assert true
|
||||
}
|
||||
|
||||
fn invalid_rfc3339(s string) string {
|
||||
if date := time.parse_rfc3339(s) {
|
||||
assert false, 'invalid date: "${s}" => "${date}"'
|
||||
} else {
|
||||
assert true
|
||||
return err.str()
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
fn test_invalid_dates_should_error_during_parse() {
|
||||
check_invalid_date('-99999-12-20 00:00:00')
|
||||
check_invalid_date('99999-12-20 00:00:00')
|
||||
|
||||
check_invalid_date('2008-00-20 00:00:00')
|
||||
check_invalid_date('2008-25-20 00:00:00')
|
||||
|
||||
check_invalid_date('2008-12-00 00:00:00')
|
||||
check_invalid_date('2008-12-32 00:00:00')
|
||||
|
||||
check_invalid_date('2008-12-01 30:00:00')
|
||||
check_invalid_date('2008-12-01 00:60:00')
|
||||
check_invalid_date('2008-12-01 00:01:60')
|
||||
}
|
||||
|
||||
fn test_parse_rfc3339() {
|
||||
pairs := [
|
||||
['2015-01-06T15:47:32.080254511Z', '2015-01-06 15:47:32.080254'],
|
||||
['2015-01-06T15:47:32.072697474Z', '2015-01-06 15:47:32.072697'],
|
||||
['2015-01-06T15:47:32.1234Z', '2015-01-06 15:47:32.123400'],
|
||||
['2015-01-06T15:47:32.001234Z', '2015-01-06 15:47:32.001234'],
|
||||
['2015-01-06T15:47:32Z', '2015-01-06 15:47:32.000000'],
|
||||
['2015-01-06T15:47:32+00:00', '2015-01-06 15:47:32.000000'],
|
||||
['2015-01-06T15:47:32-00:00', '2015-01-06 15:47:32.000000'],
|
||||
['2015-01-06T15:47:32-01:00', '2015-01-06 16:47:32.000000'],
|
||||
['2015-01-06T15:47:32+01:00', '2015-01-06 14:47:32.000000'],
|
||||
['2015-01-06T15:47:32-01:10', '2015-01-06 16:57:32.000000'],
|
||||
['2015-01-06T15:47:32+01:10', '2015-01-06 14:37:32.000000'],
|
||||
['2015-01-06T15:47:32.1234-00:00', '2015-01-06 15:47:32.123400'],
|
||||
['2015-01-06T15:47:32.1234+01:00', '2015-01-06 14:47:32.123400'],
|
||||
['2015-01-06T15:47:32.1234-01:00', '2015-01-06 16:47:32.123400'],
|
||||
['2015-01-06T22:59:59-00:10', '2015-01-06 23:09:59.000000'],
|
||||
['1979-05-27T07:32:00-08:00', '1979-05-27 15:32:00.000000'],
|
||||
['2024-10-19T22:47:08-00:00', '2024-10-19 22:47:08.000000'],
|
||||
['2024-10-19T22:47:08.9+00:00', '2024-10-19 22:47:08.900000'],
|
||||
['2024-10-20T01:47:08+03:00', '2024-10-19 22:47:08.000000'],
|
||||
['2024-10-20T01:47:08.981+03:00', '2024-10-19 22:47:08.981000'],
|
||||
['1979-05-27t07:32:00z', '1979-05-27 07:32:00.000000'],
|
||||
['1979-05-27 07:32:00Z', '1979-05-27 07:32:00.000000'],
|
||||
['1979-05-27T00:32:00.999999Z', '1979-05-27 00:32:00.999999'],
|
||||
]
|
||||
for pair in pairs {
|
||||
input, expected := pair[0], pair[1]
|
||||
res := time.parse_rfc3339(input) or {
|
||||
assert false, '>>> failing input: ${input} | err: ${err}'
|
||||
return
|
||||
}
|
||||
output := res.format_ss_micro()
|
||||
assert expected == output
|
||||
}
|
||||
assert invalid_rfc3339('22:47:08Z') == 'missing date part of RFC 3339'
|
||||
assert invalid_rfc3339('01:47:08.981+03:00') == 'missing date part of RFC 3339'
|
||||
assert invalid_rfc3339('2006-01-00T00:00:00Z') == 'date error: invalid day 0'
|
||||
assert invalid_rfc3339('2006-01-32T00:00:00Z') == 'date error: invalid day 32'
|
||||
assert invalid_rfc3339('2006-01-88T00:00:00Z') == 'date error: invalid day 88'
|
||||
assert invalid_rfc3339('2006-00-01T00:00:00Z') == 'date error: invalid month 0'
|
||||
assert invalid_rfc3339('2006-13-01T00:00:00Z') == 'date error: invalid month 13'
|
||||
assert invalid_rfc3339('2006-77-01T00:00:00Z') == 'date error: invalid month 77'
|
||||
assert invalid_rfc3339('2006-01-01T24:47:08Z') == 'invalid hour: 24'
|
||||
assert invalid_rfc3339('2006-01-01T99:47:08Z') == 'invalid hour: 99'
|
||||
assert invalid_rfc3339('2006-01-01T23:60:08Z') == 'invalid minute: 60'
|
||||
assert invalid_rfc3339('2006-01-01T23:99:08Z') == 'invalid minute: 99'
|
||||
assert invalid_rfc3339('2006-01-01T23:59:60Z') == 'invalid second: 60'
|
||||
assert invalid_rfc3339('2006-01-01T23:59:99Z') == 'invalid second: 99'
|
||||
assert invalid_rfc3339('1979-05-27X07:32:00Z') == 'invalid date-time separator:X'
|
||||
assert invalid_rfc3339('1979-05-27T07:32:00') == 'timezone error: datetime string is too short'
|
||||
assert invalid_rfc3339('1979-05-27') == 'date-time too short to parse'
|
||||
assert invalid_rfc3339('1979-05-27T00:32:00.999999') == 'timezone error: expected "Z" or "z" or "+" or "-" in position 26, not "9"'
|
||||
assert invalid_rfc3339('1979-05-27T00:32:00.') == 'timezone error: expected "Z" or "z" at the end of the string'
|
||||
}
|
||||
|
||||
fn test_parse_rfc3339_offset() {
|
||||
pairs := [
|
||||
['2024-01-15T14:00:00+01:35', '2024-01-15 12:25:00'],
|
||||
['2024-01-15T14:10:00+01:40', '2024-01-15 12:30:00'],
|
||||
['2024-01-15T14:00:00+10:55', '2024-01-15 03:05:00'],
|
||||
]
|
||||
for pair in pairs {
|
||||
input, expected := pair[0], pair[1]
|
||||
res := time.parse_rfc3339(input)!
|
||||
assert res.str() == expected
|
||||
}
|
||||
}
|
||||
|
||||
fn test_parse_rfc2616() {
|
||||
mut r := time.parse_rfc2616('Wed, 06 Nov 2024 08:49:37 GMT')!
|
||||
assert r.unix() == 1730882977
|
||||
|
||||
r = time.parse_rfc2616('Wednesday, 06-Nov-24 08:49:37 GMT')!
|
||||
assert r.unix() == 1730882977
|
||||
|
||||
r = time.parse_rfc2616('Wed Nov 6 08:49:37 2024')!
|
||||
assert r.unix() == 1730882977
|
||||
|
||||
r = time.parse_rfc2616('Thu, 19 Feb 2026 11:07:09 GMT')!
|
||||
assert r.unix() == 1771499229
|
||||
|
||||
r = time.parse_rfc2616('Tuesday, 19-Feb-26 11:07:09 GMT')!
|
||||
assert r.unix() == 1771499229
|
||||
|
||||
r = time.parse_rfc2616('Thu Feb 19 11:07:09 2026')!
|
||||
assert r.unix() == 1771499229
|
||||
|
||||
// This should map to 1994, not 2094.
|
||||
r = time.parse_rfc2616('Tuesday, 06-Nov-94 11:07:09 GMT')!
|
||||
assert r.unix() == 784120029
|
||||
|
||||
// This should map to 2020, not 1920.
|
||||
r = time.parse_rfc2616('Friday, 06-Nov-20 08:49:37 GMT')!
|
||||
assert r.unix() == 1604652577
|
||||
}
|
||||
|
||||
fn test_parse_http_header_string() {
|
||||
t := time.now()
|
||||
header := t.http_header_string()
|
||||
back_time := time.parse_http_header_string(header)!
|
||||
assert t.str() == back_time.str()
|
||||
}
|
||||
|
||||
fn test_ad_second_to_parse_result_in_2001() {
|
||||
now_tm := time.parse('2001-01-01 04:00:00')!
|
||||
future_tm := now_tm.add_seconds(60)
|
||||
assert future_tm.str() == '2001-01-01 04:01:00'
|
||||
assert now_tm.unix() < future_tm.unix()
|
||||
}
|
||||
|
||||
fn test_ad_second_to_parse_result_pre_2001() {
|
||||
now_tm := time.parse('2000-01-01 04:00:00')!
|
||||
future_tm := now_tm.add_seconds(60)
|
||||
assert future_tm.str() == '2000-01-01 04:01:00'
|
||||
assert now_tm.unix() < future_tm.unix()
|
||||
}
|
||||
|
||||
fn test_parse_format() {
|
||||
mut s := '2018-01-27 12:48:34'
|
||||
mut t := time.parse_format(s, 'YYYY-MM-DD HH:mm:ss') or {
|
||||
assert false, '> failing format: ${s} | err: ${err}'
|
||||
return
|
||||
}
|
||||
assert t.year == 2018 && t.month == 1 && t.day == 27 && t.hour == 12 && t.minute == 48
|
||||
&& t.second == 34
|
||||
|
||||
s = '2018-November-27 12:48:20'
|
||||
t = time.parse_format(s, 'YYYY-MMMM-DD HH:mm:ss') or {
|
||||
assert false, '> failing format: ${s} | err: ${err}'
|
||||
return
|
||||
}
|
||||
assert t.year == 2018 && t.month == 11 && t.day == 27 && t.hour == 12 && t.minute == 48
|
||||
&& t.second == 20
|
||||
|
||||
s = '18-1-2 0:8:2'
|
||||
t = time.parse_format(s, 'YY-M-D H:m:s') or {
|
||||
assert false, '> failing format: ${s} | err: ${err}'
|
||||
return
|
||||
}
|
||||
assert t.year == 2018 && t.month == 1 && t.day == 2 && t.hour == 0 && t.minute == 8
|
||||
&& t.second == 2
|
||||
|
||||
// This should always fail, because we test if M and D allow for a 01 value which they shouldn't
|
||||
s = '2018-01-02 1:8:2'
|
||||
t = time.parse_format(s, 'YYYY-M-D H:m:s') or { return }
|
||||
|
||||
assert false, '> failing for datetime: ${s}, the datetime string should not have passed the format "YYYY-M-D H:m:s"'
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import time
|
||||
|
||||
fn test_relative() {
|
||||
// past
|
||||
mut date := time.now()
|
||||
assert date.relative() == 'now'
|
||||
date = date.add_seconds(-61)
|
||||
assert date.relative() == '1 minute ago'
|
||||
assert date.relative_short() == '1m ago'
|
||||
date = date.add_seconds(-120)
|
||||
assert date.relative() == '3 minutes ago'
|
||||
assert date.relative_short() == '3m ago'
|
||||
date = date.add_seconds(-1 * time.seconds_per_hour)
|
||||
assert date.relative() == '1 hour ago'
|
||||
assert date.relative_short() == '1h ago'
|
||||
date = date.add_seconds(-5 * time.seconds_per_hour)
|
||||
assert date.relative() == '6 hours ago'
|
||||
assert date.relative_short() == '6h ago'
|
||||
date = date.add_seconds(-1 * time.seconds_per_day)
|
||||
assert date.relative() == '1 day ago'
|
||||
assert date.relative_short() == '1d ago'
|
||||
date = date.add_seconds(-4 * time.seconds_per_day)
|
||||
assert date.relative() == '5 days ago'
|
||||
assert date.relative_short() == '5d ago'
|
||||
date = time.now().add_seconds(-75 * time.seconds_per_day)
|
||||
assert date.relative() == 'last ${date.custom_format('MMM')} ${date.custom_format('D')}'
|
||||
assert date.relative_short() == '75d ago'
|
||||
date = time.now().add_seconds(-400 * time.seconds_per_day)
|
||||
assert date.relative() == '1 year ago'
|
||||
assert date.relative_short() == '1y ago'
|
||||
|
||||
// future
|
||||
date = time.now()
|
||||
date = date.add_seconds(61)
|
||||
assert date.relative() == 'in 1 minute'
|
||||
assert date.relative_short() == 'in 1m'
|
||||
date = date.add_seconds(120)
|
||||
assert date.relative() == 'in 3 minutes'
|
||||
assert date.relative_short() == 'in 3m'
|
||||
date = date.add_seconds(1 * time.seconds_per_hour)
|
||||
assert date.relative() == 'in 1 hour'
|
||||
assert date.relative_short() == 'in 1h'
|
||||
date = date.add_seconds(5 * time.seconds_per_hour)
|
||||
assert date.relative() == 'in 6 hours'
|
||||
assert date.relative_short() == 'in 6h'
|
||||
date = date.add_seconds(time.seconds_per_day)
|
||||
assert date.relative() == 'in 1 day'
|
||||
assert date.relative_short() == 'in 1d'
|
||||
date = date.add_seconds(4 * time.seconds_per_day)
|
||||
assert date.relative() == 'in 5 days'
|
||||
assert date.relative_short() == 'in 5d'
|
||||
date = time.now().add_seconds(75 * time.seconds_per_day)
|
||||
assert date.relative() == 'on ${date.custom_format('MMM')} ${date.custom_format('D')}'
|
||||
assert date.relative_short() == 'in 75d'
|
||||
date = time.now().add_seconds(400 * time.seconds_per_day)
|
||||
assert date.relative() == 'in 1 year'
|
||||
assert date.relative_short() == 'in 1y'
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) 2019-2024 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
|
||||
|
||||
@[params]
|
||||
pub struct StopWatchOptions {
|
||||
pub:
|
||||
auto_start bool = true
|
||||
}
|
||||
|
||||
// StopWatch is used to measure elapsed time.
|
||||
pub struct StopWatch {
|
||||
mut:
|
||||
elapsed u64
|
||||
started bool
|
||||
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
|
||||
started: opts.auto_start
|
||||
start: initial
|
||||
end: 0
|
||||
}
|
||||
}
|
||||
|
||||
// start starts the stopwatch. If the timer was paused, it continues counting.
|
||||
pub fn (mut t StopWatch) start() {
|
||||
t.start = sys_mono_now()
|
||||
t.end = 0
|
||||
t.started = true
|
||||
}
|
||||
|
||||
// restart restarts the stopwatch. If the timer was paused, it restarts counting.
|
||||
pub fn (mut t StopWatch) restart() {
|
||||
t.start = sys_mono_now()
|
||||
t.end = 0
|
||||
t.elapsed = 0
|
||||
t.started = true
|
||||
}
|
||||
|
||||
// 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.started {
|
||||
if t.end == 0 {
|
||||
t.elapsed += sys_mono_now() - t.start
|
||||
} else {
|
||||
t.elapsed += t.end - t.start
|
||||
}
|
||||
}
|
||||
t.start = 0
|
||||
t.started = false
|
||||
}
|
||||
|
||||
// elapsed returns the Duration since the last start call.
|
||||
pub fn (t StopWatch) elapsed() Duration {
|
||||
if t.started {
|
||||
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))
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
module time
|
||||
|
||||
fn test_stopwatch_elapsed_uses_explicit_started_state() {
|
||||
sw := StopWatch{
|
||||
started: true
|
||||
start: 0
|
||||
end: 7
|
||||
}
|
||||
assert sw.elapsed() == 7
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import time
|
||||
|
||||
// Note: 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright (c) 2019-2024 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 {
|
||||
pub:
|
||||
tv_sec u64
|
||||
tv_usec u64
|
||||
}
|
||||
|
||||
type C.time_t = i64
|
||||
|
||||
fn C.time(t &C.time_t) C.time_t
|
||||
fn C.localtime(t &C.time_t) &C.tm
|
||||
fn C.localtime_r(t &C.time_t, tm &C.tm)
|
||||
fn C.gmtime(t &C.time_t) &C.tm
|
||||
fn C.gmtime_r(t &C.time_t, res &C.tm) &C.tm
|
||||
fn C.strftime(buf &char, maxsize usize, const_format &char, const_tm &C.tm) usize
|
||||
|
||||
// now returns the current local time.
|
||||
pub fn now() Time {
|
||||
$if macos {
|
||||
return darwin_now()
|
||||
}
|
||||
$if windows {
|
||||
return win_now()
|
||||
}
|
||||
$if solaris {
|
||||
return solaris_now()
|
||||
}
|
||||
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()
|
||||
}
|
||||
return linux_utc()
|
||||
}
|
||||
|
||||
fn time_with_unix(t Time) Time {
|
||||
if t.unix != 0 {
|
||||
return t
|
||||
}
|
||||
normalized := normalize_zero_date_parts(t)
|
||||
return Time{
|
||||
...normalized
|
||||
unix: time_fields_to_unix(normalized)
|
||||
}
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn normalize_zero_date_parts(t Time) Time {
|
||||
if t.month != 0 && t.day != 0 {
|
||||
return t
|
||||
}
|
||||
return Time{
|
||||
...t
|
||||
month: if t.month == 0 { 1 } else { t.month }
|
||||
day: if t.day == 0 { 1 } else { t.day }
|
||||
}
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn time_fields_to_unix(t Time) i64 {
|
||||
return i64(t.days_from_unix_epoch()) * i64(seconds_per_day) +
|
||||
i64(t.hour) * i64(seconds_per_hour) + i64(t.minute) * i64(seconds_per_minute) +
|
||||
i64(t.second)
|
||||
}
|
||||
|
||||
// ticks returns the number of milliseconds since the UNIX epoch.
|
||||
// On Windows ticks returns the 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(1_000)))
|
||||
}
|
||||
// t := i64(C.mach_absolute_time())
|
||||
// # Nanoseconds elapsedNano = AbsoluteToNanoseconds( *(AbsoluteTime *) &t );
|
||||
// # return (double)(* (uint64_t *) &elapsedNano) / 1000000;
|
||||
}
|
||||
|
||||
// str returns the 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, nanosecond 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
|
||||
nanosecond: nanosecond
|
||||
unix: make_unix_time(t)
|
||||
// for the actual code base when we
|
||||
// call convert_ctime, it is always
|
||||
// when we manage the local time.
|
||||
is_local: true
|
||||
}
|
||||
}
|
||||
|
||||
// strftime returns the formatted time using `strftime(3)`.
|
||||
pub fn (t Time) strftime(fmt string) string {
|
||||
mut tm := &C.tm{}
|
||||
$if windows {
|
||||
tm = C.gmtime(voidptr(&t.unix))
|
||||
} $else {
|
||||
C.gmtime_r(voidptr(&t.unix), tm)
|
||||
}
|
||||
mut buf := [1024]char{}
|
||||
fmt_c := unsafe { &char(fmt.str) }
|
||||
C.strftime(&buf[0], usize(sizeof(buf)), fmt_c, tm)
|
||||
return unsafe { cstring_to_vstring(&buf[0]) }
|
||||
}
|
||||
|
||||
// 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 > 2147483647 * 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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
module time
|
||||
|
||||
// now returns the current local 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
|
||||
}
|
||||
|
||||
// utc returns the current UTC time.
|
||||
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
|
||||
}
|
||||
|
||||
// local returns the 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
|
||||
}
|
||||
|
||||
// sleep suspends the execution for a given duration (in nanoseconds).
|
||||
pub fn sleep(dur Duration) {
|
||||
#let now = new Date().getTime()
|
||||
#let toWait = BigInt(dur.val) / BigInt(time__millisecond)
|
||||
#while (new Date().getTime() < now + Number(toWait)) {}
|
||||
}
|
||||
|
||||
fn time_with_unix(t Time) Time {
|
||||
if t.unix != 0 {
|
||||
return t
|
||||
}
|
||||
mut res := Time{}
|
||||
#res.year.val = t.year.val
|
||||
#res.month.val = t.month.val
|
||||
#res.day.val = t.day.val
|
||||
#res.hour.val = t.hour.val
|
||||
#res.minute.val = t.minute.val
|
||||
#res.second.val = t.second.val
|
||||
#res.microsecond.val = t.microsecond.val
|
||||
#res.unix.val = t.unix.val
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// ticks returns the number of milliseconds since the UNIX epoch.
|
||||
// // On Windows ticks returns the number of milliseconds elapsed since system start.
|
||||
pub fn ticks() i64 {
|
||||
t := i64(0)
|
||||
#t.val = BigInt(new Date().getTime())
|
||||
|
||||
return t
|
||||
}
|
||||
@@ -0,0 +1,487 @@
|
||||
module time
|
||||
|
||||
pub const days_string = 'MonTueWedThuFriSatSun'
|
||||
pub const long_days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']!
|
||||
pub const month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]!
|
||||
pub const months_string = 'JanFebMarAprMayJunJulAugSepOctNovDec'
|
||||
pub const long_months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
|
||||
'September', 'October', 'November', 'December']
|
||||
// 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.
|
||||
pub const absolute_zero_year = i64(-292277022399)
|
||||
pub const seconds_per_minute = 60
|
||||
pub const seconds_per_hour = 60 * seconds_per_minute
|
||||
pub const seconds_per_day = 24 * seconds_per_hour
|
||||
pub const seconds_per_week = 7 * seconds_per_day
|
||||
pub const days_per_400_years = days_in_year * 400 + 97
|
||||
pub const days_per_100_years = days_in_year * 100 + 24
|
||||
pub const days_per_4_years = days_in_year * 4 + 1
|
||||
pub const days_in_year = 365
|
||||
pub const 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,
|
||||
]!
|
||||
|
||||
// Time contains various time units for a point in time.
|
||||
@[markused]
|
||||
pub struct Time {
|
||||
unix i64
|
||||
pub:
|
||||
year int
|
||||
month int
|
||||
day int
|
||||
hour int
|
||||
minute int
|
||||
second int
|
||||
nanosecond int
|
||||
is_local bool // used to make time.now().local().local() == time.now().local()
|
||||
}
|
||||
|
||||
// FormatDelimiter contains different time formats.
|
||||
pub enum FormatTime {
|
||||
hhmm12
|
||||
hhmm24
|
||||
hhmmss12
|
||||
hhmmss24
|
||||
hhmmss24_milli
|
||||
hhmmss24_micro
|
||||
hhmmss24_nano
|
||||
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
|
||||
}
|
||||
|
||||
fn normalize_new_time(t Time) Time {
|
||||
month := if t.month == 0 { 1 } else { t.month }
|
||||
day := if t.day == 0 { 1 } else { t.day }
|
||||
if t.year < -9999 || t.year > 9999 {
|
||||
panic('invalid time: year must be between -9999 and 9999')
|
||||
}
|
||||
if month < 1 || month > 12 {
|
||||
panic('invalid time: month must be between 1 and 12')
|
||||
}
|
||||
max_day := month_days[month - 1] + if month == 2 && is_leap_year(t.year) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
if day < 1 || day > max_day {
|
||||
panic('invalid time: day must be between 1 and ${max_day} for year ${t.year}, month ${month}')
|
||||
}
|
||||
if t.hour < 0 || t.hour > 23 {
|
||||
panic('invalid time: hour must be between 0 and 23')
|
||||
}
|
||||
if t.minute < 0 || t.minute > 59 {
|
||||
panic('invalid time: minute must be between 0 and 59')
|
||||
}
|
||||
if t.second < 0 || t.second > 59 {
|
||||
panic('invalid time: second must be between 0 and 59')
|
||||
}
|
||||
if t.nanosecond < 0 || t.nanosecond >= 1_000_000_000 {
|
||||
panic('invalid time: nanosecond must be between 0 and 999999999')
|
||||
}
|
||||
return Time{
|
||||
...t
|
||||
month: month
|
||||
day: day
|
||||
}
|
||||
}
|
||||
|
||||
// Time.new returns a time struct with the calculated Unix time.
|
||||
pub fn Time.new(t Time) Time {
|
||||
return time_with_unix(normalize_new_time(t))
|
||||
}
|
||||
|
||||
// new returns a time struct with the calculated Unix time.
|
||||
pub fn new(t Time) Time {
|
||||
return Time.new(t)
|
||||
}
|
||||
|
||||
// smonth returns the month name abbreviation.
|
||||
pub fn (t Time) smonth() string {
|
||||
if t.month <= 0 || t.month > 12 {
|
||||
return '---'
|
||||
}
|
||||
i := t.month - 1
|
||||
return months_string[i * 3..(i + 1) * 3]
|
||||
}
|
||||
|
||||
// unix returns the UNIX time with second resolution.
|
||||
@[inline]
|
||||
pub fn (t Time) unix() i64 {
|
||||
return time_with_unix(t.local_to_utc()).unix
|
||||
}
|
||||
|
||||
// local_unix returns the UNIX local time with second resolution.
|
||||
@[inline]
|
||||
pub fn (t Time) local_unix() i64 {
|
||||
return time_with_unix(t).unix
|
||||
}
|
||||
|
||||
// is_zero returns true when `t` is the zero value of `time.Time`.
|
||||
@[inline]
|
||||
pub fn (t Time) is_zero() bool {
|
||||
return t.unix == 0 && t.year == 0 && t.month == 0 && t.day == 0 && t.hour == 0 && t.minute == 0
|
||||
&& t.second == 0 && t.nanosecond == 0 && !t.is_local
|
||||
}
|
||||
|
||||
// unix_milli returns the UNIX time with millisecond resolution.
|
||||
@[inline]
|
||||
pub fn (t Time) unix_milli() i64 {
|
||||
return t.unix() * 1_000 + (i64(t.nanosecond) / 1_000_000)
|
||||
}
|
||||
|
||||
// unix_micro returns the UNIX time with microsecond resolution.
|
||||
@[inline]
|
||||
pub fn (t Time) unix_micro() i64 {
|
||||
return t.unix() * 1_000_000 + (i64(t.nanosecond) / 1_000)
|
||||
}
|
||||
|
||||
// unix_nano returns the UNIX time with nanosecond resolution.
|
||||
@[inline]
|
||||
pub fn (t Time) unix_nano() i64 {
|
||||
// TODO: use i128 here, when V supports it, since the following expression overflows for years like 3001:
|
||||
return t.unix() * 1_000_000_000 + i64(t.nanosecond)
|
||||
}
|
||||
|
||||
// add returns a new time with the given duration added.
|
||||
pub fn (t Time) add(duration_in_nanosecond Duration) Time {
|
||||
// This expression overflows i64 for big years (and we do not have i128 yet):
|
||||
// nanos := t.unix * 1_000_000_000 + i64(t.nanosecond) <-
|
||||
// ... so instead, handle the addition manually in parts ¯\_(ツ)_/¯
|
||||
mut increased_time_nanosecond := i64(t.nanosecond) + duration_in_nanosecond.nanoseconds()
|
||||
// increased_time_second
|
||||
mut increased_time_second := t.local_unix() + (increased_time_nanosecond / second)
|
||||
increased_time_nanosecond = increased_time_nanosecond % second
|
||||
if increased_time_nanosecond < 0 {
|
||||
increased_time_second--
|
||||
increased_time_nanosecond += second
|
||||
}
|
||||
res := unix_nanosecond(increased_time_second, int(increased_time_nanosecond))
|
||||
|
||||
if t.is_local {
|
||||
// we need to reset unix to 0, because we don't know the offset
|
||||
// and we can't calculate it without it without causing infinite recursion
|
||||
// so unfortunately we need to recalculate unix next time it is needed
|
||||
return Time{
|
||||
...res
|
||||
is_local: true
|
||||
unix: 0
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// add_seconds returns a new time struct with an added number of seconds.
|
||||
pub fn (t Time) add_seconds(seconds int) Time {
|
||||
return time_with_unix(t).add(i64(seconds) * second)
|
||||
}
|
||||
|
||||
// add_days returns a new time struct with an added number of days.
|
||||
pub fn (t Time) add_days(days int) Time {
|
||||
return time_with_unix(t).add(i64(days) * 24 * hour)
|
||||
}
|
||||
|
||||
// since returns the time duration elapsed since a given time.
|
||||
pub fn since(t Time) Duration {
|
||||
return now() - t
|
||||
}
|
||||
|
||||
// relative returns a string representation of the difference between t
|
||||
// and the current time.
|
||||
//
|
||||
// Sample outputs:
|
||||
// ```
|
||||
// // future
|
||||
// now
|
||||
// in 5 minutes
|
||||
// in 1 day
|
||||
// on Feb 17
|
||||
// // past
|
||||
// 2 hours ago
|
||||
// last Jan 15
|
||||
// 5 years ago
|
||||
// ```
|
||||
pub fn (t Time) relative() string {
|
||||
znow := now()
|
||||
mut secs := znow.unix() - t.unix()
|
||||
mut prefix := ''
|
||||
mut suffix := ''
|
||||
if secs < 0 {
|
||||
secs *= -1
|
||||
prefix = 'in '
|
||||
} else {
|
||||
suffix = ' ago'
|
||||
}
|
||||
if secs < seconds_per_minute / 2 {
|
||||
return 'now'
|
||||
}
|
||||
if secs < seconds_per_hour {
|
||||
m := secs / seconds_per_minute
|
||||
if m == 1 {
|
||||
return '${prefix}1 minute${suffix}'
|
||||
}
|
||||
return '${prefix}${m} minutes${suffix}'
|
||||
}
|
||||
if secs < seconds_per_hour * 24 {
|
||||
h := secs / seconds_per_hour
|
||||
if h == 1 {
|
||||
return '${prefix}1 hour${suffix}'
|
||||
}
|
||||
return '${prefix}${h} hours${suffix}'
|
||||
}
|
||||
if secs < seconds_per_hour * 24 * 7 {
|
||||
d := secs / seconds_per_hour / 24
|
||||
if d == 1 {
|
||||
return '${prefix}1 day${suffix}'
|
||||
}
|
||||
return '${prefix}${d} days${suffix}'
|
||||
}
|
||||
if secs < seconds_per_hour * 24 * days_in_year {
|
||||
if prefix == 'in ' {
|
||||
return 'on ${t.md()}'
|
||||
}
|
||||
return 'last ${t.md()}'
|
||||
}
|
||||
y := secs / seconds_per_hour / 24 / days_in_year
|
||||
if y == 1 {
|
||||
return '${prefix}1 year${suffix}'
|
||||
}
|
||||
return '${prefix}${y} years${suffix}'
|
||||
}
|
||||
|
||||
// relative_short returns a string saying how long ago a time occurred as follows:
|
||||
// 0-30 seconds: `"now"`; 30-60 seconds: `"1m"`; anything else is rounded to the
|
||||
// nearest minute, hour, day, or year
|
||||
//
|
||||
// Sample outputs:
|
||||
// ```
|
||||
// // future
|
||||
// now
|
||||
// in 5m
|
||||
// in 1d
|
||||
// // past
|
||||
// 2h ago
|
||||
// 5y ago
|
||||
// ```
|
||||
pub fn (t Time) relative_short() string {
|
||||
znow := now()
|
||||
mut secs := znow.unix() - t.unix()
|
||||
mut prefix := ''
|
||||
mut suffix := ''
|
||||
if secs < 0 {
|
||||
secs *= -1
|
||||
prefix = 'in '
|
||||
} else {
|
||||
suffix = ' ago'
|
||||
}
|
||||
if secs < seconds_per_minute / 2 {
|
||||
return 'now'
|
||||
}
|
||||
if secs < seconds_per_hour {
|
||||
m := secs / seconds_per_minute
|
||||
if m == 1 {
|
||||
return '${prefix}1m${suffix}'
|
||||
}
|
||||
return '${prefix}${m}m${suffix}'
|
||||
}
|
||||
if secs < seconds_per_hour * 24 {
|
||||
h := secs / seconds_per_hour
|
||||
if h == 1 {
|
||||
return '${prefix}1h${suffix}'
|
||||
}
|
||||
return '${prefix}${h}h${suffix}'
|
||||
}
|
||||
if secs < seconds_per_hour * 24 * days_in_year {
|
||||
d := secs / seconds_per_hour / 24
|
||||
if d == 1 {
|
||||
return '${prefix}1d${suffix}'
|
||||
}
|
||||
return '${prefix}${d}d${suffix}'
|
||||
}
|
||||
y := secs / seconds_per_hour / 24 / days_in_year
|
||||
if y == 1 {
|
||||
return '${prefix}1y${suffix}'
|
||||
}
|
||||
return '${prefix}${y}y${suffix}'
|
||||
}
|
||||
|
||||
// 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[iclamp(0, m - 1, 11)] + 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)
|
||||
}
|
||||
|
||||
// week_of_year returns the current week of year as an integer.
|
||||
// follow ISO 8601 standard
|
||||
pub fn (t Time) week_of_year() int {
|
||||
// ISO 8601 Week of Year Rules:
|
||||
// --------------------------------------------
|
||||
// 1. Week Definition:
|
||||
// - A week starts on **Monday** (Day 1) and ends on **Sunday** (Day 7).
|
||||
// 2. First Week of the Year:
|
||||
// - The first week is the one containing the year's **first Thursday**.
|
||||
// - Equivalently, the week with January 4th always belongs to Week 1.
|
||||
// 3. Year Assignment:
|
||||
// - Dates in December/January may belong to the previous/next ISO year,
|
||||
// depending on the week's Thursday.
|
||||
// 4. Week Number Format:
|
||||
// - Expressed as `YYYY-Www` (e.g., `2026-W01` for the first week of 2026).
|
||||
// --------------------------------------------
|
||||
// Algorithm Steps:
|
||||
// 1. Find the Thursday of the current week:
|
||||
// - If date is Monday-Wednesday, add days to reach Thursday.
|
||||
// - If date is Thursday-Sunday, subtract days to reach Thursday.
|
||||
// 2. The ISO year is the calendar year of this Thursday.
|
||||
// 3. Compute the week number as:
|
||||
// week_number = (thursday's day_of_year - 1) / 7 + 1
|
||||
dow := t.day_of_week()
|
||||
days_to_thursday := 4 - dow
|
||||
thursday_date := t.add_days(days_to_thursday)
|
||||
thursday_day_of_year := thursday_date.year_day()
|
||||
week_number := (thursday_day_of_year - 1) / 7 + 1
|
||||
return week_number
|
||||
}
|
||||
|
||||
// year_day returns the current day of the year as an integer.
|
||||
// See also #Time.custom_format .
|
||||
pub fn (t Time) year_day() int {
|
||||
yday := t.day + days_before[iclamp(0, t.month - 1, 12)]
|
||||
if is_leap_year(t.year) && t.month > 2 {
|
||||
return yday + 1
|
||||
}
|
||||
return yday
|
||||
}
|
||||
|
||||
// weekday_str returns the current day as a string 3 letter abbreviation.
|
||||
pub fn (t Time) weekday_str() string {
|
||||
i := t.day_of_week() - 1
|
||||
return long_days[iclamp(0, i, 6)][0..3]
|
||||
}
|
||||
|
||||
// long_weekday_str returns the current day as a string.
|
||||
pub fn (t Time) long_weekday_str() string {
|
||||
i := t.day_of_week() - 1
|
||||
return long_days[iclamp(0, i, 6)]
|
||||
}
|
||||
|
||||
// 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 := month_days[month - 1] + extra
|
||||
return res
|
||||
}
|
||||
|
||||
// debug returns detailed breakdown of time (`Time{ year: YYYY month: MM day: dd hour: HH: minute: mm second: ss nanosecond: nanos unix: unix is_local: false }`).
|
||||
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} nanosecond: ${t.nanosecond:09} unix: ${t.unix:07} is_local: ${t.is_local} }'
|
||||
}
|
||||
|
||||
// offset returns time zone UTC offset in seconds.
|
||||
pub fn offset() int {
|
||||
t := utc()
|
||||
local := t.local()
|
||||
return int(local.unix - t.unix)
|
||||
}
|
||||
|
||||
// local_to_utc converts the receiver `t` to the corresponding UTC time, if it contains local time.
|
||||
// If the receiver already does contain UTC time, it returns it unchanged.
|
||||
pub fn (t Time) local_to_utc() Time {
|
||||
if !t.is_local {
|
||||
return t
|
||||
}
|
||||
return Time{
|
||||
...t.add(i64(-offset()) * second)
|
||||
is_local: false
|
||||
}
|
||||
}
|
||||
|
||||
// utc_to_local converts the receiver `u` to the corresponding local time, if it contains UTC time.
|
||||
// If the receiver already does contain local time, it returns it unchanged.
|
||||
pub fn (u Time) utc_to_local() Time {
|
||||
if u.is_local {
|
||||
return u
|
||||
}
|
||||
return Time{
|
||||
...u.add(i64(offset()) * second)
|
||||
is_local: true
|
||||
}
|
||||
}
|
||||
|
||||
// as_local returns the exact same time, as the receiver `t`, but with its .is_local field set to true.
|
||||
// See also #Time.utc_to_local .
|
||||
pub fn (t Time) as_local() Time {
|
||||
return Time{
|
||||
...t
|
||||
is_local: true
|
||||
}
|
||||
}
|
||||
|
||||
// as_utc returns the exact same time, as the receiver `t`, but with its .is_local field set to false.
|
||||
// See also #Time.local_to_utc .
|
||||
pub fn (t Time) as_utc() Time {
|
||||
return Time{
|
||||
...t
|
||||
is_local: false
|
||||
}
|
||||
}
|
||||
|
||||
// is_utc returns true, when the receiver `t` is a UTC time, and false otherwise.
|
||||
// See also #Time.utc_to_local .
|
||||
pub fn (t Time) is_utc() bool {
|
||||
return !t.is_local
|
||||
}
|
||||
@@ -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)
|
||||
assert aa.ymmdd() == '1900-06-30'
|
||||
}
|
||||
|
||||
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-28'
|
||||
}
|
||||
|
||||
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)
|
||||
dump(a.debug())
|
||||
dump(aa.debug())
|
||||
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'
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
module time
|
||||
|
||||
#include <mach/mach_time.h>
|
||||
|
||||
// start_time is needed on Darwin and Windows because of potential overflows
|
||||
const start_time = C.mach_absolute_time()
|
||||
|
||||
const time_base = init_time_base()
|
||||
|
||||
@[typedef]
|
||||
pub 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(i32) u64
|
||||
|
||||
struct InternalTimeBase {
|
||||
numer u32 = 1
|
||||
denom u32 = 1
|
||||
}
|
||||
|
||||
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_base.denom == 0 {
|
||||
unsafe {
|
||||
C.mach_timebase_info(&time_base)
|
||||
}
|
||||
}
|
||||
return (tm - start_time) * time_base.numer / time_base.denom
|
||||
}
|
||||
|
||||
// Note: 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_base.denom == 0 {
|
||||
unsafe {
|
||||
C.mach_timebase_info(&time_base)
|
||||
}
|
||||
}
|
||||
return (tm - start_time) * time_base.numer / time_base.denom
|
||||
}
|
||||
|
||||
// darwin_now returns a better precision current time for macos
|
||||
fn darwin_now() Time {
|
||||
// Use clock_gettime_nsec_np to avoid field-address lowering on C.timespec.
|
||||
epoch_ns := i64(C.clock_gettime_nsec_np(C.CLOCK_REALTIME))
|
||||
sec := epoch_ns / i64(second)
|
||||
nsec := int(epoch_ns % i64(second))
|
||||
loc_tm := C.tm{}
|
||||
C.localtime_r(voidptr(&sec), &loc_tm)
|
||||
return convert_ctime_with_unix(loc_tm, nsec, sec + i64(loc_tm.tm_gmtoff))
|
||||
}
|
||||
|
||||
// darwin_utc returns a better precision current time for macos
|
||||
fn darwin_utc() Time {
|
||||
epoch_ns := i64(C.clock_gettime_nsec_np(C.CLOCK_REALTIME))
|
||||
sec := epoch_ns / i64(second)
|
||||
nsec := int(epoch_ns % i64(second))
|
||||
return unix_nanosecond(sec, nsec)
|
||||
}
|
||||
|
||||
// dummy to compile with all compilers
|
||||
fn solaris_now() Time {
|
||||
return Time{}
|
||||
}
|
||||
|
||||
// dummy to compile with all compilers
|
||||
fn solaris_utc() Time {
|
||||
return Time{}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import time
|
||||
|
||||
const time_to_test = time.Time{
|
||||
year: 1980
|
||||
month: 7
|
||||
day: 11
|
||||
hour: 21
|
||||
minute: 23
|
||||
second: 42
|
||||
}
|
||||
|
||||
fn test_now_format() {
|
||||
t := time.now()
|
||||
u := t.unix()
|
||||
assert t.format() == time.unix(int(u)).utc_to_local().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()
|
||||
}
|
||||
|
||||
fn test_http_header_string() {
|
||||
assert 'Fri, 11 Jul 1980 21:23:42 GMT' == time_to_test.http_header_string()
|
||||
}
|
||||
|
||||
fn test_push_to_http_header() {
|
||||
mut http1_1_buffer := 'HTTP/1.1 200 OK\r\nDate: '.bytes()
|
||||
time_to_test.push_to_http_header(mut http1_1_buffer)
|
||||
|
||||
assert http1_1_buffer.bytestr() == 'HTTP/1.1 200 OK\r\nDate: Fri, 11 Jul 1980 21:23:42 GMT'
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
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
|
||||
#}
|
||||
|
||||
// sys_mono_now returns a *monotonically increasing time*, NOT a time adjusted for daylight savings, location etc.
|
||||
pub fn sys_mono_now() u64 {
|
||||
$if js_browser {
|
||||
mut res := u64(0)
|
||||
#res = new u64(Math.floor(window.performance.now() * 1000000))
|
||||
|
||||
return res
|
||||
} $else $if js_node {
|
||||
mut res := u64(0)
|
||||
#res.val = $process.hrtime.bigint()
|
||||
|
||||
return res
|
||||
} $else {
|
||||
mut res := u64(0)
|
||||
#res = new u64($sys_mono_new_Date() * 1000000)
|
||||
|
||||
return res
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
fn darwin_now() Time {
|
||||
return Time{}
|
||||
}
|
||||
|
||||
// solaris_now - dummy fn to compile on all platforms/compilers
|
||||
fn solaris_now() Time {
|
||||
return Time{}
|
||||
}
|
||||
|
||||
// darwin_utc - dummy fn to compile on all platforms/compilers
|
||||
fn darwin_utc() Time {
|
||||
return Time{}
|
||||
}
|
||||
|
||||
// solaris_utc - dummy fn to compile on all platforms/compilers
|
||||
fn solaris_utc() Time {
|
||||
return Time{}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright (c) 2019-2024 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
|
||||
|
||||
#flag darwin -I@VEXEROOT/thirdparty/legacy/include/LegacySupport
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
|
||||
pub struct C.tm {
|
||||
pub mut:
|
||||
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
|
||||
tm_gmtoff int
|
||||
}
|
||||
|
||||
fn C.timegm(&C.tm) C.time_t
|
||||
|
||||
// preferring localtime_r over the localtime because
|
||||
// from docs localtime_r is thread safe,
|
||||
fn C.localtime_r(t &C.time_t, tm &C.tm)
|
||||
|
||||
fn make_unix_time(t C.tm) i64 {
|
||||
return unsafe { i64(C.timegm(&t)) }
|
||||
}
|
||||
|
||||
// localtime_r already resolves the local UTC offset, so callers can reuse it.
|
||||
@[inline]
|
||||
fn convert_ctime_with_unix(t C.tm, nanosecond int, unix i64) 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
|
||||
nanosecond: nanosecond
|
||||
unix: unix
|
||||
is_local: true
|
||||
}
|
||||
}
|
||||
|
||||
// local returns t with the location set to local time.
|
||||
pub fn (t Time) local() Time {
|
||||
if t.is_local {
|
||||
return t
|
||||
}
|
||||
loc_tm := C.tm{}
|
||||
t_ := t.unix()
|
||||
C.localtime_r(voidptr(&t_), &loc_tm)
|
||||
return convert_ctime_with_unix(loc_tm, t.nanosecond, t_ + i64(loc_tm.tm_gmtoff))
|
||||
}
|
||||
|
||||
// in most systems, these are __quad_t, which is an i64
|
||||
pub struct C.timespec {
|
||||
pub 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(i32, &C.timespec) i32
|
||||
|
||||
fn C.nanosleep(req &C.timespec, rem &C.timespec) i32
|
||||
|
||||
// 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) * 1_000_000_000 + u64(ts.tv_nsec)
|
||||
}
|
||||
}
|
||||
|
||||
// Note: 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) * 1_000_000_000 + 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_with_unix(loc_tm, int(ts.tv_nsec), i64(ts.tv_sec) + i64(loc_tm.tm_gmtoff))
|
||||
}
|
||||
|
||||
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 unix_nanosecond(i64(ts.tv_sec), int(ts.tv_nsec))
|
||||
}
|
||||
|
||||
// dummy to compile with all compilers
|
||||
fn win_now() Time {
|
||||
return Time{}
|
||||
}
|
||||
|
||||
// dummy to compile with all compilers
|
||||
fn win_utc() Time {
|
||||
return Time{}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// sleep suspends the execution of the calling thread 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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_with_unix(loc_tm, int(ts.tv_nsec), i64(ts.tv_sec) + i64(loc_tm.tm_gmtoff))
|
||||
}
|
||||
|
||||
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 unix_nanosecond(i64(ts.tv_sec), int(ts.tv_nsec))
|
||||
}
|
||||
|
||||
// dummy to compile with all compilers
|
||||
fn darwin_now() Time {
|
||||
return Time{}
|
||||
}
|
||||
|
||||
// dummy to compile with all compilers
|
||||
fn darwin_utc() Time {
|
||||
return Time{}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import time
|
||||
|
||||
fn test_tm_gmtoff() {
|
||||
$if windows {
|
||||
return
|
||||
} $else {
|
||||
rawtime := i64(0) // C.time_t{}
|
||||
|
||||
C.time(&rawtime) // C.tm{}
|
||||
|
||||
info := C.localtime(&rawtime)
|
||||
t1 := time.now()
|
||||
t2 := time.utc()
|
||||
dump(t1)
|
||||
dump(t2)
|
||||
dump(t1.nanosecond)
|
||||
dump(t2.nanosecond)
|
||||
diff := int(t1.local_unix() - t2.unix())
|
||||
dump(diff)
|
||||
dump(info.tm_gmtoff)
|
||||
assert diff in [info.tm_gmtoff - 1, info.tm_gmtoff, info.tm_gmtoff + 1]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,532 @@
|
||||
import time
|
||||
import math
|
||||
import os
|
||||
|
||||
const local_time_to_test = time.new(
|
||||
year: 1980
|
||||
month: 7
|
||||
day: 11
|
||||
hour: 21
|
||||
minute: 23
|
||||
second: 42
|
||||
nanosecond: 123456789
|
||||
is_local: true
|
||||
)
|
||||
|
||||
const utc_time_to_test = time.new(
|
||||
year: 1980
|
||||
month: 7
|
||||
day: 11
|
||||
hour: 21
|
||||
minute: 23
|
||||
second: 42
|
||||
nanosecond: 123456789
|
||||
is_local: false
|
||||
)
|
||||
|
||||
fn assert_new_panics(expr string, expected string) {
|
||||
source_path := os.join_path(os.temp_dir(), 'time_new_invalid_prog.v')
|
||||
source := 'import time
|
||||
|
||||
fn main() {
|
||||
println(${expr})
|
||||
}
|
||||
'
|
||||
os.write_file(source_path, source) or { panic(err) }
|
||||
defer {
|
||||
os.rm(source_path) or {}
|
||||
}
|
||||
res := os.execute('${os.quoted_path(@VEXE)} run ${os.quoted_path(source_path)}')
|
||||
assert res.exit_code != 0, 'expected `${expr}` to fail'
|
||||
assert res.output.contains(expected), 'expected `${expected}` in `${res.output}`'
|
||||
}
|
||||
|
||||
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_new_defaults_missing_month_and_day() {
|
||||
t := time.new(year: 2024)
|
||||
assert t.year == 2024
|
||||
assert t.month == 1
|
||||
assert t.day == 1
|
||||
assert t.hour == 0
|
||||
assert t.minute == 0
|
||||
assert t.second == 0
|
||||
assert t.nanosecond == 0
|
||||
assert t.str() == '2024-01-01 00:00:00'
|
||||
}
|
||||
|
||||
fn test_new_rejects_invalid_values() {
|
||||
assert_new_panics('time.new(year: 10000)', 'invalid time: year must be between -9999 and 9999')
|
||||
assert_new_panics('time.new(year: 2024, month: 13)',
|
||||
'invalid time: month must be between 1 and 12')
|
||||
assert_new_panics('time.new(year: 2024, month: 2, day: 30)',
|
||||
'invalid time: day must be between 1 and 29 for year 2024, month 2')
|
||||
assert_new_panics('time.new(year: 2024, hour: 24)',
|
||||
'invalid time: hour must be between 0 and 23')
|
||||
}
|
||||
|
||||
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
|
||||
assert utc_time_to_test.unix() == 332198622
|
||||
}
|
||||
|
||||
fn test_format_rfc3339() {
|
||||
// assert '1980-07-11T19:23:42.123Z'
|
||||
utc_res := utc_time_to_test.format_rfc3339()
|
||||
assert utc_res.ends_with('23:42.123Z')
|
||||
assert utc_res.starts_with('1980-07-1')
|
||||
assert utc_res.contains('T')
|
||||
}
|
||||
|
||||
fn test_format_rfc3339_micro() {
|
||||
utc_res := utc_time_to_test.format_rfc3339_micro()
|
||||
assert utc_res.ends_with('23:42.123456Z')
|
||||
assert utc_res.starts_with('1980-07-1')
|
||||
assert utc_res.contains('T')
|
||||
}
|
||||
|
||||
fn test_format_rfc3339_nano() {
|
||||
utc_res := utc_time_to_test.format_rfc3339_nano()
|
||||
assert utc_res.ends_with('23:42.123456789Z')
|
||||
assert utc_res.starts_with('1980-07-1')
|
||||
assert utc_res.contains('T')
|
||||
}
|
||||
|
||||
fn test_format_ss() {
|
||||
assert '11.07.1980 21:23:42' == local_time_to_test.get_fmt_str(.dot, .hhmmss24, .ddmmyyyy)
|
||||
|
||||
assert '11.07.1980 21:23:42' == utc_time_to_test.get_fmt_str(.dot, .hhmmss24, .ddmmyyyy)
|
||||
}
|
||||
|
||||
fn test_format_ss_milli() {
|
||||
assert '11.07.1980 21:23:42.123' == local_time_to_test.get_fmt_str(.dot, .hhmmss24_milli,
|
||||
.ddmmyyyy)
|
||||
assert '1980-07-11 21:23:42.123' == local_time_to_test.format_ss_milli()
|
||||
|
||||
assert '11.07.1980 21:23:42.123' == utc_time_to_test.get_fmt_str(.dot, .hhmmss24_milli,
|
||||
.ddmmyyyy)
|
||||
assert '1980-07-11 21:23:42.123' == utc_time_to_test.format_ss_milli()
|
||||
}
|
||||
|
||||
fn test_format_ss_micro() {
|
||||
assert '11.07.1980 21:23:42.123456' == local_time_to_test.get_fmt_str(.dot, .hhmmss24_micro,
|
||||
.ddmmyyyy)
|
||||
assert '1980-07-11 21:23:42.123456' == local_time_to_test.format_ss_micro()
|
||||
|
||||
assert '11.07.1980 21:23:42.123456' == utc_time_to_test.get_fmt_str(.dot, .hhmmss24_micro,
|
||||
.ddmmyyyy)
|
||||
assert '1980-07-11 21:23:42.123456' == utc_time_to_test.format_ss_micro()
|
||||
}
|
||||
|
||||
fn test_format_ss_nano() {
|
||||
assert '11.07.1980 21:23:42.123456789' == local_time_to_test.get_fmt_str(.dot, .hhmmss24_nano,
|
||||
.ddmmyyyy)
|
||||
assert '1980-07-11 21:23:42.123456789' == local_time_to_test.format_ss_nano()
|
||||
|
||||
assert '11.07.1980 21:23:42.123456789' == utc_time_to_test.get_fmt_str(.dot, .hhmmss24_nano,
|
||||
.ddmmyyyy)
|
||||
assert '1980-07-11 21:23:42.123456789' == utc_time_to_test.format_ss_nano()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
assert day_of_week == t.day_of_week()
|
||||
}
|
||||
}
|
||||
|
||||
fn test_week_of_year() {
|
||||
// As windows use msvcrt.dll, which `strftime` does not support %V, so skip test
|
||||
// TODO: newer version windows use ucrtbase.dll, which support %V
|
||||
$if !windows {
|
||||
for year in 2000 .. 2100 {
|
||||
mut t := time.new(time.Time{
|
||||
year: year
|
||||
month: 12
|
||||
day: 20
|
||||
})
|
||||
|
||||
// check from year.12.20 to next_year.1.8
|
||||
for _ in 0 .. 20 {
|
||||
assert t.strftime('%V') == '${t.week_of_year():02}', '${t}'
|
||||
t = t.add_days(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t1 := time.Time{
|
||||
year: 2025
|
||||
month: 3
|
||||
day: 3
|
||||
}
|
||||
assert t1.week_of_year() == 10
|
||||
assert t1.add_days(1).week_of_year() == 10
|
||||
}
|
||||
|
||||
fn test_year_day() {
|
||||
// testing if December 31st in a leap year is numbered as 366
|
||||
assert time.parse('2024-12-31 20:00:00')!.year_day() == 366
|
||||
|
||||
// testing December 31st's number in a non leap year
|
||||
assert time.parse('2025-12-31 20:00:00')!.year_day() == 365
|
||||
|
||||
assert time.parse('2024-02-28 20:00:00')!.year_day() == 59
|
||||
assert time.parse('2024-02-29 20:00:00')!.year_day() == 60
|
||||
assert time.parse('2024-03-01 20:00:00')!.year_day() == 61
|
||||
assert time.parse('2024-03-02 20:00:00')!.year_day() == 62
|
||||
|
||||
assert time.parse('2025-02-28 20:00:00')!.year_day() == 59
|
||||
assert time.parse('2025-03-01 20:00:00')!.year_day() == 60
|
||||
|
||||
assert time.parse('2024-01-01 20:00:00')!.year_day() == 1
|
||||
assert time.parse('2025-01-01 20:00:00')!.year_day() == 1
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
assert t.weekday_str() == name
|
||||
}
|
||||
}
|
||||
|
||||
fn test_add() {
|
||||
d_seconds := 3
|
||||
d_nanoseconds := 13
|
||||
duration := time.Duration(i64(d_seconds) * time.second + d_nanoseconds * time.nanosecond)
|
||||
// dump(duration.debug())
|
||||
t1 := local_time_to_test
|
||||
// dump(t1.debug())
|
||||
t2 := local_time_to_test.add(duration)
|
||||
// dump(t2.debug())
|
||||
assert t2.second == t1.second + d_seconds
|
||||
assert t2.nanosecond == t1.nanosecond + d_nanoseconds
|
||||
assert t2.unix() == t1.unix() + d_seconds
|
||||
assert t2.is_local == t1.is_local
|
||||
|
||||
t3 := local_time_to_test.add(-duration)
|
||||
// dump(t3.debug())
|
||||
assert t3.second == t1.second - d_seconds
|
||||
assert t3.nanosecond == t1.nanosecond - d_nanoseconds
|
||||
assert t3.unix() == t1.unix() - d_seconds
|
||||
assert t3.is_local == t1.is_local
|
||||
|
||||
t4 := local_time_to_test.as_local()
|
||||
// dump(t4.debug())
|
||||
t5 := t4.add(duration)
|
||||
// dump(t5.debug())
|
||||
assert t5.is_local == t4.is_local
|
||||
|
||||
t := time.Time{
|
||||
year: 2024
|
||||
month: 4
|
||||
day: 3
|
||||
}
|
||||
t_5am := t.add(time.hour * 5)
|
||||
assert t_5am.hour == 5
|
||||
next_day := t_5am.add_days(1)
|
||||
assert next_day.day == 4 && next_day.day == t_5am.day + 1
|
||||
assert next_day.year == t_5am.year && next_day.month == t.month
|
||||
assert next_day.month == t_5am.month && next_day.month == t.month
|
||||
assert next_day.hour == t_5am.hour && next_day.month == t.month
|
||||
}
|
||||
|
||||
fn test_add_days() {
|
||||
num_of_days := 3
|
||||
t := local_time_to_test.add_days(num_of_days)
|
||||
assert t.day == local_time_to_test.day + num_of_days
|
||||
assert t.unix() == local_time_to_test.unix() + 86400 * num_of_days
|
||||
}
|
||||
|
||||
fn test_str() {
|
||||
assert '1980-07-11 21:23:42' == local_time_to_test.str()
|
||||
|
||||
assert '1980-07-11 21:23:42' == utc_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.nanosecond >= 0
|
||||
assert now.nanosecond < time.second
|
||||
}
|
||||
|
||||
fn test_utc() {
|
||||
now := time.utc()
|
||||
// The year the test was built
|
||||
// dump(now.debug())
|
||||
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.nanosecond >= 0
|
||||
assert now.nanosecond < time.second
|
||||
}
|
||||
|
||||
fn test_unix_time() {
|
||||
t1 := time.utc()
|
||||
time.sleep(50 * time.millisecond)
|
||||
t2 := time.utc()
|
||||
eprintln(' t1: ${t1}')
|
||||
eprintln(' t2: ${t2}')
|
||||
ut1 := t1.unix()
|
||||
ut2 := t2.unix()
|
||||
eprintln(' ut1: ${ut1}')
|
||||
eprintln(' ut2: ${ut2}')
|
||||
assert ut2 - ut1 < 2
|
||||
|
||||
utm1 := t1.unix_milli()
|
||||
utm2 := t2.unix_milli()
|
||||
eprintln('utm1: ${utm1}')
|
||||
eprintln('utm2: ${utm2}')
|
||||
assert (utm1 - ut1 * 1000) < 1000
|
||||
assert (utm2 - ut2 * 1000) < 1000
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
fn test_since() {
|
||||
t1 := time.now()
|
||||
time.sleep(20 * time.millisecond)
|
||||
d1 := time.since(t1)
|
||||
assert d1 >= 20_000_000
|
||||
time.sleep(20 * time.millisecond)
|
||||
d2 := time.since(t1)
|
||||
assert d2 >= 40_000_000
|
||||
}
|
||||
|
||||
// issue relate https://github.com/vlang/v/issues/13828
|
||||
// problem: the local method add 2h on the time in a Linux machine
|
||||
// the other machine are not tested in a local env
|
||||
fn test_recursive_local_call() {
|
||||
now_tm := time.now()
|
||||
assert now_tm.str() == now_tm.local().str()
|
||||
assert now_tm.local().str() == now_tm.local().local().str()
|
||||
}
|
||||
|
||||
fn test_strftime() {
|
||||
assert '1980 July 11' == local_time_to_test.strftime('%Y %B %d')
|
||||
|
||||
assert '1980 July 11' == utc_time_to_test.strftime('%Y %B %d')
|
||||
}
|
||||
|
||||
fn test_add_seconds_to_time() {
|
||||
now_tm := time.now()
|
||||
future_tm := now_tm.add_seconds(60)
|
||||
assert now_tm.unix() < future_tm.unix()
|
||||
}
|
||||
|
||||
fn test_plus_equals_duration() {
|
||||
mut d := time.second
|
||||
d += time.second
|
||||
assert d == 2 * time.second
|
||||
}
|
||||
|
||||
fn test_parse_three_letters_month() {
|
||||
tm := time.now()
|
||||
format := 'MMM DD HH:mm:ss YYYY'
|
||||
tm_s := tm.custom_format(format)
|
||||
tm_tm := time.parse_format(tm_s, format)!
|
||||
assert tm_tm.month == tm.month
|
||||
}
|
||||
|
||||
fn test_parse_ordinal_weekday_d() {
|
||||
format := 'd MMM DD HH:mm:ss YYYY'
|
||||
dt := '0 Jan 01 00:00:00 1970'
|
||||
tm := time.parse_format(dt, format)!
|
||||
tm_s := tm.custom_format(format)
|
||||
assert tm_s == '4 Jan 01 00:00:00 1970'
|
||||
}
|
||||
|
||||
fn test_parse_ordinal_weekday_c() {
|
||||
format := 'c MMM DD HH:mm:ss YYYY'
|
||||
dt := '7 Jan 01 00:00:00 1970'
|
||||
tm := time.parse_format(dt, format)!
|
||||
tm_s := tm.custom_format(format)
|
||||
assert tm_s == '4 Jan 01 00:00:00 1970'
|
||||
}
|
||||
|
||||
fn test_parse_two_letters_weekday() {
|
||||
format := 'dd MMM DD HH:mm:ss YYYY'
|
||||
dt := 'Su Jan 01 00:00:00 1970'
|
||||
tm := time.parse_format(dt, format)!
|
||||
tm_s := tm.custom_format(format)
|
||||
assert tm_s == 'Th Jan 01 00:00:00 1970'
|
||||
}
|
||||
|
||||
fn test_parse_three_letters_weekday() {
|
||||
format := 'ddd MMM DD HH:mm:ss YYYY'
|
||||
dt := 'Sun Jan 01 00:00:00 1970'
|
||||
tm := time.parse_format(dt, format)!
|
||||
tm_s := tm.custom_format(format)
|
||||
assert tm_s == 'Thu Jan 01 00:00:00 1970'
|
||||
}
|
||||
|
||||
fn test_parse_weekday() {
|
||||
format := 'dddd MMM DD HH:mm:ss YYYY'
|
||||
dt := 'Sunday Jan 01 00:00:00 1970'
|
||||
tm := time.parse_format(dt, format)!
|
||||
tm_s := tm.custom_format(format)
|
||||
assert tm_s == 'Thursday Jan 01 00:00:00 1970'
|
||||
}
|
||||
|
||||
fn test_empty_time() {
|
||||
t := time.Time{}
|
||||
assert t.unix() == -62167132800
|
||||
assert t.format_rfc3339() == '0000-01-01T00:00:00.000Z'
|
||||
assert t == time.parse_rfc3339(t.format_rfc3339())!
|
||||
assert t.custom_format('MMMM YYYY') == 'January 0'
|
||||
}
|
||||
|
||||
fn test_pre_epoch_unix_calculation() {
|
||||
assert time.new(time.Time{}).unix() == -62167132800
|
||||
assert time.new(
|
||||
year: 1
|
||||
month: 1
|
||||
day: 1
|
||||
).unix() == -62135596800
|
||||
assert time.parse_rfc3339('0001-01-01T00:00:00Z')!.unix() == -62135596800
|
||||
t := time.Time{}
|
||||
assert t.is_zero()
|
||||
// assert t.unix() == -62169984000
|
||||
assert t.custom_format('MMMM YYYY') == 'January 0'
|
||||
}
|
||||
|
||||
fn test_is_zero() {
|
||||
assert time.Time{}.is_zero()
|
||||
assert !time.unix(0).is_zero()
|
||||
assert !time.new(year: 2024).is_zero()
|
||||
assert !time.Time{
|
||||
is_local: true
|
||||
}.is_zero()
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
// Copyright (c) 2019-2024 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>
|
||||
|
||||
pub struct C.tm {
|
||||
pub mut:
|
||||
tm_year int
|
||||
tm_mon int
|
||||
tm_mday int
|
||||
tm_hour int
|
||||
tm_min int
|
||||
tm_sec int
|
||||
}
|
||||
|
||||
pub struct C._FILETIME {
|
||||
pub mut:
|
||||
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)
|
||||
|
||||
fn C.timespec_get(t &C.timespec, base i32) i32
|
||||
|
||||
// start_time and time_coefficients are needed on Windows because of potential overflows
|
||||
const start_time = init_win_time_start()
|
||||
const time_coefficients = init_win_time_coefficients()
|
||||
const start_local_time = local_as_unix_time()
|
||||
|
||||
// in most systems, these are __quad_t, which is an i64
|
||||
pub struct C.timespec {
|
||||
pub:
|
||||
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)
|
||||
}
|
||||
|
||||
struct WinTimeCoefficients {
|
||||
numer u64
|
||||
denom u64
|
||||
}
|
||||
|
||||
fn init_win_time_coefficients() WinTimeCoefficients {
|
||||
mut f := u64(0)
|
||||
C.QueryPerformanceFrequency(voidptr(&f))
|
||||
mut nom := u64(1000000000)
|
||||
for f % 10 == 0 {
|
||||
f = f / 10
|
||||
nom = nom / 10
|
||||
}
|
||||
res := WinTimeCoefficients{
|
||||
numer: nom
|
||||
denom: f
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
fn init_win_time_start() u64 {
|
||||
s := u64(0)
|
||||
C.QueryPerformanceCounter(voidptr(&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(voidptr(&tm)) // XP or later never fail
|
||||
return (tm - start_time) * time_coefficients.numer / time_coefficients.denom
|
||||
}
|
||||
|
||||
// Note: 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(voidptr(&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 {
|
||||
if t.is_local {
|
||||
return t
|
||||
}
|
||||
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)
|
||||
millisecond: u16(t.nanosecond / 1_000_000)
|
||||
}
|
||||
st_local := SystemTime{}
|
||||
C.SystemTimeToTzSpecificLocalTime(unsafe { nil }, voidptr(&st_utc), voidptr(&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
|
||||
nanosecond: int(st_local.millisecond) * 1_000_000
|
||||
unix: st_local.unix()
|
||||
}
|
||||
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 precise 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, voidptr(&st_utc))
|
||||
st_local := SystemTime{}
|
||||
C.SystemTimeToTzSpecificLocalTime(unsafe { nil }, voidptr(&st_utc), voidptr(&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
|
||||
nanosecond: int(st_local.millisecond) * 1_000_000
|
||||
unix: st_local.unix()
|
||||
is_local: true
|
||||
}
|
||||
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 precise 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, voidptr(&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
|
||||
nanosecond: int(st_utc.millisecond) * 1_000_000
|
||||
unix: st_utc.unix()
|
||||
is_local: false
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// unix returns Unix time.
|
||||
fn (st SystemTime) unix() 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
|
||||
fn darwin_now() Time {
|
||||
return Time{}
|
||||
}
|
||||
|
||||
// dummy to compile with all compilers
|
||||
fn linux_now() Time {
|
||||
return Time{}
|
||||
}
|
||||
|
||||
// dummy to compile with all compilers
|
||||
fn solaris_now() Time {
|
||||
return Time{}
|
||||
}
|
||||
|
||||
// dummy to compile with all compilers
|
||||
fn darwin_utc() Time {
|
||||
return Time{}
|
||||
}
|
||||
|
||||
// dummy to compile with all compilers
|
||||
fn linux_utc() Time {
|
||||
return Time{}
|
||||
}
|
||||
|
||||
// dummy to compile with all compilers
|
||||
fn solaris_utc() Time {
|
||||
return Time{}
|
||||
}
|
||||
|
||||
// sleep makes the calling thread sleep for a given duration (in nanoseconds).
|
||||
pub fn sleep(duration Duration) {
|
||||
C.Sleep(int(duration / millisecond))
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) 2019-2024 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 calculated from the given Unix timestamp in seconds since 1970-01-01 .
|
||||
pub fn unix(epoch i64) Time {
|
||||
return unix_nanosecond(epoch, 0)
|
||||
}
|
||||
|
||||
// unix_milli returns a Time calculated from the given Unix timestamp in milliseconds since 1970-01-01 .
|
||||
pub fn unix_milli(ms i64) Time {
|
||||
return ts_to_time_impl(ms, 1_000, 1_000_000)
|
||||
}
|
||||
|
||||
// unix_micro returns a Time calculated from the given Unix timestamp in microseconds since 1970-01-01 .
|
||||
pub fn unix_micro(us i64) Time {
|
||||
return ts_to_time_impl(us, 1_000_000, 1_000)
|
||||
}
|
||||
|
||||
// unix_nano returns a Time calculated from the given Unix timestamp in nanoseconds since 1970-01-01 .
|
||||
pub fn unix_nano(ns i64) Time {
|
||||
return ts_to_time_impl(ns, 1_000_000_000, 1)
|
||||
}
|
||||
|
||||
fn ts_to_time_impl(value i64, down i64, up i64) Time {
|
||||
epoch := value / down
|
||||
remainder := (value % down) * up
|
||||
return unix_nanosecond(epoch, int(remainder))
|
||||
}
|
||||
|
||||
// unix_microsecond returns a Time struct, given an Unix timestamp in seconds, and a microsecond value.
|
||||
pub fn unix_microsecond(epoch i64, microsecond int) Time {
|
||||
return unix_nanosecond(epoch, microsecond * 1000)
|
||||
}
|
||||
|
||||
// unix_nanosecond returns a Time struct given a Unix timestamp in seconds and a nanosecond value.
|
||||
pub fn unix_nanosecond(abs_unix_timestamp i64, nanosecond int) Time {
|
||||
// Split into day and time
|
||||
mut day_offset := abs_unix_timestamp / seconds_per_day
|
||||
if abs_unix_timestamp % seconds_per_day < 0 {
|
||||
// Compensate for rounding towards zero on integers as we want floored instead
|
||||
day_offset--
|
||||
}
|
||||
year, month, day := calculate_date_from_day_offset(day_offset)
|
||||
hour_, minute_, second_ :=
|
||||
calculate_time_from_second_offset(abs_unix_timestamp % seconds_per_day)
|
||||
return Time{
|
||||
year: year
|
||||
month: month
|
||||
day: day
|
||||
hour: hour_
|
||||
minute: minute_
|
||||
second: second_
|
||||
nanosecond: nanosecond
|
||||
unix: abs_unix_timestamp
|
||||
}
|
||||
}
|
||||
|
||||
// calculate_date_from_day_offset returns the year, month, and day based on the given day offset.
|
||||
fn calculate_date_from_day_offset(day_offset_ i64) (int, int, int) {
|
||||
mut day_offset := day_offset_
|
||||
|
||||
// source: http://howardhinnant.github.io/date_algorithms.html#civil_from_days
|
||||
|
||||
// shift from 1970-01-01 to 0000-03-01
|
||||
day_offset += 719468 // int(days_per_400_years * 1970 / 400 - (28+31))
|
||||
|
||||
mut era := 0
|
||||
if day_offset >= 0 {
|
||||
era = int(day_offset / days_per_400_years)
|
||||
} else {
|
||||
era = int((day_offset - days_per_400_years - 1) / days_per_400_years)
|
||||
}
|
||||
|
||||
// day_of_era => [0..146096]
|
||||
day_of_era := day_offset - era * days_per_400_years
|
||||
|
||||
// year_of_era => [0..399]
|
||||
year_of_era := (day_of_era - day_of_era / (days_per_4_years - 1) +
|
||||
day_of_era / days_per_100_years - day_of_era / (days_per_400_years - 1)) / days_in_year
|
||||
|
||||
mut year := int(year_of_era + era * 400)
|
||||
|
||||
// day_of_year => with year beginning Mar 1 [0..365]
|
||||
day_of_year := day_of_era - (days_in_year * year_of_era + year_of_era / 4 - year_of_era / 100)
|
||||
|
||||
month_position := (5 * day_of_year + 2) / 153
|
||||
day := int(day_of_year - (153 * month_position + 2) / 5 + 1)
|
||||
mut month := int(month_position)
|
||||
|
||||
if month_position < 10 {
|
||||
month += 3
|
||||
} else {
|
||||
month -= 9
|
||||
}
|
||||
|
||||
if month <= 2 {
|
||||
year += 1
|
||||
}
|
||||
|
||||
return year, month, day
|
||||
}
|
||||
|
||||
// calculate_time_from_second_offset returns the hour, minute, and second
|
||||
// based on the given second offset.
|
||||
fn calculate_time_from_second_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
|
||||
minute_ := second_offset / seconds_per_minute
|
||||
second_offset %= seconds_per_minute
|
||||
return int(hour_), int(minute_), int(second_offset)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import time
|
||||
|
||||
fn test_unix_timestamp_conversion_to_time() {
|
||||
t := time.now()
|
||||
ss := dump(time.unix(t.unix()))
|
||||
sms := dump(time.unix_milli(t.unix_milli()))
|
||||
sus := dump(time.unix_micro(t.unix_micro()))
|
||||
sns := dump(time.unix_nano(t.unix_nano()))
|
||||
assert sns.format_ss() == ss.format_ss()
|
||||
assert sns.format_ss_milli() == sms.format_ss_milli()
|
||||
assert sns.format_ss_micro() == sus.format_ss_micro()
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import time
|
||||
|
||||
fn test_as_utc() {
|
||||
t := time.now()
|
||||
u := t.as_utc()
|
||||
dump(u)
|
||||
assert u.is_local == false
|
||||
}
|
||||
|
||||
fn test_as_local() {
|
||||
t := time.now()
|
||||
l := t.as_local()
|
||||
dump(l)
|
||||
assert l.is_local == true
|
||||
}
|
||||
|
||||
fn test_local_to_utc() {
|
||||
n := time.now()
|
||||
u := n.local_to_utc()
|
||||
dump(u)
|
||||
o := time.offset()
|
||||
dump(o)
|
||||
if o != 0 {
|
||||
assert n != u
|
||||
back := u.utc_to_local() // convert it back to local time
|
||||
assert n == back, 'the converted original local->utc->local time, should be the same as the original local time'
|
||||
|
||||
double_u := u.local_to_utc()
|
||||
assert u == double_u, 'calling t.local_to_utc().local_to_utc() several times, should not change the time'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_utc_to_local() {
|
||||
z := time.Time{}
|
||||
assert z.is_local == false, 'simply constructing a time instance, should construct an UTC time'
|
||||
l := z.utc_to_local()
|
||||
dump(l)
|
||||
o := time.offset()
|
||||
dump(o)
|
||||
if o != 0 {
|
||||
assert z != l, 'when there is a time offset, the local time and the utc time should be different'
|
||||
assert l == l.utc_to_local(), 'converting a local to local time should not change the time'
|
||||
assert l == l.utc_to_local().utc_to_local(), 'double converting a local to local time to local time, should not change the time'
|
||||
}
|
||||
sz := z.format_rfc3339()
|
||||
dump(sz)
|
||||
assert sz == '0000-01-01T00:00:00.000Z'
|
||||
}
|
||||
Reference in new issue
Block a user