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

time: store time with nanosecond resolution in time.Time, deprecate Time.microsecond, add utility methods and tests (#19062)

This commit is contained in:
Delyan Angelov 2023-08-05 23:41:23 +03:00 committed by GitHub
parent cc97b8df1e
commit b9a523cefd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 265 additions and 199 deletions

View File

@ -74,12 +74,12 @@ fn on_frame(mut app App) {
// draw minute hand
mut j := f32(n.minute)
if n.second == 59 { // make minute hand move smoothly
j += f32(math.sin(f32(n.microsecond) / 1e6 * math.pi / 2.0))
j += f32(math.sin(f32(n.nanosecond) / 1e9 * math.pi / 2.0))
}
draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.minute_hand, hand_color, j * 6)
// draw second hand with smooth transition
k := f32(n.second) + f32(math.sin(f32(n.microsecond) / 1e6 * math.pi / 2.0))
k := f32(n.second) + f32(math.sin(f32(n.nanosecond) / 1e9 * math.pi / 2.0))
draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.second_hand, second_hand_color,
0 + k * 6)

View File

@ -28,7 +28,7 @@ const time_to_test = time.Time{
hour: 21
minute: 23
second: 42
microsecond: 123456
nanosecond: 123456789
unix: 332198622
}
@ -38,6 +38,7 @@ 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,

View File

@ -31,3 +31,9 @@ fn test_duration_str() {
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_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'
}

View File

@ -17,7 +17,7 @@ pub fn (t Time) format_ss() string {
// format_ss_milli returns a date string in "YYYY-MM-DD HH:mm:ss.123" format (24h).
pub fn (t Time) format_ss_milli() string {
return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.microsecond / 1000):03d}'
return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.nanosecond / 1_000_000):03d}'
}
// 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)
@ -25,12 +25,17 @@ pub fn (t Time) format_ss_milli() string {
// It is intended to improve consistency and interoperability, when representing and using date and time in Internet protocols.
pub fn (t Time) format_rfc3339() string {
u := t.local_to_utc()
return '${u.year:04d}-${u.month:02d}-${u.day:02d}T${u.hour:02d}:${u.minute:02d}:${u.second:02d}.${(u.microsecond / 1000):03d}Z'
return '${u.year:04d}-${u.month:02d}-${u.day:02d}T${u.hour:02d}:${u.minute:02d}:${u.second:02d}.${(u.nanosecond / 1_000_000):03d}Z'
}
// format_ss_micro returns a date string in "YYYY-MM-DD HH:mm:ss.123456" format (24h).
pub fn (t Time) format_ss_micro() string {
return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${t.microsecond:06d}'
return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.nanosecond / 1_000):06d}'
}
// format_ss_nano returns a date string in "YYYY-MM-DD HH:mm:ss.123456789" format (24h).
pub fn (t Time) format_ss_nano() string {
return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${t.nanosecond:09d}'
}
// hhmm returns a date string in "HH:mm" format (24h).
@ -381,8 +386,9 @@ pub fn (t Time) get_fmt_time_str(fmt_time FormatTime) string {
.hhmm24 { '${t.hour:02d}:${t.minute:02d}' }
.hhmmss12 { '${hour_}:${t.minute:02d}:${t.second:02d} ${tp}' }
.hhmmss24 { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}' }
.hhmmss24_milli { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.microsecond / 1000):03d}' }
.hhmmss24_micro { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${t.microsecond:06d}' }
.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}' }
}
}

View File

@ -3,19 +3,22 @@ module time
// operator `==` returns true if provided time is equal to time
[inline]
pub fn (t1 Time) == (t2 Time) bool {
return t1.unix == t2.unix && t1.microsecond == t2.microsecond
return t1.unix == t2.unix && t1.nanosecond == t2.nanosecond
}
// operator `<` returns true if provided time is less than time
[inline]
pub fn (t1 Time) < (t2 Time) bool {
return t1.unix < t2.unix || (t1.unix == t2.unix && t1.microsecond < t2.microsecond)
return t1.unix < t2.unix || (t1.unix == t2.unix && t1.nanosecond < t2.nanosecond)
}
// Time subtract using operator overloading.
[inline]
pub fn (lhs Time) - (rhs Time) Duration {
lhs_micro := lhs.unix * 1_000_000 + lhs.microsecond
rhs_micro := rhs.unix * 1_000_000 + rhs.microsecond
return (lhs_micro - rhs_micro) * microsecond
// 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
}

View File

@ -39,7 +39,7 @@ fn test_time1_should_be_same_as_time2() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
t2 := new_time(Time{
year: 2000
@ -48,7 +48,7 @@ fn test_time1_should_be_same_as_time2() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
assert t1 == t2
}
@ -61,9 +61,9 @@ fn test_time1_should_not_be_same_as_time2() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
// Difference is one microsecond
// Difference is one nanosecond
t2 := new_time(Time{
year: 2000
month: 5
@ -71,7 +71,7 @@ fn test_time1_should_not_be_same_as_time2() {
hour: 22
minute: 11
second: 3
microsecond: 101
nanosecond: 101
})
t3 := new_time(Time{
year: 2000
@ -80,7 +80,7 @@ fn test_time1_should_not_be_same_as_time2() {
hour: 22
minute: 11
second: 3
microsecond: 0
nanosecond: 0
})
// Difference is one second
t4 := new_time(Time{
@ -90,7 +90,7 @@ fn test_time1_should_not_be_same_as_time2() {
hour: 22
minute: 11
second: 4
microsecond: 0
nanosecond: 0
})
assert t1 != t2
assert t3 != t4
@ -104,9 +104,9 @@ fn test_time1_should_be_greater_than_time2() {
hour: 22
minute: 11
second: 3
microsecond: 102
nanosecond: 102
})
// Difference is one microsecond
// Difference is one nanosecond
t2 := new_time(Time{
year: 2000
month: 5
@ -114,7 +114,7 @@ fn test_time1_should_be_greater_than_time2() {
hour: 22
minute: 11
second: 3
microsecond: 101
nanosecond: 101
})
t3 := new_time(Time{
year: 2000
@ -123,7 +123,7 @@ fn test_time1_should_be_greater_than_time2() {
hour: 22
minute: 11
second: 5
microsecond: 0
nanosecond: 0
})
// Difference is one second
t4 := new_time(Time{
@ -133,7 +133,7 @@ fn test_time1_should_be_greater_than_time2() {
hour: 22
minute: 11
second: 4
microsecond: 0
nanosecond: 0
})
assert t1 > t2
assert t3 > t4
@ -147,9 +147,9 @@ fn test_time2_should_be_less_than_time1() {
hour: 22
minute: 11
second: 3
microsecond: 102
nanosecond: 102
})
// Difference is one microsecond
// Difference is one nanosecond
t2 := new_time(Time{
year: 2000
month: 5
@ -157,7 +157,7 @@ fn test_time2_should_be_less_than_time1() {
hour: 22
minute: 11
second: 3
microsecond: 101
nanosecond: 101
})
t3 := new_time(Time{
year: 2000
@ -166,7 +166,7 @@ fn test_time2_should_be_less_than_time1() {
hour: 22
minute: 11
second: 3
microsecond: 0
nanosecond: 0
})
// Difference is one second
t4 := new_time(Time{
@ -176,7 +176,7 @@ fn test_time2_should_be_less_than_time1() {
hour: 22
minute: 11
second: 2
microsecond: 0
nanosecond: 0
})
assert t2 < t1
assert t4 < t3
@ -190,9 +190,9 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_gt() {
hour: 22
minute: 11
second: 3
microsecond: 102
nanosecond: 102
})
// Difference is one microsecond
// Difference is one nanosecond
t2 := new_time(Time{
year: 2000
month: 5
@ -200,7 +200,7 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_gt() {
hour: 22
minute: 11
second: 3
microsecond: 101
nanosecond: 101
})
t3 := new_time(Time{
year: 2000
@ -209,7 +209,7 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_gt() {
hour: 22
minute: 11
second: 5
microsecond: 0
nanosecond: 0
})
// Difference is one second
t4 := new_time(Time{
@ -219,7 +219,7 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_gt() {
hour: 22
minute: 11
second: 4
microsecond: 0
nanosecond: 0
})
assert t1 >= t2
assert t3 >= t4
@ -233,9 +233,9 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_eq() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
// Difference is one microsecond
// Difference is one nanosecond
t2 := new_time(Time{
year: 2000
month: 5
@ -243,7 +243,7 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_eq() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
t3 := new_time(Time{
year: 2000
@ -252,7 +252,7 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_eq() {
hour: 22
minute: 11
second: 3
microsecond: 0
nanosecond: 0
})
// Difference is one second
t4 := new_time(Time{
@ -262,7 +262,7 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_eq() {
hour: 22
minute: 11
second: 3
microsecond: 0
nanosecond: 0
})
assert t1 >= t2
assert t3 >= t4
@ -276,9 +276,9 @@ fn test_time1_should_be_less_or_equal_to_time2_when_lt() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
// Difference is one microsecond
// Difference is one nanosecond
t2 := new_time(Time{
year: 2000
month: 5
@ -286,7 +286,7 @@ fn test_time1_should_be_less_or_equal_to_time2_when_lt() {
hour: 22
minute: 11
second: 3
microsecond: 101
nanosecond: 101
})
t3 := new_time(Time{
year: 2000
@ -295,7 +295,7 @@ fn test_time1_should_be_less_or_equal_to_time2_when_lt() {
hour: 22
minute: 11
second: 3
microsecond: 0
nanosecond: 0
})
// Difference is one second
t4 := new_time(Time{
@ -305,7 +305,7 @@ fn test_time1_should_be_less_or_equal_to_time2_when_lt() {
hour: 22
minute: 11
second: 4
microsecond: 0
nanosecond: 0
})
assert t1 <= t2
assert t3 <= t4
@ -319,9 +319,9 @@ fn test_time1_should_be_less_or_equal_to_time2_when_eq() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
// Difference is one microsecond
// Difference is one nanosecond
t2 := new_time(Time{
year: 2000
month: 5
@ -329,7 +329,7 @@ fn test_time1_should_be_less_or_equal_to_time2_when_eq() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
t3 := new_time(Time{
year: 2000
@ -338,7 +338,7 @@ fn test_time1_should_be_less_or_equal_to_time2_when_eq() {
hour: 22
minute: 11
second: 3
microsecond: 0
nanosecond: 0
})
// Difference is one second
t4 := new_time(Time{
@ -348,7 +348,7 @@ fn test_time1_should_be_less_or_equal_to_time2_when_eq() {
hour: 22
minute: 11
second: 3
microsecond: 0
nanosecond: 0
})
assert t1 <= t2
assert t3 <= t4
@ -362,7 +362,7 @@ fn test_time2_copied_from_time1_should_be_equal() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
t2 := new_time(t1)
assert t2 == t1
@ -370,8 +370,8 @@ fn test_time2_copied_from_time1_should_be_equal() {
fn test_subtract() {
d_seconds := 3
d_microseconds := 13
duration := d_seconds * second + d_microseconds * microsecond
d_nanoseconds := 13
duration := d_seconds * second + d_nanoseconds * nanosecond
t1 := new_time(Time{
year: 2000
month: 5
@ -379,9 +379,9 @@ fn test_subtract() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
t2 := unix2(i64(t1.unix) + d_seconds, t1.microsecond + d_microseconds)
t2 := unix_nanosecond(i64(t1.unix) + d_seconds, t1.nanosecond + d_nanoseconds)
d1 := t2 - t1
d2 := t1 - t2
assert d1 > 0

View File

@ -35,13 +35,13 @@ pub fn parse_rfc3339(s string) !Time {
}
// Check if sn is time only
if !parts[0].contains('-') && parts[0].contains(':') {
mut hour_, mut minute_, mut second_, mut microsecond_, mut unix_offset, mut is_local_time := 0, 0, 0, 0, i64(0), true
hour_, minute_, second_, microsecond_, unix_offset, is_local_time = parse_iso8601_time(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
hour_, minute_, second_, microsecond_, nanosecond_, unix_offset, is_local_time = parse_iso8601_time(parts[0])!
t = new_time(Time{
hour: hour_
minute: minute_
second: second_
microsecond: microsecond_
nanosecond: nanosecond_
})
if is_local_time {
return t // Time is already local time
@ -52,7 +52,7 @@ pub fn parse_rfc3339(s string) !Time {
} else if unix_offset > 0 {
unix_time += unix_offset
}
t = unix2(i64(unix_time), t.microsecond)
t = unix_nanosecond(i64(unix_time), t.nanosecond)
return t
}
@ -171,9 +171,9 @@ pub fn parse_iso8601(s string) !Time {
return error_invalid_time(12, 'malformed date')
}
year, month, day := parse_iso8601_date(parts[0])!
mut hour_, mut minute_, mut second_, mut microsecond_, mut unix_offset, mut is_local_time := 0, 0, 0, 0, i64(0), true
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_, unix_offset, is_local_time = parse_iso8601_time(parts[1])!
hour_, minute_, second_, microsecond_, nanosecond_, unix_offset, is_local_time = parse_iso8601_time(parts[1])!
}
mut t := new_time(
year: year
@ -182,7 +182,7 @@ pub fn parse_iso8601(s string) !Time {
hour: hour_
minute: minute_
second: second_
microsecond: microsecond_
nanosecond: nanosecond_
)
if is_local_time {
return t // Time already local time
@ -193,7 +193,7 @@ pub fn parse_iso8601(s string) !Time {
} else if unix_offset > 0 {
unix_time += unix_offset
}
t = unix2(i64(unix_time), t.microsecond)
t = unix_nanosecond(i64(unix_time), t.nanosecond)
return t
}
@ -237,7 +237,7 @@ fn parse_iso8601_date(s string) !(int, int, int) {
return year, month, day
}
fn parse_iso8601_time(s string) !(int, int, int, int, i64, bool) {
fn parse_iso8601_time(s string) !(int, int, int, int, int, i64, bool) {
hour_ := 0
minute_ := 0
second_ := 0
@ -281,6 +281,7 @@ fn parse_iso8601_time(s string) !(int, int, int, int, i64, bool) {
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
@ -300,5 +301,6 @@ fn parse_iso8601_time(s string) !(int, int, int, int, i64, bool) {
if plus_min_z == `+` {
unix_offset *= -1
}
return hour_, minute_, second_, microsecond_, unix_offset, is_local_time
// eprintln('parse_iso8601_time s: $s | hour_: $hour_ | minute_: $minute_ | second_: $second_ | microsecond_: $microsecond_ | nanosecond_: $nanosecond_ | unix_offset: $unix_offset | is_local_time: $is_local_time')
return hour_, minute_, second_, microsecond_, nanosecond_, unix_offset, is_local_time
}

View File

@ -65,11 +65,11 @@ fn test_parse_iso8601() {
]
times := [
[2020, 6, 5, 15, 38, 6, 0],
[2020, 6, 5, 15, 38, 6, 15959],
[2020, 6, 5, 15, 38, 6, 15959],
[2020, 6, 5, 13, 38, 6, 15959],
[2020, 6, 5, 17, 38, 6, 15959],
[2020, 11, 5, 15, 38, 6, 15959],
[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 {
@ -89,8 +89,8 @@ fn test_parse_iso8601() {
assert t.minute == minute
second := times[i][5]
assert t.second == second
microsecond := times[i][6]
assert t.microsecond == microsecond
nanosecond := times[i][6]
assert t.nanosecond == nanosecond
}
}
@ -107,7 +107,7 @@ fn test_parse_iso8601_local() {
assert t.hour == 15
assert t.minute == 38
assert t.second == 6
assert t.microsecond == 15959
assert t.nanosecond == 15959_000
}
fn test_parse_iso8601_invalid() {
@ -145,7 +145,7 @@ fn test_parse_iso8601_date_only() {
assert t.hour == 0
assert t.minute == 0
assert t.second == 0
assert t.microsecond == 0
assert t.nanosecond == 0
}
fn check_invalid_date(s string) {

View File

@ -53,13 +53,6 @@ pub fn utc() Time {
return solaris_utc()
}
return linux_utc()
/*
// defaults to most common feature, the microsecond precision is not available
// in this API call
t := C.time(0)
_ = C.time(&t)
return unix2(i64(t), 0)
*/
}
// new_time returns a time struct with the calculated Unix time.
@ -90,7 +83,7 @@ pub fn ticks() i64 {
} $else {
ts := C.timeval{}
C.gettimeofday(&ts, 0)
return i64(ts.tv_sec * u64(1000) + (ts.tv_usec / u64(1000)))
return i64(ts.tv_sec * u64(1000) + (ts.tv_usec / u64(1_000)))
}
// t := i64(C.mach_absolute_time())
// # Nanoseconds elapsedNano = AbsoluteToNanoseconds( *(AbsoluteTime *) &t );
@ -105,7 +98,7 @@ pub fn (t Time) str() string {
}
// convert_ctime converts a C time to V time.
fn convert_ctime(t C.tm, microsecond int) Time {
fn convert_ctime(t C.tm, nanosecond int) Time {
return Time{
year: t.tm_year + 1900
month: t.tm_mon + 1
@ -113,7 +106,7 @@ fn convert_ctime(t C.tm, microsecond int) Time {
hour: t.tm_hour
minute: t.tm_min
second: t.tm_sec
microsecond: microsecond
nanosecond: nanosecond
unix: make_unix_time(t)
// for the actual code base when we
// call convert_ctime, it is always

View File

@ -40,15 +40,17 @@ pub const (
// Time contains various time units for a point in time.
pub struct Time {
pub:
year int
month int
day int
hour int
minute int
second int
microsecond int
unix i64
is_local bool // used to make time.now().local().local() == time.now().local()
year int
month int
day int
hour int
minute int
second int
nanosecond int
unix i64
is_local bool // used to make time.now().local().local() == time.now().local()
//
microsecond int [deprecated: 'use t.nanosecond / 1000 instead'; deprecated_after: '2023-08-05']
}
// FormatDelimiter contains different time formats.
@ -59,6 +61,7 @@ pub enum FormatTime {
hhmmss24
hhmmss24_milli
hhmmss24_micro
hhmmss24_nano
no_time
}
@ -99,7 +102,7 @@ pub fn (t Time) smonth() string {
return time.months_string[i * 3..(i + 1) * 3]
}
// unix_time returns the UNIX time.
// unix_time returns the UNIX time with second resolution.
[inline]
pub fn (t Time) unix_time() i64 {
return t.unix
@ -108,18 +111,39 @@ pub fn (t Time) unix_time() i64 {
// unix_time_milli returns the UNIX time with millisecond resolution.
[inline]
pub fn (t Time) unix_time_milli() i64 {
return t.unix * 1000 + (t.microsecond / 1000)
return t.unix * 1_000 + (i64(t.nanosecond) / 1_000_000)
}
// unix_time_micro returns the UNIX time with microsecond resolution.
[inline]
pub fn (t Time) unix_time_micro() i64 {
return t.unix * 1_000_000 + (i64(t.nanosecond) / 1_000)
}
// unix_time_nano returns the UNIX time with nanosecond resolution.
[inline]
pub fn (t Time) unix_time_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(d Duration) Time {
microseconds := i64(t.unix) * 1_000_000 + t.microsecond + d.microseconds()
unix := microseconds / 1_000_000
micro := microseconds % 1_000_000
if t.is_local {
return unix2(unix, int(micro)).as_local()
// 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 unixs := t.unix
mut nanos := i64(t.nanosecond) + d.nanoseconds()
unixs += nanos / time.second
nanos = nanos % time.second
if nanos < 0 {
unixs--
nanos += time.second
}
return unix2(unix, int(micro))
if t.is_local {
return unix_nanosecond(unixs, int(nanos)).as_local()
}
return unix_nanosecond(unixs, int(nanos))
}
// add_seconds returns a new time struct with an added number of seconds.
@ -311,9 +335,9 @@ pub fn days_in_month(month int, year int) !int {
return res
}
// debug returns detailed breakdown of time (`Time{ year: YYYY month: MM day: dd hour: HH: minute: mm second: ss microsecond: micros unix: unix }`)
// debug returns detailed breakdown of time (`Time{ year: YYYY month: MM day: dd hour: HH: minute: mm second: ss nanosecond: nanos unix: unix }`)
pub fn (t Time) debug() string {
return 'Time{ year: ${t.year:04} month: ${t.month:02} day: ${t.day:02} hour: ${t.hour:02} minute: ${t.minute:02} second: ${t.second:02} microsecond: ${t.microsecond:06} unix: ${t.unix:07} }'
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} }'
}
// A lot of these are taken from the Go library.
@ -326,6 +350,7 @@ pub const (
second = Duration(1000 * millisecond)
minute = Duration(60 * second)
hour = Duration(60 * minute)
// day = Duration(24 * hour)
infinite = Duration(i64(9223372036854775807))
)
@ -348,23 +373,22 @@ pub fn (d Duration) milliseconds() i64 {
// consider all of them in sub-one intervals
// seconds returns the duration as a floating point number of seconds.
pub fn (d Duration) seconds() f64 {
sec := d / time.second
nsec := d % time.second
return f64(sec) + f64(nsec) / time.second
return f64(d) / f64(time.second)
}
// minutes returns the duration as a floating point number of minutes.
pub fn (d Duration) minutes() f64 {
min := d / time.minute
nsec := d % time.minute
return f64(min) + f64(nsec) / time.minute
return f64(d) / f64(time.minute)
}
// hours returns the duration as a floating point number of hours.
pub fn (d Duration) hours() f64 {
hr := d / time.hour
nsec := d % time.hour
return f64(hr) + f64(nsec) / time.hour
return f64(d) / f64(time.hour)
}
// days returns the duration as a floating point number of days.
pub fn (d Duration) days() f64 {
return f64(d) / f64(time.hour * 24)
}
// str pretty prints the duration
@ -412,6 +436,35 @@ pub fn (d Duration) str() string {
return '${ns}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 * time.hour
'h': time.hour
'm': time.minute
's': time.second
'ms': time.millisecond
'us': time.microsecond
} {
if x > v {
xx := x / v
x = x % v
res << xx.str() + label
}
}
if x > 0 {
res << '${x}ns'
}
return 'Duration: ${sign}${res.join(', ')}'
}
// offset returns time zone UTC offset in seconds.
pub fn offset() int {
t := utc()

View File

@ -3,8 +3,6 @@ import time
fn test_add_to_day_in_the_previous_century() {
a := time.parse_iso8601('1900-01-01')!
aa := a.add_days(180)
dump(a.debug())
dump(aa.debug())
assert aa.ymmdd() == '1900-06-30'
}
@ -23,6 +21,8 @@ fn test_add_to_day_in_the_recent_past() {
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'
}

View File

@ -2,11 +2,10 @@ module time
#include <mach/mach_time.h>
const (
// start_time is needed on Darwin and Windows because of potential overflows
start_time = C.mach_absolute_time()
time_base = init_time_base()
)
// 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]
struct C.mach_timebase_info_data_t {
@ -25,11 +24,6 @@ struct InternalTimeBase {
denom u32 = 1
}
pub struct C.timeval {
tv_sec u64
tv_usec u64
}
fn init_time_base() C.mach_timebase_info_data_t {
tb := C.mach_timebase_info_data_t{}
C.mach_timebase_info(&tb)
@ -62,29 +56,22 @@ fn vpc_now_darwin() u64 {
return (tm - time.start_time) * time.time_base.numer / time.time_base.denom
}
// darwin_now returns a better precision current time for Darwin based operating system
// this should be implemented with native system calls eventually
// but for now a bit tweaky. It uses the deprecated gettimeofday clock to get
// the microseconds seconds part and converts to local time
// darwin_now returns a better precision current time for macos
fn darwin_now() Time {
// get the high precision time as UTC clock
tv := C.timeval{}
C.gettimeofday(&tv, 0)
// 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{}
asec := voidptr(&tv.tv_sec)
C.localtime_r(asec, &loc_tm)
return convert_ctime(loc_tm, int(tv.tv_usec))
C.localtime_r(voidptr(&ts.tv_sec), &loc_tm)
return convert_ctime(loc_tm, int(ts.tv_nsec))
}
// darwin_utc returns a better precision current time for Darwin based operating system
// this should be implemented with native system calls eventually
// but for now a bit tweaky. It uses the deprecated gettimeofday clock to get
// the microseconds seconds part and normal local time to get correct local time
// darwin_utc returns a better precision current time for macos
fn darwin_utc() Time {
// get the high precision time as UTC clock
tv := C.timeval{}
C.gettimeofday(&tv, 0)
return unix2(i64(tv.tv_sec), int(tv.tv_usec))
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

View File

@ -36,7 +36,7 @@ pub fn (t Time) local() Time {
}
loc_tm := C.tm{}
C.localtime_r(voidptr(&t.unix), &loc_tm)
return convert_ctime(loc_tm, t.microsecond)
return convert_ctime(loc_tm, t.nanosecond)
}
// in most systems, these are __quad_t, which is an i64
@ -58,7 +58,7 @@ pub fn sys_mono_now() u64 {
} $else {
ts := C.timespec{}
C.clock_gettime(C.CLOCK_MONOTONIC, &ts)
return u64(ts.tv_sec) * 1000000000 + u64(ts.tv_nsec)
return u64(ts.tv_sec) * 1_000_000_000 + u64(ts.tv_nsec)
}
}
@ -68,7 +68,7 @@ pub fn sys_mono_now() u64 {
fn vpc_now() u64 {
ts := C.timespec{}
C.clock_gettime(C.CLOCK_MONOTONIC, &ts)
return u64(ts.tv_sec) * 1000000000 + u64(ts.tv_nsec)
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
@ -83,7 +83,7 @@ fn linux_now() Time {
C.clock_gettime(C.CLOCK_REALTIME, &ts)
loc_tm := C.tm{}
C.localtime_r(voidptr(&ts.tv_sec), &loc_tm)
return convert_ctime(loc_tm, int(ts.tv_nsec / 1000))
return convert_ctime(loc_tm, int(ts.tv_nsec))
}
fn linux_utc() Time {
@ -91,7 +91,7 @@ fn linux_utc() Time {
// and use the nanoseconds part
mut ts := C.timespec{}
C.clock_gettime(C.CLOCK_REALTIME, &ts)
return unix2(i64(ts.tv_sec), int(ts.tv_nsec / 1000))
return unix_nanosecond(i64(ts.tv_sec), int(ts.tv_nsec))
}
// dummy to compile with all compilers
@ -104,12 +104,6 @@ fn win_utc() Time {
return Time{}
}
// dummy to compile with all compilers
pub struct C.timeval {
tv_sec u64
tv_usec u64
}
// return absolute timespec for now()+d
pub fn (d Duration) timespec() C.timespec {
mut ts := C.timespec{}

View File

@ -10,7 +10,7 @@ fn solaris_now() Time {
C.clock_gettime(C.CLOCK_REALTIME, &ts)
loc_tm := C.tm{}
C.localtime_r(voidptr(&ts.tv_sec), &loc_tm)
return convert_ctime(loc_tm, int(ts.tv_nsec / 1000))
return convert_ctime(loc_tm, int(ts.tv_nsec))
}
fn solaris_utc() Time {
@ -18,7 +18,7 @@ fn solaris_utc() Time {
// and use the nanoseconds part
mut ts := C.timespec{}
C.clock_gettime(C.CLOCK_REALTIME, &ts)
return unix2(i64(ts.tv_sec), int(ts.tv_nsec / 1000))
return unix_nanosecond(i64(ts.tv_sec), int(ts.tv_nsec))
}
// dummy to compile with all compilers

View File

@ -1,18 +1,16 @@
import time
import math
const (
time_to_test = time.Time{
year: 1980
month: 7
day: 11
hour: 21
minute: 23
second: 42
microsecond: 123456
unix: 332198622
}
)
const time_to_test = time.Time{
year: 1980
month: 7
day: 11
hour: 21
minute: 23
second: 42
nanosecond: 123456789
unix: 332198622
}
fn test_is_leap_year() {
// 1996 % 4 = 0 and 1996 % 100 > 0
@ -83,6 +81,14 @@ fn test_unix() {
assert t6.second == 29
}
fn test_format_rfc3339() {
// assert '1980-07-11T19:23:42.123Z'
res := time_to_test.format_rfc3339()
assert res.ends_with('23:42.123Z')
assert res.starts_with('1980-07-1')
assert res.contains('T')
}
fn test_format_ss() {
assert '11.07.1980 21:23:42' == time_to_test.get_fmt_str(.dot, .hhmmss24, .ddmmyyyy)
}
@ -93,20 +99,18 @@ fn test_format_ss_milli() {
assert '1980-07-11 21:23:42.123' == time_to_test.format_ss_milli()
}
fn test_format_rfc3339() {
// assert '1980-07-11T19:23:42.123Z'
res := time_to_test.format_rfc3339()
assert res.ends_with('23:42.123Z')
assert res.starts_with('1980-07-1')
assert res.contains('T')
}
fn test_format_ss_micro() {
assert '11.07.1980 21:23:42.123456' == time_to_test.get_fmt_str(.dot, .hhmmss24_micro,
.ddmmyyyy)
assert '1980-07-11 21:23:42.123456' == time_to_test.format_ss_micro()
}
fn test_format_ss_nano() {
assert '11.07.1980 21:23:42.123456789' == time_to_test.get_fmt_str(.dot, .hhmmss24_nano,
.ddmmyyyy)
assert '1980-07-11 21:23:42.123456789' == time_to_test.format_ss_nano()
}
fn test_smonth() {
month_names := ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov',
'Dec']
@ -180,21 +184,29 @@ fn test_weekday_str() {
fn test_add() {
d_seconds := 3
d_microseconds := 13
duration := time.Duration(d_seconds * time.second + d_microseconds * time.microsecond)
d_nanoseconds := 13
duration := time.Duration(d_seconds * time.second + d_nanoseconds * time.nanosecond)
// dump(duration.debug())
t1 := time_to_test
// dump(t1.debug())
t2 := time_to_test.add(duration)
// dump(t2.debug())
assert t2.second == t1.second + d_seconds
assert t2.microsecond == t1.microsecond + d_microseconds
assert t2.nanosecond == t1.nanosecond + d_nanoseconds
assert t2.unix == t1.unix + d_seconds
assert t2.is_local == t1.is_local
//
t3 := time_to_test.add(-duration)
// dump(t3.debug())
assert t3.second == t1.second - d_seconds
assert t3.microsecond == t1.microsecond - d_microseconds
assert t3.nanosecond == t1.nanosecond - d_nanoseconds
assert t3.unix == t1.unix - d_seconds
assert t3.is_local == t1.is_local
//
t4 := time_to_test.as_local()
// dump(t4.debug())
t5 := t4.add(duration)
// dump(t5.debug())
assert t5.is_local == t4.is_local
}
@ -220,13 +232,14 @@ fn test_now() {
assert now.minute < 60
assert now.second >= 0
assert now.second <= 60 // <= 60 cause of leap seconds
assert now.microsecond >= 0
assert now.microsecond < 1000000
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
@ -234,20 +247,20 @@ fn test_utc() {
assert now.minute < 60
assert now.second >= 0
assert now.second <= 60 // <= 60 cause of leap seconds
assert now.microsecond >= 0
assert now.microsecond < 1000000
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}')
eprintln(' t1: ${t1}')
eprintln(' t2: ${t2}')
ut1 := t1.unix_time()
ut2 := t2.unix_time()
eprintln('ut1: ${ut1}')
eprintln('ut2: ${ut2}')
eprintln(' ut1: ${ut1}')
eprintln(' ut2: ${ut2}')
assert ut2 - ut1 < 2
//
utm1 := t1.unix_time_milli()

View File

@ -39,6 +39,8 @@ fn C.SystemTimeToTzSpecificLocalTime(lpTimeZoneInformation &C.TIME_ZONE_INFORMAT
fn C.localtime_s(t &C.time_t, tm &C.tm)
fn C.timespec_get(t &C.timespec, base int) int
const (
// start_time is needed on Darwin and Windows because of potential overflows
start_time = init_win_time_start()
@ -107,7 +109,7 @@ pub fn (t Time) local() Time {
hour: u16(t.hour)
minute: u16(t.minute)
second: u16(t.second)
millisecond: u16(t.microsecond / 1000)
millisecond: u16(t.nanosecond / 1_000_000)
}
st_local := SystemTime{}
C.SystemTimeToTzSpecificLocalTime(unsafe { nil }, &st_utc, &st_local)
@ -118,7 +120,7 @@ pub fn (t Time) local() Time {
hour: st_local.hour
minute: st_local.minute
second: st_local.second // These are the same
microsecond: int(st_local.millisecond) * 1000
nanosecond: int(st_local.millisecond) * 1_000_000
unix: st_local.unix_time()
}
return t_local
@ -141,7 +143,7 @@ fn win_now() Time {
hour: st_local.hour
minute: st_local.minute
second: st_local.second
microsecond: int(st_local.millisecond) * 1000
nanosecond: int(st_local.millisecond) * 1_000_000
unix: st_local.unix_time()
is_local: true
}
@ -163,7 +165,7 @@ fn win_utc() Time {
hour: st_utc.hour
minute: st_utc.minute
second: st_utc.second
microsecond: int(st_utc.millisecond) * 1000
nanosecond: int(st_utc.millisecond) * 1_000_000
unix: st_utc.unix_time()
is_local: false
}
@ -213,12 +215,6 @@ fn solaris_utc() Time {
return Time{}
}
// dummy to compile with all compilers
pub struct C.timeval {
tv_sec u64
tv_usec u64
}
// sleep makes the calling thread sleep for a given duration (in nanoseconds).
pub fn sleep(duration Duration) {
C.Sleep(int(duration / millisecond))

View File

@ -3,7 +3,7 @@
// that can be found in the LICENSE file.
module time
// unix returns a time struct from Unix time.
// unix returns a time struct from an Unix timestamp (number of seconds since 1970-01-01)
pub fn unix(abs i64) Time {
// Split into day and time
mut day_offset := abs / seconds_per_day
@ -24,8 +24,20 @@ pub fn unix(abs i64) Time {
}
}
// unix2 returns a time struct from Unix time and microsecond value
// unix2 returns a Time struct, given an Unix timestamp in seconds, and a microsecond value
[deprecated: 'use unix_microsecond(unix_ts, us) instead']
[deprecated_after: '2023-09-05']
pub fn unix2(abs i64, microsecond int) Time {
return unix_nanosecond(abs, microsecond * 1000)
}
// unix_microsecond returns a Time struct, given an Unix timestamp in seconds, and a microsecond value
pub fn unix_microsecond(abs i64, microsecond int) Time {
return unix_nanosecond(abs, microsecond * 1000)
}
// unix_nanosecond returns a Time struct, given an Unix timestamp in seconds, and a nanosecond value
pub fn unix_nanosecond(abs i64, nanosecond int) Time {
// Split into day and time
mut day_offset := abs / seconds_per_day
if abs % seconds_per_day < 0 {
@ -41,7 +53,7 @@ pub fn unix2(abs i64, microsecond int) Time {
hour: hr
minute: min
second: sec
microsecond: microsecond
nanosecond: nanosecond
unix: abs
}
}