authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-07-10 11:26:28 -07:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-07-10 11:26:28 -07:00
log3051fe1421fc4e01caba7ac0b8ccdf8776a93441
tree37ff95c83b1d379e4fa9748d18ab3fb19667ab45
parent70ef29a006ceed7379df8801e3889da197de36ce
signaturebadge-check Signed by SSH key SHA256:4hHJbtBRU58AYXwjL7fkz2fnQHdiye8x1QpTCQ0sHNw

make timezone offset in accordance with temporal persistence model


3 files changed, 44 insertions(+), 14 deletions(-)

README.md-2
......@@ -10,8 +10,6 @@ Exposes a `DateTime` structure that can be initialized and acted upon using vari
1010
1111Currently handles dates and times based on the [Proleptic Gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar) in adherence to [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601).
1212
13Does not currently support time zones outside of UTC.
14
1513Does not handle leap seconds.
1614
1715See the `FormatSeq` structure for display information on what to pass as a `fmt` string.
test.zig+19-3
......@@ -120,7 +120,6 @@ comptime {
120120 .{ "SS", "78" },
121121 .{ "SSS", "789" },
122122
123 .{ "z", "UTC" },
124123 .{ "Z", "+00:00" },
125124 .{ "ZZ", "+0000" },
126125
......@@ -155,7 +154,24 @@ comptime {
155154// https://github.com/nektro/zig-time/issues/9
156155test {
157156 var t = time.DateTime.initUnix(1330502962);
158 try expectFmt(t, "YYYY-MM-DD hh:mm:ss A z", "2012-02-29 08:09:22 AM UTC");
157 try expectFmt(t, "YYYY-MM-DD hh:mm:ss A", "2012-02-29 08:09:22 AM");
159158 t = t.addYears(1);
160 try expectFmt(t, "YYYY-MM-DD hh:mm:ss A z", "2013-03-01 08:09:22 AM UTC");
159 try expectFmt(t, "YYYY-MM-DD hh:mm:ss A", "2013-03-01 08:09:22 AM");
160}
161
162test {
163 var t = time.DateTime.initUnix(1330502962);
164 try expectFmt(t, "Z", "+00:00");
165 t.z_offset += 31;
166 try expectFmt(t, "Z", "+07:45");
167 t.z_offset -= 65;
168 try expectFmt(t, "Z", "-08:30");
169}
170test {
171 var t1 = time.DateTime.init(2026, 7, 10, 11, 14, 45, 0);
172 t1.z_offset -= 28;
173 try expectFmt(t1, "Z", "-07:00");
174 var t2 = time.DateTime.init(2026, 7, 10, 18, 14, 45, 0);
175 try expectFmt(t2, "Z", "+00:00");
176 try expect(t1.toUnix()).toEqual(t2.toUnix());
161177}
time.zig+25-9
......@@ -19,7 +19,7 @@ pub const DateTime = struct {
1919 days: u8,
2020 months: u8,
2121 years: u16,
22 timezone: TimeZone,
22 z_offset: i8,
2323
2424 pub fn initUnixMs(unix: u64) DateTime {
2525 return epoch_unix.addMs(unix);
......@@ -53,7 +53,7 @@ pub const DateTime = struct {
5353 .days = 0,
5454 .months = 0,
5555 .years = 1970,
56 .timezone = .UTC,
56 .z_offset = 0,
5757 };
5858
5959 pub fn toISOString(self: DateTime) [20]u8 {
......@@ -220,8 +220,7 @@ pub const DateTime = struct {
220220 }
221221
222222 pub fn toUnix(self: DateTime) u64 {
223 const x = self.toUnixMilli();
224 return x / 1000;
223 return self.toUnixMilli() / 1000;
225224 }
226225
227226 pub fn toUnixMilli(self: DateTime) u64 {
......@@ -231,6 +230,7 @@ pub const DateTime = struct {
231230 res += @as(u64, self.minutes) * ms_per_min;
232231 res += @as(u64, self.hours) * ms_per_hour;
233232 res += self.daysSinceEpoch() * ms_per_day;
233 res = extras.safeAdd(res, @as(i32, -self.z_offset) * 15 * ms_per_min);
234234 return res;
235235 }
236236
......@@ -249,6 +249,19 @@ pub const DateTime = struct {
249249 return res;
250250 }
251251
252 /// the timezone offset is stored as a signed amount of 15-min increments.
253 /// the structure of the return value is .{ sign, hrs, mins }.
254 /// 'sign' will be either '+' or '-'
255 pub fn tz_parts(self: DateTime) [3]u8 {
256 const o = self.z_offset;
257 const a = @abs(o);
258 return .{
259 if (o < 0) '-' else '+',
260 a / 4,
261 (a % 4) * 15,
262 };
263 }
264
252265 /// fmt is based on https://momentjs.com/docs/#/displaying/format/
253266 pub fn formatFmt(self: DateTime, comptime fmt: string, writer: anytype) !void {
254267 if (fmt.len == 0) @compileError("DateTime: format string can't be empty");
......@@ -325,9 +338,8 @@ pub const DateTime = struct {
325338 .SS => try writer.print("{:0>2}", .{self.ms / 10}),
326339 .SSS => try writer.print("{:0>3}", .{self.ms}),
327340
328 .z => try writer.writeAll(@tagName(self.timezone)),
329 .Z => try writer.writeAll("+00:00"),
330 .ZZ => try writer.writeAll("+0000"),
341 .Z => try writer.print("{c}{:0>2}:{:0>2}", toTuple(self.tz_parts())),
342 .ZZ => try writer.print("{c}{:0>2}{:0>2}", toTuple(self.tz_parts())),
331343
332344 .x => try writer.print("{}", .{self.toUnixMilli()}),
333345 .X => try writer.print("{}", .{self.toUnix()}),
......@@ -358,7 +370,6 @@ pub const DateTime = struct {
358370 pub fn formatAlloc(self: DateTime, alloc: std.mem.Allocator, comptime fmt: string) !string {
359371 var list = std.Io.Writer.Allocating.init(alloc);
360372 defer list.deinit();
361
362373 try self.formatFmt(fmt, &list.writer);
363374 return list.toOwnedSlice();
364375 }
......@@ -408,7 +419,6 @@ pub const DateTime = struct {
408419 S, // 0 1 ... 8 9 (second fraction)
409420 SS, // 00 01 ... 98 99
410421 SSS, // 000 001 ... 998 999
411 z, // EST CST ... MST PST
412422 Z, // -07:00 -06:00 ... +06:00 +07:00
413423 ZZ, // -0700 -0600 ... +0600 +0700
414424 x, // unix milli
......@@ -530,6 +540,12 @@ fn wrap(val: u16, at: u16) u16 {
530540 return if (tmp == 0) at else tmp;
531541}
532542
543fn toTuple(array: anytype) @Tuple(&@as([array.len]type, @splat(std.meta.Child(@TypeOf(array))))) {
544 var result: @Tuple(&@as([array.len]type, @splat(std.meta.Child(@TypeOf(array))))) = undefined;
545 inline for (array, 0..) |v, i| result[i] = v;
546 return result;
547}
548
533549pub const Duration = struct {
534550 ms: u64,
535551};