time: always return utc() timezone for Time.unix/0 (fix #17784) (#25233)
Some checks are pending
Graphics CI / gg-regressions (push) Waiting to run
vlib modules CI / build-module-docs (push) Waiting to run
native backend CI / native-backend-windows (push) Waiting to run
native backend CI / native-backend-ubuntu (push) Waiting to run
Shy and PV CI / v-compiles-puzzle-vibes (push) Waiting to run
Sanitized CI / sanitize-undefined-clang (push) Waiting to run
Sanitized CI / sanitize-undefined-gcc (push) Waiting to run
Sanitized CI / tests-sanitize-address-clang (push) Waiting to run
Sanitized CI / sanitize-address-msvc (push) Waiting to run
Sanitized CI / sanitize-address-gcc (push) Waiting to run
Sanitized CI / sanitize-memory-clang (push) Waiting to run
sdl CI / v-compiles-sdl-examples (push) Waiting to run
Time CI / time-linux (push) Waiting to run
Time CI / time-macos (push) Waiting to run
Time CI / time-windows (push) Waiting to run
toml CI / toml-module-pass-external-test-suites (push) Waiting to run
Tools CI / tools-linux (clang) (push) Waiting to run
Tools CI / tools-linux (gcc) (push) Waiting to run
Tools CI / tools-linux (tcc) (push) Waiting to run
Tools CI / tools-macos (clang) (push) Waiting to run
Tools CI / tools-windows (gcc) (push) Waiting to run
Tools CI / tools-windows (msvc) (push) Waiting to run
Tools CI / tools-windows (tcc) (push) Waiting to run
Tools CI / tools-docker-ubuntu-musl (push) Waiting to run
vab CI / vab-compiles-v-examples (push) Waiting to run
vab CI / v-compiles-os-android (push) Waiting to run
wasm backend CI / wasm-backend (ubuntu-22.04) (push) Waiting to run
wasm backend CI / wasm-backend (windows-2022) (push) Waiting to run

This commit is contained in:
Leo Developer 2025-09-04 23:22:52 +02:00 committed by GitHub
parent dbd5b5f56c
commit 2b4253caf9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 38 additions and 46 deletions

View file

@ -393,7 +393,9 @@ fn test_orm() {
// Note: usually updated_time_mod.created != t, because t has
// its microseconds set, while the value retrieved from the DB
// has them zeroed, because the db field resolution is seconds.
assert modules.first().created.format_ss() == t.format_ss()
// Note: the database also stores the time in UTC, so the
// comparison must be done on the unix timestamp.
assert modules.first().created.unix() == t.unix()
users = sql db {
select from User where (name == 'Sam' && is_customer == true) || id == 1

View file

@ -3,13 +3,16 @@ 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.nanosecond == t2.nanosecond
return t1.is_local == t2.is_local && t1.local_unix() == t2.local_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.nanosecond < t2.nanosecond)
t1u := t1.unix()
t2u := t2.unix()
return t1u < t2u || (t1u == t2u && t1.nanosecond < t2.nanosecond)
}
// Time subtract using operator overloading.

View file

@ -1,14 +0,0 @@
// tests that use and test private functions
module time
// test the old behavior is same as new, the unix time should always be local time
fn test_new_is_same_as_old_for_all_platforms() {
t := C.time(0)
tm := C.localtime(&t)
old_time := convert_ctime(tm, 0)
new_time := now()
diff := new_time.unix - old_time.unix
// could in very rare cases be that the second changed between calls
dump(diff)
assert (diff >= -2 && diff <= 2) == true
}

View file

@ -106,6 +106,12 @@ pub fn (t Time) smonth() string {
// 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
}
@ -135,14 +141,26 @@ pub fn (t Time) add(duration_in_nanosecond Duration) Time {
// ... 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.unix() + (increased_time_nanosecond / 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))
return if t.is_local { res.as_local() } else { res }
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.
@ -177,7 +195,7 @@ pub fn since(t Time) Duration {
// ```
pub fn (t Time) relative() string {
znow := now()
mut secs := znow.unix - t.unix()
mut secs := znow.unix() - t.unix()
mut prefix := ''
mut suffix := ''
if secs < 0 {
@ -239,7 +257,7 @@ pub fn (t Time) relative() string {
// ```
pub fn (t Time) relative_short() string {
znow := now()
mut secs := znow.unix - t.unix()
mut secs := znow.unix() - t.unix()
mut prefix := ''
mut suffix := ''
if secs < 0 {
@ -364,9 +382,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 nanosecond: nanos 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 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} }'
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.

View file

@ -12,7 +12,7 @@ const time_to_test = time.Time{
fn test_now_format() {
t := time.now()
u := t.unix()
assert t.format() == time.unix(int(u)).format()
assert t.format() == time.unix(int(u)).utc_to_local().format()
}
fn test_format() {

View file

@ -15,7 +15,7 @@ fn test_tm_gmtoff() {
dump(t2)
dump(t1.nanosecond)
dump(t2.nanosecond)
diff := int(t1.unix() - t2.unix())
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]

View file

@ -90,17 +90,10 @@ fn test_unix() {
assert t6.hour == 6
assert t6.minute == 9
assert t6.second == 29
assert local_time_to_test.unix() == 332198622
assert utc_time_to_test.unix() == 332198622
}
fn test_format_rfc3339() {
// assert '1980-07-11T19:23:42.123Z'
res := local_time_to_test.format_rfc3339()
assert res.ends_with('23:42.123Z')
assert res.starts_with('1980-07-1')
assert res.contains('T')
// assert '1980-07-11T19:23:42.123Z'
utc_res := utc_time_to_test.format_rfc3339()
assert utc_res.ends_with('23:42.123Z')
@ -109,11 +102,6 @@ fn test_format_rfc3339() {
}
fn test_format_rfc3339_micro() {
res := local_time_to_test.format_rfc3339_micro()
assert res.ends_with('23:42.123456Z')
assert res.starts_with('1980-07-1')
assert res.contains('T')
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')
@ -121,11 +109,6 @@ fn test_format_rfc3339_micro() {
}
fn test_format_rfc3339_nano() {
res := local_time_to_test.format_rfc3339_nano()
assert res.ends_with('23:42.123456789Z')
assert res.starts_with('1980-07-1')
assert res.contains('T')
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')

View file

@ -417,7 +417,7 @@ fn (mut g Gen) gen_sumtype_enc_dec(utyp ast.Type, sym ast.TypeSymbol, mut enc st
if variant_sym.kind == .enum {
enc.writeln('\t\tcJSON_AddItemToObject(o, "${unmangled_variant_name}", ${js_enc_name('u64')}(*${var_data}${field_op}_${variant_typ}));')
} else if variant_sym.name == 'time.Time' {
enc.writeln('\t\tcJSON_AddItemToObject(o, "${unmangled_variant_name}", ${js_enc_name('i64')}(${var_data}${field_op}_${variant_typ}->__v_unix));')
enc.writeln('\t\tcJSON_AddItemToObject(o, "${unmangled_variant_name}", ${js_enc_name('i64')}(time__Time_unix(*${var_data}${field_op}_${variant_typ})));')
} else {
enc.writeln('\t\tcJSON_AddItemToObject(o, "${unmangled_variant_name}", ${js_enc_name(variant_typ)}(*${var_data}${field_op}_${variant_typ}));')
}
@ -442,7 +442,7 @@ fn (mut g Gen) gen_sumtype_enc_dec(utyp ast.Type, sym ast.TypeSymbol, mut enc st
}
} else if variant_sym.name == 'time.Time' {
enc.writeln('\t\tcJSON_AddItemToObject(o, "_type", cJSON_CreateString("${unmangled_variant_name}"));')
enc.writeln('\t\tcJSON_AddItemToObject(o, "value", ${js_enc_name('i64')}(${var_data}${field_op}_${variant_typ}->__v_unix));')
enc.writeln('\t\tcJSON_AddItemToObject(o, "value", ${js_enc_name('i64')}(time__Time_unix(*${var_data}${field_op}_${variant_typ})));')
} else {
enc.writeln('\t\tcJSON_free(o);')
enc.writeln('\t\to = ${js_enc_name(variant_typ)}(*${var_data}${field_op}_${variant_typ});')
@ -954,9 +954,9 @@ fn (mut g Gen) gen_struct_enc_dec(utyp ast.Type, type_info ast.TypeInfo, styp st
// time struct requires special treatment
// it has to be encoded as a unix timestamp number
if is_option {
enc.writeln('${indent}cJSON_AddItemToObject(o, "${name}", json__encode_u64((*(${g.base_type(field.typ)}*)(${prefix_enc}${op}${c_name(field.name)}.data)).__v_unix));')
enc.writeln('${indent}cJSON_AddItemToObject(o, "${name}", json__encode_u64(time__Time_unix(*(${g.base_type(field.typ)}*)(${prefix_enc}${op}${c_name(field.name)}.data))));')
} else {
enc.writeln('${indent}cJSON_AddItemToObject(o, "${name}", json__encode_u64(${prefix_enc}${op}${c_name(field.name)}.__v_unix));')
enc.writeln('${indent}cJSON_AddItemToObject(o, "${name}", json__encode_u64(time__Time_unix(${prefix_enc}${op}${c_name(field.name)})));')
}
} else {
if !field.typ.is_any_kind_of_pointer() {