diff --git a/README.md b/README.md index bafeed3837a68b8d70fd0c840e8f6f72df515e37..84af43e578e1ce4a5138c54b9f8aef753d29b927 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,6 @@ Exposes a `DateTime` structure that can be initialized and acted upon using vari Currently 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). -Does not currently support time zones outside of UTC. - Does not handle leap seconds. See the `FormatSeq` structure for display information on what to pass as a `fmt` string. diff --git a/test.zig b/test.zig index 8d89c076b87f29d9564e0b2768eddbfc612a843c..c538038388dd84171259003a232bb63078d9d0cb 100644 --- a/test.zig +++ b/test.zig @@ -120,7 +120,6 @@ comptime { .{ "SS", "78" }, .{ "SSS", "789" }, - .{ "z", "UTC" }, .{ "Z", "+00:00" }, .{ "ZZ", "+0000" }, @@ -155,7 +154,24 @@ comptime { // https://github.com/nektro/zig-time/issues/9 test { var t = time.DateTime.initUnix(1330502962); - try expectFmt(t, "YYYY-MM-DD hh:mm:ss A z", "2012-02-29 08:09:22 AM UTC"); + try expectFmt(t, "YYYY-MM-DD hh:mm:ss A", "2012-02-29 08:09:22 AM"); t = t.addYears(1); - try expectFmt(t, "YYYY-MM-DD hh:mm:ss A z", "2013-03-01 08:09:22 AM UTC"); + try expectFmt(t, "YYYY-MM-DD hh:mm:ss A", "2013-03-01 08:09:22 AM"); +} + +test { + var t = time.DateTime.initUnix(1330502962); + try expectFmt(t, "Z", "+00:00"); + t.z_offset += 31; + try expectFmt(t, "Z", "+07:45"); + t.z_offset -= 65; + try expectFmt(t, "Z", "-08:30"); +} +test { + var t1 = time.DateTime.init(2026, 7, 10, 11, 14, 45, 0); + t1.z_offset -= 28; + try expectFmt(t1, "Z", "-07:00"); + var t2 = time.DateTime.init(2026, 7, 10, 18, 14, 45, 0); + try expectFmt(t2, "Z", "+00:00"); + try expect(t1.toUnix()).toEqual(t2.toUnix()); } diff --git a/time.zig b/time.zig index 09ce9ec4b006acf7a6f3ee06584809a8765ad5ac..a080f8db9aa656d96b09e5360149dae28473a612 100644 --- a/time.zig +++ b/time.zig @@ -19,7 +19,7 @@ pub const DateTime = struct { days: u8, months: u8, years: u16, - timezone: TimeZone, + z_offset: i8, pub fn initUnixMs(unix: u64) DateTime { return epoch_unix.addMs(unix); @@ -53,7 +53,7 @@ pub const DateTime = struct { .days = 0, .months = 0, .years = 1970, - .timezone = .UTC, + .z_offset = 0, }; pub fn toISOString(self: DateTime) [20]u8 { @@ -220,8 +220,7 @@ pub const DateTime = struct { } pub fn toUnix(self: DateTime) u64 { - const x = self.toUnixMilli(); - return x / 1000; + return self.toUnixMilli() / 1000; } pub fn toUnixMilli(self: DateTime) u64 { @@ -231,6 +230,7 @@ pub const DateTime = struct { res += @as(u64, self.minutes) * ms_per_min; res += @as(u64, self.hours) * ms_per_hour; res += self.daysSinceEpoch() * ms_per_day; + res = extras.safeAdd(res, @as(i32, -self.z_offset) * 15 * ms_per_min); return res; } @@ -249,6 +249,19 @@ pub const DateTime = struct { return res; } + /// the timezone offset is stored as a signed amount of 15-min increments. + /// the structure of the return value is .{ sign, hrs, mins }. + /// 'sign' will be either '+' or '-' + pub fn tz_parts(self: DateTime) [3]u8 { + const o = self.z_offset; + const a = @abs(o); + return .{ + if (o < 0) '-' else '+', + a / 4, + (a % 4) * 15, + }; + } + /// fmt is based on https://momentjs.com/docs/#/displaying/format/ pub fn formatFmt(self: DateTime, comptime fmt: string, writer: anytype) !void { if (fmt.len == 0) @compileError("DateTime: format string can't be empty"); @@ -325,9 +338,8 @@ pub const DateTime = struct { .SS => try writer.print("{:0>2}", .{self.ms / 10}), .SSS => try writer.print("{:0>3}", .{self.ms}), - .z => try writer.writeAll(@tagName(self.timezone)), - .Z => try writer.writeAll("+00:00"), - .ZZ => try writer.writeAll("+0000"), + .Z => try writer.print("{c}{:0>2}:{:0>2}", toTuple(self.tz_parts())), + .ZZ => try writer.print("{c}{:0>2}{:0>2}", toTuple(self.tz_parts())), .x => try writer.print("{}", .{self.toUnixMilli()}), .X => try writer.print("{}", .{self.toUnix()}), @@ -358,7 +370,6 @@ pub const DateTime = struct { pub fn formatAlloc(self: DateTime, alloc: std.mem.Allocator, comptime fmt: string) !string { var list = std.Io.Writer.Allocating.init(alloc); defer list.deinit(); - try self.formatFmt(fmt, &list.writer); return list.toOwnedSlice(); } @@ -408,7 +419,6 @@ pub const DateTime = struct { S, // 0 1 ... 8 9 (second fraction) SS, // 00 01 ... 98 99 SSS, // 000 001 ... 998 999 - z, // EST CST ... MST PST Z, // -07:00 -06:00 ... +06:00 +07:00 ZZ, // -0700 -0600 ... +0600 +0700 x, // unix milli @@ -530,6 +540,12 @@ fn wrap(val: u16, at: u16) u16 { return if (tmp == 0) at else tmp; } +fn toTuple(array: anytype) @Tuple(&@as([array.len]type, @splat(std.meta.Child(@TypeOf(array))))) { + var result: @Tuple(&@as([array.len]type, @splat(std.meta.Child(@TypeOf(array))))) = undefined; + inline for (array, 0..) |v, i| result[i] = v; + return result; +} + pub const Duration = struct { ms: u64, };