commit 2029bf3778841788c2888dd3f87632ea01308dad Author: Nathan Fisher Date: Mon Jun 12 22:59:12 2023 -0400 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a50a769 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +zig-out/ +zig-cache/ +tags +tags.temp +tags.lock diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..70d52ce --- /dev/null +++ b/build.zig @@ -0,0 +1,17 @@ +const std = @import("std"); + +pub fn build(b: *std.build.Builder) void { + // Standard release options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. + const mode = b.standardReleaseOptions(); + + const lib = b.addStaticLibrary("zigDateTime", "src/main.zig"); + lib.setBuildMode(mode); + lib.install(); + + const main_tests = b.addTest("src/main.zig"); + main_tests.setBuildMode(mode); + + const test_step = b.step("test", "Run library tests"); + test_step.dependOn(&main_tests.step); +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..387e9aa --- /dev/null +++ b/src/main.zig @@ -0,0 +1,223 @@ +const std = @import("std"); +const testing = std.testing; + +pub const SECONDS_PER_MINUTE = 60; +pub const SECONDS_PER_HOUR = 60 * 60; +pub const SECONDS_PER_DAY = SECONDS_PER_HOUR * 24; + +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; + } +}; + +pub const Year = union(YearTag) { + normal: u31, + leap: u31, + + const Self = @This(); + + pub fn new(year: u31) Self { + return switch (YearTag.new(year)) { + .normal => Self{ .normal = year }, + .leap => Self{ .leap = year }, + }; + } + + pub fn days(self: Self) u16 { + return switch (self) { + .normal => 365, + .leap => 366, + }; + } + + pub fn seconds(self: Self) i64 { + return self.days * SECONDS_PER_DAY; + } + + pub fn get(self: Self) u31 { + return switch (self) { + .normal => |year| year, + .leap => |year| year, + }; + } + + pub fn next(self: Self) Self { + return Self.new(self.get() + 1); + } + + pub fn previous(self: Self) Self { + return Self.new(self.get() - 1); + } +}; + +test "new year" { + try testing.expectEqual(Year.new(2023), Year{ .normal = 2023 }); + try testing.expectEqual(Year.new(2024), Year{ .leap = 2024 }); +} + +test "get year" { + try testing.expectEqual(Year.new(2023).get(), 2023); + try testing.expectEqual(Year.new(2024).get(), 2024); +} + +test "next year" { + try testing.expectEqual(Year.new(2023).next(), Year{ .leap = 2024 }); +} + +test "last year" { + try testing.expectEqual(Year.new(2024).previous(), Year{ .normal = 2023 }); +} + +pub const Month = enum(u4) { + january = 1, + february = 2, + march = 3, + april = 4, + may = 5, + june = 6, + july = 7, + august = 8, + september = 9, + october = 10, + november = 11, + december = 12, + + const Self = @This(); + + pub fn days(self: Self, year: Year) u5 { + return switch (@enumToInt(self)) { + 1, 3, 5, 7, 8, 10, 12 => 31, + 2 => switch (year) { + .normal => 28, + .leap => 29, + }, + else => 30, + }; + } + + pub fn seconds(self: Self) u32 { + return self.days() * SECONDS_PER_DAY; + } + + pub fn next(self: Self) ?Self { + const num = @enumToInt(self); + return if (num < 12) @intToEnum(Self, num + 1) else null; + } + + pub fn previous(self: Self) ?Self { + const num = @enumToInt(self); + return if (num > 1) @intToEnum(Self, num - 1) else null; + } +}; + +test "get days in month" { + const year = Year.new(2023); + const month = Month.february; + try testing.expectEqual(month.days(year), 28); +} + +test "next month" { + try testing.expectEqual(Month.june.next(), .july); + try testing.expectEqual(Month.december.next(), null); +} + +test "last month" { + try testing.expectEqual(Month.june.previous(), .may); + try testing.expectEqual(Month.january.previous(), null); +} + +pub const TimeZoneTag = enum(u1) { + utc, + offset, +}; + +pub const TimeZone = union(TimeZoneTag) { + utc: void, + offset: i8, + + const Self = @This(); + + pub fn new(offset: ?i8) ?Self { + return if (offset) |ofs| blk: { + if (ofs < -12 or ofs > 12) { + break :blk null; + } else if (ofs == 0) { + break :blk .utc; + } else { + break :blk Self{ .offset = ofs }; + } + } else .utc; + } +}; + +test "new timezone" { + const tz = TimeZone.new(-5).?; + try testing.expectEqual(@as(TimeZoneTag, tz), .offset); + switch (tz) { + .offset => |ofs| try testing.expectEqual(ofs, -5), + else => unreachable, + } +} + +test "new timezone utc" { + const tz0 = TimeZone.new(null).?; + const tz1 = TimeZone.new(0).?; + try testing.expectEqual(@as(TimeZoneTag, tz0), .utc); + try testing.expectEqual(@as(TimeZoneTag, tz1), .utc); +} + +pub const DateTime = struct { + year: Year, + month: Month, + day: u8, + hour: ?u5, + minute: ?u6, + second: ?u6, + nanos: ?u32, + tz: TimeZone, + + const Self = @This(); + + pub fn getYear(self: Self) u32 { + return self.year.get(); + } + + pub fn getOffset(self: Self) ?i8 { + return switch (self.tz) { + .utc => null, + .offset => |ofs| ofs, + }; + } +}; + +test "get year" { + const dt = DateTime{ + .year = Year.new(2023), + .month = .june, + .day = 12, + .hour = 1, + .minute = 5, + .second = 14, + .nanos = null, + .tz = TimeZone.new(-5).?, + }; + try testing.expectEqual(dt.getYear(), 2023); +} + +test "get offset" { + const dt = DateTime{ + .year = Year.new(2023), + .month = .june, + .day = 12, + .hour = 1, + .minute = 5, + .second = 14, + .nanos = null, + .tz = TimeZone.new(-5).?, + }; + try testing.expectEqual(dt.getOffset().?, -5); +}