const std = @import("std"); const debug = std.debug; const testing = std.testing; pub const TimeZoneTag = enum(u1) { utc, offset, }; pub const Sign = enum(u1) { positive, negative, }; const HoursMinutes = struct { hours: u4, minutes: ?u6, }; pub const Offset = union(Sign) { positive: HoursMinutes, negative: HoursMinutes, const Self = @This(); pub fn new(hours: i8, minutes: ?u6) ?Self { if (hours > 12 or hours < -12) { return null; } if (minutes) |m| { if (m > 59) return null; if (hours == 0 and m == 0) return null; } else if (hours == 0) return null; if (hours < 0) { const h = @intCast(u4, @as(i8, hours) * -1); return Self{ .negative = .{ .hours = h, .minutes = minutes } }; } else { return Self{ .positive = .{ .hours = @intCast(u4, hours), .minutes = minutes } }; } } pub fn asSeconds(self: Self) i64 { return switch (self) { .positive => |ofs| blk: { var seconds = @as(i64, ofs.hours) * 3600; if (ofs.minutes) |m| seconds += (@as(i64, m) * 60); break :blk seconds; }, .negative => |ofs| blk: { var seconds = @as(i64, ofs.hours) * 3600; if (ofs.minutes) |m| seconds += (@as(i64, m) * 60); break :blk seconds * -1; }, }; } }; pub const TimeZone = union(TimeZoneTag) { utc: void, offset: Offset, const Self = @This(); pub fn new(hours: ?i8, minutes: ?u6) ?Self { return if (hours) |h| blk: { if (h == 0) { break :blk .utc; } else if (Offset.new(h, minutes)) |ofs| { break :blk Self{ .offset = ofs }; } else { break :blk null; } } else if (minutes) |m| Self{ .offset = Offset.new(0, m).? } else .utc; } pub fn format( self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) !void { _ = fmt; _ = options; switch (self) { .utc => try writer.writeAll("Z"), .offset => |ofs| switch (ofs) { .positive => |p| { try writer.print("+{d:0>2}", .{p.hours}); if (p.minutes) |m| { try writer.print(":{d:0>2}", .{m}); } }, .negative => |n| { try writer.print("-{d:0>2}", .{n.hours}); if (n.minutes) |m| { try writer.print(":{d:0>2}", .{m}); } }, }, } } };