Add fromTimestamp and improved testing coverage and feedback
This commit is contained in:
parent
c79292181a
commit
20afb68910
122
src/main.zig
122
src/main.zig
@ -1,4 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const debug = std.debug;
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
pub const SECONDS_PER_MINUTE = 60;
|
pub const SECONDS_PER_MINUTE = 60;
|
||||||
@ -9,18 +10,18 @@ pub const YearTag = enum(u1) {
|
|||||||
normal,
|
normal,
|
||||||
leap,
|
leap,
|
||||||
|
|
||||||
fn new(year: u31) YearTag {
|
fn new(year: i32) YearTag {
|
||||||
return if (year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)) .leap else .normal;
|
return if (@rem(year, 4) == 0 and (@rem(year, 100) != 0 or @rem(year, 400) == 0)) .leap else .normal;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Year = union(YearTag) {
|
pub const Year = union(YearTag) {
|
||||||
normal: u31,
|
normal: i32,
|
||||||
leap: u31,
|
leap: i32,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub fn new(year: u31) Self {
|
pub fn new(year: i32) Self {
|
||||||
return switch (YearTag.new(year)) {
|
return switch (YearTag.new(year)) {
|
||||||
.normal => Self{ .normal = year },
|
.normal => Self{ .normal = year },
|
||||||
.leap => Self{ .leap = year },
|
.leap => Self{ .leap = year },
|
||||||
@ -38,7 +39,7 @@ pub const Year = union(YearTag) {
|
|||||||
return @as(i64, self.days()) * SECONDS_PER_DAY;
|
return @as(i64, self.days()) * SECONDS_PER_DAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(self: Self) u31 {
|
pub fn get(self: Self) i32 {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
.normal => |year| year,
|
.normal => |year| year,
|
||||||
.leap => |year| year,
|
.leap => |year| year,
|
||||||
@ -57,19 +58,23 @@ pub const Year = union(YearTag) {
|
|||||||
test "new year" {
|
test "new year" {
|
||||||
try testing.expectEqual(Year.new(2023), Year{ .normal = 2023 });
|
try testing.expectEqual(Year.new(2023), Year{ .normal = 2023 });
|
||||||
try testing.expectEqual(Year.new(2024), Year{ .leap = 2024 });
|
try testing.expectEqual(Year.new(2024), Year{ .leap = 2024 });
|
||||||
|
debug.print("Passed\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "get year" {
|
test "get year" {
|
||||||
try testing.expectEqual(Year.new(2023).get(), 2023);
|
try testing.expectEqual(Year.new(2023).get(), 2023);
|
||||||
try testing.expectEqual(Year.new(2024).get(), 2024);
|
try testing.expectEqual(Year.new(2024).get(), 2024);
|
||||||
|
debug.print("Passed\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "next year" {
|
test "next year" {
|
||||||
try testing.expectEqual(Year.new(2023).next(), Year{ .leap = 2024 });
|
try testing.expectEqual(Year.new(2023).next(), Year{ .leap = 2024 });
|
||||||
|
debug.print("Passed\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "last year" {
|
test "last year" {
|
||||||
try testing.expectEqual(Year.new(2024).previous(), Year{ .normal = 2023 });
|
try testing.expectEqual(Year.new(2024).previous(), Year{ .normal = 2023 });
|
||||||
|
debug.print("Passed\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const Month = enum(u4) {
|
pub const Month = enum(u4) {
|
||||||
@ -118,16 +123,19 @@ test "get days in month" {
|
|||||||
const year = Year.new(2023);
|
const year = Year.new(2023);
|
||||||
const month = Month.february;
|
const month = Month.february;
|
||||||
try testing.expectEqual(month.days(year), 28);
|
try testing.expectEqual(month.days(year), 28);
|
||||||
|
debug.print("Passed\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "next month" {
|
test "next month" {
|
||||||
try testing.expectEqual(Month.june.next(), .july);
|
try testing.expectEqual(Month.june.next(), .july);
|
||||||
try testing.expectEqual(Month.december.next(), null);
|
try testing.expectEqual(Month.december.next(), null);
|
||||||
|
debug.print("Passed\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "last month" {
|
test "last month" {
|
||||||
try testing.expectEqual(Month.june.previous(), .may);
|
try testing.expectEqual(Month.june.previous(), .may);
|
||||||
try testing.expectEqual(Month.january.previous(), null);
|
try testing.expectEqual(Month.january.previous(), null);
|
||||||
|
debug.print("Passed\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const TimeZoneTag = enum(u1) {
|
pub const TimeZoneTag = enum(u1) {
|
||||||
@ -186,11 +194,13 @@ pub const Offset = union(Sign) {
|
|||||||
test "new offsets" {
|
test "new offsets" {
|
||||||
try testing.expectEqual(Offset.new(-5, null), Offset{ .negative = .{ .hours = 5, .minutes = null } });
|
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 } });
|
try testing.expectEqual(Offset.new(3, null), Offset{ .positive = .{ .hours = 3, .minutes = null } });
|
||||||
|
debug.print("Passed\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "as seconds" {
|
test "as seconds" {
|
||||||
try testing.expectEqual(Offset.new(-4, 30).?.asSeconds(), -16200);
|
try testing.expectEqual(Offset.new(-4, 30).?.asSeconds(), -16200);
|
||||||
try testing.expectEqual(Offset.new(3, null).?.asSeconds(), 10800);
|
try testing.expectEqual(Offset.new(3, null).?.asSeconds(), 10800);
|
||||||
|
debug.print("Passed\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const TimeZone = union(TimeZoneTag) {
|
pub const TimeZone = union(TimeZoneTag) {
|
||||||
@ -219,6 +229,7 @@ test "new timezone" {
|
|||||||
.offset => |ofs| try testing.expectEqual(ofs, Offset{ .negative = .{ .hours = 5, .minutes = null } }),
|
.offset => |ofs| try testing.expectEqual(ofs, Offset{ .negative = .{ .hours = 5, .minutes = null } }),
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
}
|
}
|
||||||
|
debug.print("Passed\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "new timezone utc" {
|
test "new timezone utc" {
|
||||||
@ -226,6 +237,7 @@ test "new timezone utc" {
|
|||||||
const tz1 = TimeZone.new(0, null).?;
|
const tz1 = TimeZone.new(0, null).?;
|
||||||
try testing.expectEqual(@as(TimeZoneTag, tz0), .utc);
|
try testing.expectEqual(@as(TimeZoneTag, tz0), .utc);
|
||||||
try testing.expectEqual(@as(TimeZoneTag, tz1), .utc);
|
try testing.expectEqual(@as(TimeZoneTag, tz1), .utc);
|
||||||
|
debug.print("Passed\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const WeekDay = enum(u3) {
|
pub const WeekDay = enum(u3) {
|
||||||
@ -255,7 +267,7 @@ pub const DateTime = struct {
|
|||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub fn getYear(self: Self) u32 {
|
pub fn getYear(self: Self) i32 {
|
||||||
return self.year.get();
|
return self.year.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,6 +314,87 @@ pub const DateTime = struct {
|
|||||||
return seconds;
|
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 {
|
pub fn weekday(self: Self) WeekDay {
|
||||||
const ts = self.toTimestamp();
|
const ts = self.toTimestamp();
|
||||||
const days = @divTrunc(ts, SECONDS_PER_DAY);
|
const days = @divTrunc(ts, SECONDS_PER_DAY);
|
||||||
@ -326,6 +419,7 @@ test "get year" {
|
|||||||
.tz = TimeZone.new(-5, null).?,
|
.tz = TimeZone.new(-5, null).?,
|
||||||
};
|
};
|
||||||
try testing.expectEqual(dt.getYear(), 2023);
|
try testing.expectEqual(dt.getYear(), 2023);
|
||||||
|
debug.print("Passed\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "get offset" {
|
test "get offset" {
|
||||||
@ -339,6 +433,7 @@ test "get offset" {
|
|||||||
.tz = TimeZone.new(-5, null).?,
|
.tz = TimeZone.new(-5, null).?,
|
||||||
};
|
};
|
||||||
try testing.expectEqual(dt.getOffset().?, Offset{ .negative = .{ .hours = 5, .minutes = null } });
|
try testing.expectEqual(dt.getOffset().?, Offset{ .negative = .{ .hours = 5, .minutes = null } });
|
||||||
|
debug.print("Passed\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "to timestamp utc" {
|
test "to timestamp utc" {
|
||||||
@ -352,6 +447,7 @@ test "to timestamp utc" {
|
|||||||
.tz = .utc,
|
.tz = .utc,
|
||||||
};
|
};
|
||||||
try testing.expectEqual(dt.toTimestamp(), 1686633682);
|
try testing.expectEqual(dt.toTimestamp(), 1686633682);
|
||||||
|
debug.print("Passed\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "to timestamp negative offset" {
|
test "to timestamp negative offset" {
|
||||||
@ -365,6 +461,14 @@ test "to timestamp negative offset" {
|
|||||||
.tz = TimeZone.new(-5, null).?,
|
.tz = TimeZone.new(-5, null).?,
|
||||||
};
|
};
|
||||||
try testing.expectEqual(dt.toTimestamp(), 1686633682);
|
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" {
|
test "get weekday" {
|
||||||
@ -378,6 +482,7 @@ test "get weekday" {
|
|||||||
.tz = .utc,
|
.tz = .utc,
|
||||||
};
|
};
|
||||||
try testing.expectEqual(dt.weekday(), .tuesday);
|
try testing.expectEqual(dt.weekday(), .tuesday);
|
||||||
|
debug.print("Passed\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "get weekday 2" {
|
test "get weekday 2" {
|
||||||
@ -391,6 +496,7 @@ test "get weekday 2" {
|
|||||||
.tz = .utc,
|
.tz = .utc,
|
||||||
};
|
};
|
||||||
try testing.expectEqual(dt.weekday(), .saturday);
|
try testing.expectEqual(dt.weekday(), .saturday);
|
||||||
|
debug.print("Passed\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "ordering lt" {
|
test "ordering lt" {
|
||||||
@ -413,6 +519,7 @@ test "ordering lt" {
|
|||||||
.tz = .utc,
|
.tz = .utc,
|
||||||
};
|
};
|
||||||
try testing.expectEqual(a.compare(b), .lt);
|
try testing.expectEqual(a.compare(b), .lt);
|
||||||
|
debug.print("Passed\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "ordering gt" {
|
test "ordering gt" {
|
||||||
@ -435,4 +542,5 @@ test "ordering gt" {
|
|||||||
.tz = TimeZone.new(1, null).?,
|
.tz = TimeZone.new(1, null).?,
|
||||||
};
|
};
|
||||||
try testing.expectEqual(a.compare(b), .gt);
|
try testing.expectEqual(a.compare(b), .gt);
|
||||||
|
debug.print("Passed\n", .{});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user