diff --git a/src/main.zig b/src/main.zig index ba688c6..e524042 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3,154 +3,14 @@ const debug = std.debug; const testing = std.testing; pub const Year = @import("year.zig").Year; pub const Month = @import("month.zig").Month; +pub const tz = @import("timezone.zig"); +pub const Offset = tz.Offset; +pub const TimeZone = tz.TimeZone; pub const SECONDS_PER_MINUTE = 60; pub const SECONDS_PER_HOUR = 60 * 60; pub const SECONDS_PER_DAY = SECONDS_PER_HOUR * 24; -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(); - - 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 } }; - } - } - - 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; - }, - }; - } -}; - -test "new year" { - try testing.expectEqual(Year.new(2023), Year{ .normal = 2023 }); - try testing.expectEqual(Year.new(2024), Year{ .leap = 2024 }); - debug.print("Passed\n", .{}); -} - -test "get year" { - try testing.expectEqual(Year.new(2023).get(), 2023); - try testing.expectEqual(Year.new(2024).get(), 2024); - debug.print("Passed\n", .{}); -} - -test "next year" { - try testing.expectEqual(Year.new(2023).next(), Year{ .leap = 2024 }); - debug.print("Passed\n", .{}); -} - -test "last year" { - try testing.expectEqual(Year.new(2024).previous(), Year{ .normal = 2023 }); - debug.print("Passed\n", .{}); -} - -test "get days in month" { - const year = Year.new(2023); - const month = Month.february; - try testing.expectEqual(month.days(year), 28); - debug.print("Passed\n", .{}); -} - -test "next month" { - try testing.expectEqual(Month.june.next(), .july); - try testing.expectEqual(Month.december.next(), null); - debug.print("Passed\n", .{}); -} - -test "last month" { - try testing.expectEqual(Month.june.previous(), .may); - try testing.expectEqual(Month.january.previous(), null); - debug.print("Passed\n", .{}); -} - -test "new offsets" { - try testing.expectEqual(Offset.new(-5, null), Offset{ .negative = .{ .hours = 5, .minutes = null } }); - try testing.expectEqual(Offset.new(3, null), Offset{ .positive = .{ .hours = 3, .minutes = null } }); - debug.print("Passed\n", .{}); -} - -test "as seconds" { - try testing.expectEqual(Offset.new(-4, 30).?.asSeconds(), -16200); - try testing.expectEqual(Offset.new(3, null).?.asSeconds(), 10800); - debug.print("Passed\n", .{}); -} - -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; - } -}; - -test "new timezone" { - const tz = TimeZone.new(-5, null).?; - try testing.expectEqual(@as(TimeZoneTag, tz), .offset); - switch (tz) { - .offset => |ofs| try testing.expectEqual(ofs, Offset{ .negative = .{ .hours = 5, .minutes = null } }), - else => unreachable, - } - debug.print("Passed\n", .{}); -} - -test "new timezone utc" { - const tz0 = TimeZone.new(null, null).?; - const tz1 = TimeZone.new(0, null).?; - try testing.expectEqual(@as(TimeZoneTag, tz0), .utc); - try testing.expectEqual(@as(TimeZoneTag, tz1), .utc); - debug.print("Passed\n", .{}); -} - pub const WeekDay = enum(u3) { thursday = 0, friday, @@ -323,6 +183,77 @@ pub const DateTime = struct { } }; +test "new year" { + try testing.expectEqual(Year.new(2023), Year{ .normal = 2023 }); + try testing.expectEqual(Year.new(2024), Year{ .leap = 2024 }); + debug.print("Passed\n", .{}); +} + +test "get year" { + try testing.expectEqual(Year.new(2023).get(), 2023); + try testing.expectEqual(Year.new(2024).get(), 2024); + debug.print("Passed\n", .{}); +} + +test "next year" { + try testing.expectEqual(Year.new(2023).next(), Year{ .leap = 2024 }); + debug.print("Passed\n", .{}); +} + +test "last year" { + try testing.expectEqual(Year.new(2024).previous(), Year{ .normal = 2023 }); + debug.print("Passed\n", .{}); +} + +test "get days in month" { + const year = Year.new(2023); + const month = Month.february; + try testing.expectEqual(month.days(year), 28); + debug.print("Passed\n", .{}); +} + +test "next month" { + try testing.expectEqual(Month.june.next(), .july); + try testing.expectEqual(Month.december.next(), null); + debug.print("Passed\n", .{}); +} + +test "last month" { + try testing.expectEqual(Month.june.previous(), .may); + try testing.expectEqual(Month.january.previous(), null); + debug.print("Passed\n", .{}); +} + +test "new offsets" { + try testing.expectEqual(Offset.new(-5, null), Offset{ .negative = .{ .hours = 5, .minutes = null } }); + try testing.expectEqual(Offset.new(3, null), Offset{ .positive = .{ .hours = 3, .minutes = null } }); + debug.print("Passed\n", .{}); +} + +test "as seconds" { + try testing.expectEqual(Offset.new(-4, 30).?.asSeconds(), -16200); + try testing.expectEqual(Offset.new(3, null).?.asSeconds(), 10800); + debug.print("Passed\n", .{}); +} + +test "new timezone" { + const zone = TimeZone.new(-5, null).?; + try testing.expectEqual(@as(tz.TimeZoneTag, zone), .offset); + switch (zone) { + .offset => |ofs| try testing.expectEqual(ofs, Offset{ .negative = .{ .hours = 5, .minutes = null } }), + else => unreachable, + } + debug.print("Passed\n", .{}); +} + +test "new timezone utc" { + const tz0 = TimeZone.new(null, null).?; + const tz1 = TimeZone.new(0, null).?; + try testing.expectEqual(@as(tz.TimeZoneTag, tz0), .utc); + try testing.expectEqual(@as(tz.TimeZoneTag, tz1), .utc); + debug.print("Passed\n", .{}); +} + test "get year" { const dt = DateTime{ .year = Year.new(2023), diff --git a/src/timezone.zig b/src/timezone.zig new file mode 100644 index 0000000..939e47b --- /dev/null +++ b/src/timezone.zig @@ -0,0 +1,75 @@ +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; + } +};