Skip to content

Commit 2b4253c

Browse files
authored
time: always return utc() timezone for Time.unix/0 (fix #17784) (#25233)
1 parent dbd5b5f commit 2b4253c

8 files changed

Lines changed: 38 additions & 46 deletions

File tree

vlib/orm/orm_test.v

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,9 @@ fn test_orm() {
393393
// Note: usually updated_time_mod.created != t, because t has
394394
// its microseconds set, while the value retrieved from the DB
395395
// has them zeroed, because the db field resolution is seconds.
396-
assert modules.first().created.format_ss() == t.format_ss()
396+
// Note: the database also stores the time in UTC, so the
397+
// comparison must be done on the unix timestamp.
398+
assert modules.first().created.unix() == t.unix()
397399

398400
users = sql db {
399401
select from User where (name == 'Sam' && is_customer == true) || id == 1

vlib/time/operator.v

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ module time
33
// operator `==` returns true if provided time is equal to time
44
@[inline]
55
pub fn (t1 Time) == (t2 Time) bool {
6-
return t1.unix() == t2.unix() && t1.nanosecond == t2.nanosecond
6+
return t1.is_local == t2.is_local && t1.local_unix() == t2.local_unix()
7+
&& t1.nanosecond == t2.nanosecond
78
}
89

910
// operator `<` returns true if provided time is less than time
1011
@[inline]
1112
pub fn (t1 Time) < (t2 Time) bool {
12-
return t1.unix() < t2.unix() || (t1.unix() == t2.unix() && t1.nanosecond < t2.nanosecond)
13+
t1u := t1.unix()
14+
t2u := t2.unix()
15+
return t1u < t2u || (t1u == t2u && t1.nanosecond < t2.nanosecond)
1316
}
1417

1518
// Time subtract using operator overloading.

vlib/time/private_test.c.v

Lines changed: 0 additions & 14 deletions
This file was deleted.

vlib/time/time.v

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ pub fn (t Time) smonth() string {
106106
// unix returns the UNIX time with second resolution.
107107
@[inline]
108108
pub fn (t Time) unix() i64 {
109+
return time_with_unix(t.local_to_utc()).unix
110+
}
111+
112+
// local_unix returns the UNIX local time with second resolution.
113+
@[inline]
114+
pub fn (t Time) local_unix() i64 {
109115
return time_with_unix(t).unix
110116
}
111117

@@ -135,14 +141,26 @@ pub fn (t Time) add(duration_in_nanosecond Duration) Time {
135141
// ... so instead, handle the addition manually in parts ¯\_(ツ)_/¯
136142
mut increased_time_nanosecond := i64(t.nanosecond) + duration_in_nanosecond.nanoseconds()
137143
// increased_time_second
138-
mut increased_time_second := t.unix() + (increased_time_nanosecond / second)
144+
mut increased_time_second := t.local_unix() + (increased_time_nanosecond / second)
139145
increased_time_nanosecond = increased_time_nanosecond % second
140146
if increased_time_nanosecond < 0 {
141147
increased_time_second--
142148
increased_time_nanosecond += second
143149
}
144150
res := unix_nanosecond(increased_time_second, int(increased_time_nanosecond))
145-
return if t.is_local { res.as_local() } else { res }
151+
152+
if t.is_local {
153+
// we need to reset unix to 0, because we don't know the offset
154+
// and we can't calculate it without it without causing infinite recursion
155+
// so unfortunately we need to recalculate unix next time it is needed
156+
return Time{
157+
...res
158+
is_local: true
159+
unix: 0
160+
}
161+
}
162+
163+
return res
146164
}
147165

148166
// add_seconds returns a new time struct with an added number of seconds.
@@ -177,7 +195,7 @@ pub fn since(t Time) Duration {
177195
// ```
178196
pub fn (t Time) relative() string {
179197
znow := now()
180-
mut secs := znow.unix - t.unix()
198+
mut secs := znow.unix() - t.unix()
181199
mut prefix := ''
182200
mut suffix := ''
183201
if secs < 0 {
@@ -239,7 +257,7 @@ pub fn (t Time) relative() string {
239257
// ```
240258
pub fn (t Time) relative_short() string {
241259
znow := now()
242-
mut secs := znow.unix - t.unix()
260+
mut secs := znow.unix() - t.unix()
243261
mut prefix := ''
244262
mut suffix := ''
245263
if secs < 0 {
@@ -364,9 +382,9 @@ pub fn days_in_month(month int, year int) !int {
364382
return res
365383
}
366384

367-
// debug returns detailed breakdown of time (`Time{ year: YYYY month: MM day: dd hour: HH: minute: mm second: ss nanosecond: nanos unix: unix }`).
385+
// 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 }`).
368386
pub fn (t Time) debug() string {
369-
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} }'
387+
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} }'
370388
}
371389

372390
// offset returns time zone UTC offset in seconds.

vlib/time/time_format_test.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const time_to_test = time.Time{
1212
fn test_now_format() {
1313
t := time.now()
1414
u := t.unix()
15-
assert t.format() == time.unix(int(u)).format()
15+
assert t.format() == time.unix(int(u)).utc_to_local().format()
1616
}
1717

1818
fn test_format() {

vlib/time/time_test.c.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ fn test_tm_gmtoff() {
1515
dump(t2)
1616
dump(t1.nanosecond)
1717
dump(t2.nanosecond)
18-
diff := int(t1.unix() - t2.unix())
18+
diff := int(t1.local_unix() - t2.unix())
1919
dump(diff)
2020
dump(info.tm_gmtoff)
2121
assert diff in [info.tm_gmtoff - 1, info.tm_gmtoff, info.tm_gmtoff + 1]

vlib/time/time_test.v

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -90,17 +90,10 @@ fn test_unix() {
9090
assert t6.hour == 6
9191
assert t6.minute == 9
9292
assert t6.second == 29
93-
assert local_time_to_test.unix() == 332198622
9493
assert utc_time_to_test.unix() == 332198622
9594
}
9695

9796
fn test_format_rfc3339() {
98-
// assert '1980-07-11T19:23:42.123Z'
99-
res := local_time_to_test.format_rfc3339()
100-
assert res.ends_with('23:42.123Z')
101-
assert res.starts_with('1980-07-1')
102-
assert res.contains('T')
103-
10497
// assert '1980-07-11T19:23:42.123Z'
10598
utc_res := utc_time_to_test.format_rfc3339()
10699
assert utc_res.ends_with('23:42.123Z')
@@ -109,23 +102,13 @@ fn test_format_rfc3339() {
109102
}
110103

111104
fn test_format_rfc3339_micro() {
112-
res := local_time_to_test.format_rfc3339_micro()
113-
assert res.ends_with('23:42.123456Z')
114-
assert res.starts_with('1980-07-1')
115-
assert res.contains('T')
116-
117105
utc_res := utc_time_to_test.format_rfc3339_micro()
118106
assert utc_res.ends_with('23:42.123456Z')
119107
assert utc_res.starts_with('1980-07-1')
120108
assert utc_res.contains('T')
121109
}
122110

123111
fn test_format_rfc3339_nano() {
124-
res := local_time_to_test.format_rfc3339_nano()
125-
assert res.ends_with('23:42.123456789Z')
126-
assert res.starts_with('1980-07-1')
127-
assert res.contains('T')
128-
129112
utc_res := utc_time_to_test.format_rfc3339_nano()
130113
assert utc_res.ends_with('23:42.123456789Z')
131114
assert utc_res.starts_with('1980-07-1')

vlib/v/gen/c/json.v

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ fn (mut g Gen) gen_sumtype_enc_dec(utyp ast.Type, sym ast.TypeSymbol, mut enc st
417417
if variant_sym.kind == .enum {
418418
enc.writeln('\t\tcJSON_AddItemToObject(o, "${unmangled_variant_name}", ${js_enc_name('u64')}(*${var_data}${field_op}_${variant_typ}));')
419419
} else if variant_sym.name == 'time.Time' {
420-
enc.writeln('\t\tcJSON_AddItemToObject(o, "${unmangled_variant_name}", ${js_enc_name('i64')}(${var_data}${field_op}_${variant_typ}->__v_unix));')
420+
enc.writeln('\t\tcJSON_AddItemToObject(o, "${unmangled_variant_name}", ${js_enc_name('i64')}(time__Time_unix(*${var_data}${field_op}_${variant_typ})));')
421421
} else {
422422
enc.writeln('\t\tcJSON_AddItemToObject(o, "${unmangled_variant_name}", ${js_enc_name(variant_typ)}(*${var_data}${field_op}_${variant_typ}));')
423423
}
@@ -442,7 +442,7 @@ fn (mut g Gen) gen_sumtype_enc_dec(utyp ast.Type, sym ast.TypeSymbol, mut enc st
442442
}
443443
} else if variant_sym.name == 'time.Time' {
444444
enc.writeln('\t\tcJSON_AddItemToObject(o, "_type", cJSON_CreateString("${unmangled_variant_name}"));')
445-
enc.writeln('\t\tcJSON_AddItemToObject(o, "value", ${js_enc_name('i64')}(${var_data}${field_op}_${variant_typ}->__v_unix));')
445+
enc.writeln('\t\tcJSON_AddItemToObject(o, "value", ${js_enc_name('i64')}(time__Time_unix(*${var_data}${field_op}_${variant_typ})));')
446446
} else {
447447
enc.writeln('\t\tcJSON_free(o);')
448448
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
954954
// time struct requires special treatment
955955
// it has to be encoded as a unix timestamp number
956956
if is_option {
957-
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));')
957+
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))));')
958958
} else {
959-
enc.writeln('${indent}cJSON_AddItemToObject(o, "${name}", json__encode_u64(${prefix_enc}${op}${c_name(field.name)}.__v_unix));')
959+
enc.writeln('${indent}cJSON_AddItemToObject(o, "${name}", json__encode_u64(time__Time_unix(${prefix_enc}${op}${c_name(field.name)})));')
960960
}
961961
} else {
962962
if !field.typ.is_any_kind_of_pointer() {

0 commit comments

Comments
 (0)