Compare commits

...

2 Commits

Author SHA1 Message Date
Nathan Fisher 8ab36acb45 Split off `timezone` module 2023-06-13 16:05:33 -04:00
Nathan Fisher 2578b0f4fa Move `Month` into module 2023-06-13 15:54:28 -04:00
3 changed files with 197 additions and 185 deletions

View File

@ -2,196 +2,15 @@ const std = @import("std");
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 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, year: Year) u32 {
return @as(u32, self.days(year)) * 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 "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", .{});
}
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 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,
@ -364,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),

47
src/month.zig Normal file
View File

@ -0,0 +1,47 @@
const std = @import("std");
const debug = std.debug;
const testing = std.testing;
const Year = @import("year.zig").Year;
const SECONDS_PER_DAY = @import("main.zig").SECONDS_PER_DAY;
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, year: Year) u32 {
return @as(u32, self.days(year)) * 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;
}
};

75
src/timezone.zig Normal file
View File

@ -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;
}
};