diff --git a/src/main.zig b/src/main.zig index 36ea861..41b7138 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const debug = std.debug; const testing = std.testing; pub const SECONDS_PER_MINUTE = 60; @@ -9,18 +10,18 @@ pub const YearTag = enum(u1) { normal, leap, - fn new(year: u31) YearTag { - return if (year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)) .leap else .normal; + fn new(year: i32) YearTag { + return if (@rem(year, 4) == 0 and (@rem(year, 100) != 0 or @rem(year, 400) == 0)) .leap else .normal; } }; pub const Year = union(YearTag) { - normal: u31, - leap: u31, + normal: i32, + leap: i32, const Self = @This(); - pub fn new(year: u31) Self { + pub fn new(year: i32) Self { return switch (YearTag.new(year)) { .normal => Self{ .normal = year }, .leap => Self{ .leap = year }, @@ -38,7 +39,7 @@ pub const Year = union(YearTag) { return @as(i64, self.days()) * SECONDS_PER_DAY; } - pub fn get(self: Self) u31 { + pub fn get(self: Self) i32 { return switch (self) { .normal => |year| year, .leap => |year| year, @@ -57,19 +58,23 @@ pub const Year = union(YearTag) { 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", .{}); } pub const Month = enum(u4) { @@ -118,16 +123,19 @@ 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", .{}); } pub const TimeZoneTag = enum(u1) { @@ -186,11 +194,13 @@ pub const Offset = union(Sign) { 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) { @@ -219,6 +229,7 @@ test "new timezone" { .offset => |ofs| try testing.expectEqual(ofs, Offset{ .negative = .{ .hours = 5, .minutes = null } }), else => unreachable, } + debug.print("Passed\n", .{}); } test "new timezone utc" { @@ -226,6 +237,7 @@ test "new timezone utc" { 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) { @@ -255,7 +267,7 @@ pub const DateTime = struct { const Self = @This(); - pub fn getYear(self: Self) u32 { + pub fn getYear(self: Self) i32 { return self.year.get(); } @@ -302,6 +314,87 @@ pub const DateTime = struct { return seconds; } + pub fn fromTimestamp(ts: i64) Self { + if (ts < 0) { + var seconds: i64 = 0; + var year = Year.new(-1); + while (seconds > -year.seconds()) { + seconds -= year.seconds(); + year = year.previous(); + } + var month: ?Month = Month.december; + while (month != null) { + if (month) |m| { + if (seconds > m.seconds(year)) break; + seconds -= m.seconds(year); + month = m.previous(); + } + } + var day = month.?.days(year); + while (day > 0 and seconds < -SECONDS_PER_DAY) { + seconds -= SECONDS_PER_DAY; + day -= 1; + } + var hours: u5 = 23; + while (hours >= 0 and seconds < -SECONDS_PER_HOUR) { + seconds -= SECONDS_PER_HOUR; + hours -= 1; + } + var minutes: u6 = 60; + while (minutes >= 0 and seconds < -60) { + seconds -= 60; + minutes -= 1; + } + const second = @intCast(u6, seconds + 60); + return Self{ + .year = year, + .month = month.?, + .day = day, + .hour = hours, + .minute = minutes, + .second = @as(u6, second), + .tz = .utc, + }; + } else if (ts > 0) { + var seconds = ts; + var year = Year.new(1970); + while (year.seconds() < seconds) { + seconds -= year.seconds(); + year = year.next(); + } + var month = Month.january; + while (month.seconds(year) < seconds) { + seconds -= month.seconds(year); + month = month.next().?; + } + const day = @divTrunc(seconds, SECONDS_PER_DAY) + 1; + seconds = @rem(seconds, SECONDS_PER_DAY); + const hour = @divTrunc(seconds, SECONDS_PER_HOUR); + seconds = @rem(seconds, SECONDS_PER_HOUR); + const minute = @divTrunc(seconds, 60); + seconds = @rem(seconds, 60); + return Self{ + .year = year, + .month = month, + .day = @intCast(u8, day), + .hour = @intCast(u5, hour), + .minute = @intCast(u6, minute), + .second = @intCast(u6, seconds), + .tz = .utc, + }; + } else { + return Self{ + .year = Year.new(0), + .month = .january, + .day = 1, + .hour = 0, + .minute = 0, + .second = 0, + .tz = .utc, + }; + } + } + pub fn weekday(self: Self) WeekDay { const ts = self.toTimestamp(); const days = @divTrunc(ts, SECONDS_PER_DAY); @@ -326,6 +419,7 @@ test "get year" { .tz = TimeZone.new(-5, null).?, }; try testing.expectEqual(dt.getYear(), 2023); + debug.print("Passed\n", .{}); } test "get offset" { @@ -339,6 +433,7 @@ test "get offset" { .tz = TimeZone.new(-5, null).?, }; try testing.expectEqual(dt.getOffset().?, Offset{ .negative = .{ .hours = 5, .minutes = null } }); + debug.print("Passed\n", .{}); } test "to timestamp utc" { @@ -352,6 +447,7 @@ test "to timestamp utc" { .tz = .utc, }; try testing.expectEqual(dt.toTimestamp(), 1686633682); + debug.print("Passed\n", .{}); } test "to timestamp negative offset" { @@ -365,6 +461,14 @@ test "to timestamp negative offset" { .tz = TimeZone.new(-5, null).?, }; try testing.expectEqual(dt.toTimestamp(), 1686633682); + debug.print("Passed\n", .{}); +} + +test "conversions" { + const ts = std.time.timestamp(); + const dt = DateTime.fromTimestamp(ts); + try testing.expectEqual(dt.toTimestamp(), ts); + debug.print("Passed\n", .{}); } test "get weekday" { @@ -378,6 +482,7 @@ test "get weekday" { .tz = .utc, }; try testing.expectEqual(dt.weekday(), .tuesday); + debug.print("Passed\n", .{}); } test "get weekday 2" { @@ -391,6 +496,7 @@ test "get weekday 2" { .tz = .utc, }; try testing.expectEqual(dt.weekday(), .saturday); + debug.print("Passed\n", .{}); } test "ordering lt" { @@ -413,6 +519,7 @@ test "ordering lt" { .tz = .utc, }; try testing.expectEqual(a.compare(b), .lt); + debug.print("Passed\n", .{}); } test "ordering gt" { @@ -435,4 +542,5 @@ test "ordering gt" { .tz = TimeZone.new(1, null).?, }; try testing.expectEqual(a.compare(b), .gt); + debug.print("Passed\n", .{}); }