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...@@ -10,8 +10,6 @@ Exposes a `DateTime` structure that can be initialized and acted upon using vari
1010
11Currently 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).11Currently 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
15Does not handle leap seconds.13Does not handle leap seconds.
1614
17See the `FormatSeq` structure for display information on what to pass as a `fmt` string.15See the `FormatSeq` structure for display information on what to pass as a `fmt` string.
test.zig+19-3
...@@ -120,7 +120,6 @@ comptime {...@@ -120,7 +120,6 @@ comptime {
120 .{ "SS", "78" },120 .{ "SS", "78" },
121 .{ "SSS", "789" },121 .{ "SSS", "789" },
122122
123 .{ "z", "UTC" },
124 .{ "Z", "+00:00" },123 .{ "Z", "+00:00" },
125 .{ "ZZ", "+0000" },124 .{ "ZZ", "+0000" },
126125
...@@ -155,7 +154,24 @@ comptime {...@@ -155,7 +154,24 @@ comptime {
155// https://github.com/nektro/zig-time/issues/9154// https://github.com/nektro/zig-time/issues/9
156test {155test {
157 var t = time.DateTime.initUnix(1330502962);156 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");
159 t = t.addYears(1);158 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());
161}177}
time.zig+25-9
...@@ -19,7 +19,7 @@ pub const DateTime = struct {...@@ -19,7 +19,7 @@ pub const DateTime = struct {
19 days: u8,19 days: u8,
20 months: u8,20 months: u8,
21 years: u16,21 years: u16,
22 timezone: TimeZone,22 z_offset: i8,
2323
24 pub fn initUnixMs(unix: u64) DateTime {24 pub fn initUnixMs(unix: u64) DateTime {
25 return epoch_unix.addMs(unix);25 return epoch_unix.addMs(unix);
...@@ -53,7 +53,7 @@ pub const DateTime = struct {...@@ -53,7 +53,7 @@ pub const DateTime = struct {
53 .days = 0,53 .days = 0,
54 .months = 0,54 .months = 0,
55 .years = 1970,55 .years = 1970,
56 .timezone = .UTC,56 .z_offset = 0,
57 };57 };
5858
59 pub fn toISOString(self: DateTime) [20]u8 {59 pub fn toISOString(self: DateTime) [20]u8 {
...@@ -220,8 +220,7 @@ pub const DateTime = struct {...@@ -220,8 +220,7 @@ pub const DateTime = struct {
220 }220 }
221221
222 pub fn toUnix(self: DateTime) u64 {222 pub fn toUnix(self: DateTime) u64 {
223 const x = self.toUnixMilli();223 return self.toUnixMilli() / 1000;
224 return x / 1000;
225 }224 }
226225
227 pub fn toUnixMilli(self: DateTime) u64 {226 pub fn toUnixMilli(self: DateTime) u64 {
...@@ -231,6 +230,7 @@ pub const DateTime = struct {...@@ -231,6 +230,7 @@ pub const DateTime = struct {
231 res += @as(u64, self.minutes) * ms_per_min;230 res += @as(u64, self.minutes) * ms_per_min;
232 res += @as(u64, self.hours) * ms_per_hour;231 res += @as(u64, self.hours) * ms_per_hour;
233 res += self.daysSinceEpoch() * ms_per_day;232 res += self.daysSinceEpoch() * ms_per_day;
233 res = extras.safeAdd(res, @as(i32, -self.z_offset) * 15 * ms_per_min);
234 return res;234 return res;
235 }235 }
236236
...@@ -249,6 +249,19 @@ pub const DateTime = struct {...@@ -249,6 +249,19 @@ pub const DateTime = struct {
249 return res;249 return res;
250 }250 }
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
252 /// fmt is based on https://momentjs.com/docs/#/displaying/format/265 /// fmt is based on https://momentjs.com/docs/#/displaying/format/
253 pub fn formatFmt(self: DateTime, comptime fmt: string, writer: anytype) !void {266 pub fn formatFmt(self: DateTime, comptime fmt: string, writer: anytype) !void {
254 if (fmt.len == 0) @compileError("DateTime: format string can't be empty");267 if (fmt.len == 0) @compileError("DateTime: format string can't be empty");
...@@ -325,9 +338,8 @@ pub const DateTime = struct {...@@ -325,9 +338,8 @@ pub const DateTime = struct {
325 .SS => try writer.print("{:0>2}", .{self.ms / 10}),338 .SS => try writer.print("{:0>2}", .{self.ms / 10}),
326 .SSS => try writer.print("{:0>3}", .{self.ms}),339 .SSS => try writer.print("{:0>3}", .{self.ms}),
327340
328 .z => try writer.writeAll(@tagName(self.timezone)),341 .Z => try writer.print("{c}{:0>2}:{:0>2}", toTuple(self.tz_parts())),
329 .Z => try writer.writeAll("+00:00"),342 .ZZ => try writer.print("{c}{:0>2}{:0>2}", toTuple(self.tz_parts())),
330 .ZZ => try writer.writeAll("+0000"),
331343
332 .x => try writer.print("{}", .{self.toUnixMilli()}),344 .x => try writer.print("{}", .{self.toUnixMilli()}),
333 .X => try writer.print("{}", .{self.toUnix()}),345 .X => try writer.print("{}", .{self.toUnix()}),
...@@ -358,7 +370,6 @@ pub const DateTime = struct {...@@ -358,7 +370,6 @@ pub const DateTime = struct {
358 pub fn formatAlloc(self: DateTime, alloc: std.mem.Allocator, comptime fmt: string) !string {370 pub fn formatAlloc(self: DateTime, alloc: std.mem.Allocator, comptime fmt: string) !string {
359 var list = std.Io.Writer.Allocating.init(alloc);371 var list = std.Io.Writer.Allocating.init(alloc);
360 defer list.deinit();372 defer list.deinit();
361
362 try self.formatFmt(fmt, &list.writer);373 try self.formatFmt(fmt, &list.writer);
363 return list.toOwnedSlice();374 return list.toOwnedSlice();
364 }375 }
...@@ -408,7 +419,6 @@ pub const DateTime = struct {...@@ -408,7 +419,6 @@ pub const DateTime = struct {
408 S, // 0 1 ... 8 9 (second fraction)419 S, // 0 1 ... 8 9 (second fraction)
409 SS, // 00 01 ... 98 99420 SS, // 00 01 ... 98 99
410 SSS, // 000 001 ... 998 999421 SSS, // 000 001 ... 998 999
411 z, // EST CST ... MST PST
412 Z, // -07:00 -06:00 ... +06:00 +07:00422 Z, // -07:00 -06:00 ... +06:00 +07:00
413 ZZ, // -0700 -0600 ... +0600 +0700423 ZZ, // -0700 -0600 ... +0600 +0700
414 x, // unix milli424 x, // unix milli
...@@ -530,6 +540,12 @@ fn wrap(val: u16, at: u16) u16 {...@@ -530,6 +540,12 @@ fn wrap(val: u16, at: u16) u16 {
530 return if (tmp == 0) at else tmp;540 return if (tmp == 0) at else tmp;
531}541}
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
533pub const Duration = struct {549pub const Duration = struct {
534 ms: u64,550 ms: u64,
535};551};