1038 lines
32 KiB
Zig
1038 lines
32 KiB
Zig
const std = @import("std");
|
|
|
|
pub const Day = u6;
|
|
|
|
pub const Month = enum(u4) {
|
|
Jan = 1,
|
|
Feb = 2,
|
|
Mar = 3,
|
|
Apr = 4,
|
|
May = 5,
|
|
Jun = 6,
|
|
Jul = 7,
|
|
Aug = 8,
|
|
Sep = 9,
|
|
Oct = 10,
|
|
Nov = 11,
|
|
Dec = 12,
|
|
|
|
const Self = @This();
|
|
|
|
pub fn monthNumber(self: Self) u4 {
|
|
return switch (self) {
|
|
.Jan => 1,
|
|
.Feb => 2,
|
|
.Mar => 3,
|
|
.Apr => 4,
|
|
.May => 5,
|
|
.Jun => 6,
|
|
.Jul => 7,
|
|
.Aug => 8,
|
|
.Sep => 9,
|
|
.Oct => 10,
|
|
.Nov => 11,
|
|
.Dec => 12,
|
|
};
|
|
}
|
|
|
|
pub fn next(self: Self) Self {
|
|
return switch (self) {
|
|
.Jan => .Feb,
|
|
.Feb => .Mar,
|
|
.Mar => .Apr,
|
|
.Apr => .May,
|
|
.May => .Jun,
|
|
.Jun => .Jul,
|
|
.Jul => .Aug,
|
|
.Aug => .Sep,
|
|
.Sep => .Oct,
|
|
.Oct => .Nov,
|
|
.Nov => .Dec,
|
|
.Dec => .Jan,
|
|
};
|
|
}
|
|
|
|
pub fn prev(self: Self) Self {
|
|
return switch (self) {
|
|
.Jan => .Dec,
|
|
.Feb => .Jan,
|
|
.Mar => .Feb,
|
|
.Apr => .Mar,
|
|
.May => .Apr,
|
|
.Jun => .May,
|
|
.Jul => .Jun,
|
|
.Aug => .Jul,
|
|
.Sep => .Aug,
|
|
.Oct => .Sep,
|
|
.Nov => .Oct,
|
|
.Dec => .Nov,
|
|
};
|
|
}
|
|
|
|
pub fn lastDay(self: Self, year: Year) Day {
|
|
return switch (self) {
|
|
.Jan => 31,
|
|
.Feb => if (isLeap(year)) 29 else 28,
|
|
.Mar => 31,
|
|
.Apr => 30,
|
|
.May => 31,
|
|
.Jun => 30,
|
|
.Jul => 31,
|
|
.Aug => 31,
|
|
.Sep => 30,
|
|
.Oct => 31,
|
|
.Nov => 30,
|
|
.Dec => 31,
|
|
};
|
|
}
|
|
|
|
pub fn quarter(self: Self) u3 {
|
|
return switch (self) {
|
|
.Jan => 1,
|
|
.Feb => 1,
|
|
.Mar => 1,
|
|
.Apr => 2,
|
|
.May => 2,
|
|
.Jun => 2,
|
|
.Jul => 3,
|
|
.Aug => 3,
|
|
.Sep => 3,
|
|
.Oct => 4,
|
|
.Nov => 4,
|
|
.Dec => 4,
|
|
};
|
|
}
|
|
|
|
pub fn shortName(self: Self) []const u8 {
|
|
return switch (self) {
|
|
.Jan => "Jan",
|
|
.Feb => "Feb",
|
|
.Mar => "Mar",
|
|
.Apr => "Apr",
|
|
.May => "May",
|
|
.Jun => "Jun",
|
|
.Jul => "Jul",
|
|
.Aug => "Aug",
|
|
.Sep => "Sep",
|
|
.Oct => "Oct",
|
|
.Nov => "Nov",
|
|
.Dec => "Dec",
|
|
};
|
|
}
|
|
|
|
pub fn longName(self: Self) []const u8 {
|
|
return switch (self) {
|
|
.Jan => "January",
|
|
.Feb => "Febuary",
|
|
.Mar => "March",
|
|
.Apr => "April",
|
|
.May => "May",
|
|
.Jun => "June",
|
|
.Jul => "July",
|
|
.Aug => "August",
|
|
.Sep => "September",
|
|
.Oct => "October",
|
|
.Nov => "November",
|
|
.Dec => "December",
|
|
};
|
|
}
|
|
|
|
pub fn daysBefore(self: Self, year: i32) u9 {
|
|
// var days = 0;
|
|
// var month = .Jan;
|
|
// while (month != self) {
|
|
// days += month.lastDay(year);
|
|
// month = month.next();
|
|
// }
|
|
// return days;
|
|
return switch (self) {
|
|
.Jan => 0,
|
|
.Feb => 31,
|
|
.Mar => if (isLeap(year)) 60 else 59,
|
|
.Apr => if (isLeap(year)) 91 else 90,
|
|
.May => if (isLeap(year)) 121 else 120,
|
|
.Jun => if (isLeap(year)) 152 else 151,
|
|
.Jul => if (isLeap(year)) 182 else 181,
|
|
.Aug => if (isLeap(year)) 213 else 212,
|
|
.Sep => if (isLeap(year)) 244 else 243,
|
|
.Oct => if (isLeap(year)) 274 else 273,
|
|
.Nov => if (isLeap(year)) 305 else 304,
|
|
.Dec => if (isLeap(year)) 335 else 334,
|
|
};
|
|
}
|
|
};
|
|
|
|
pub const Year = i32;
|
|
pub const Second = u6;
|
|
pub const Minute = u6;
|
|
pub const Hour = u5;
|
|
|
|
pub fn writeTwelveHour(hour: Hour, case: enum { lower, upper }, writer: anytype) !void {
|
|
if (hour < 12) switch (case) {
|
|
.lower => try writer.writeAll("am"),
|
|
.upper => try writer.writeAll("AM"),
|
|
} else switch (case) {
|
|
.lower => try writer.writeAll("pm"),
|
|
.upper => try writer.writeAll("PM"),
|
|
}
|
|
}
|
|
|
|
pub const Nanosecond = u30;
|
|
|
|
// https://github.com/nektro/zig-time
|
|
|
|
pub const DateTime = struct {
|
|
nanosecond: Nanosecond, // [0..999999999]
|
|
second: Second, // [0..61]
|
|
minute: Minute, // [0..59]
|
|
hour: Hour, // [0-23]
|
|
day: Day, // [0-31]
|
|
month: Month, // [1-12]
|
|
year: Year,
|
|
weekday: DayOfWeek,
|
|
|
|
const Self = @This();
|
|
|
|
pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
|
|
_ = options;
|
|
|
|
if (fmt.len == 0) @compileError("DateTime: format string can't be empty");
|
|
|
|
@setEvalBranchQuota(100000);
|
|
|
|
comptime var s = 0;
|
|
comptime var e = 0;
|
|
comptime var next: ?FormatSeq = null;
|
|
inline for (fmt, 0..) |c, i| {
|
|
e = i + 1;
|
|
// std.debug.print("test: {s}\n", .{fmt[s..e]});
|
|
if (comptime std.meta.stringToEnum(FormatSeq, fmt[s..e])) |tag| {
|
|
// std.debug.print("next {any}\n", .{tag});
|
|
next = tag;
|
|
if (i < fmt.len - 1) continue;
|
|
}
|
|
|
|
if (next) |tag| {
|
|
// std.debug.print("tag {any}\n", .{tag});
|
|
switch (tag) {
|
|
.M => try writer.print("{}", .{@intFromEnum(self.month)}),
|
|
.Mo => try printOrdinal(writer, @intFromEnum(self.month)),
|
|
.MO => try printOrdinalSuperscript(writer, @intFromEnum(self.month)),
|
|
.Mm => try writer.writeAll(self.month.veryShortName()),
|
|
.MM => try writer.print("{:0>2}", .{@intFromEnum(self.month)}),
|
|
.MMM => try writer.writeAll(self.month.shortName()),
|
|
.MMMM => try writer.writeAll(self.month.longName()),
|
|
|
|
.Q => try writer.print("{}", .{self.month.quarter()}),
|
|
.Qo => try printOrdinal(writer, self.month.quarter()),
|
|
.QO => try printOrdinalSuperscript(writer, self.month.quarter()),
|
|
|
|
.D => try writer.print("{}", .{self.day}),
|
|
.Do => try printOrdinal(writer, self.day),
|
|
.DO => try printOrdinalSuperscript(writer, self.day),
|
|
.DD => try writer.print("{:0>2}", .{self.day}),
|
|
|
|
.DDD => try writer.print("{}", .{self.dayOfThisYear()}),
|
|
.DDDo => try printOrdinal(writer, self.dayOfThisYear()),
|
|
.DDDO => try printOrdinalSuperscript(writer, self.dayOfThisYear()),
|
|
.DDDD => try writer.print("{:0>3}", .{self.dayOfThisYear()}),
|
|
|
|
.d => try writer.print("{}", .{self.weekday.weekdayNumber()}),
|
|
.do => try printOrdinal(writer, self.weekday.weekdayNumber()),
|
|
.dO => try printOrdinalSuperscript(writer, self.weekday.weekdayNumber()),
|
|
.dd => try writer.writeAll(self.weekday.veryShortName()),
|
|
.ddd => try writer.writeAll(self.weekday.shortName()),
|
|
.dddd => try writer.writeAll(self.weekday.longName()),
|
|
|
|
.e => try writer.print("{}", .{self.weekday.weekdayNumber()}),
|
|
.E => try writer.print("{}", .{self.weekday.isoWeekdayNumber()}),
|
|
|
|
.w => try writer.print("{}", .{self.dayOfThisYear() / 7}),
|
|
.wo => try printOrdinal(writer, self.dayOfThisYear() / 7),
|
|
.wO => try printOrdinalSuperscript(writer, self.dayOfThisYear() / 7),
|
|
.ww => try writer.print("{:0>2}", .{self.dayOfThisYear() / 7}),
|
|
|
|
.YY => try writer.print("{d:0>2}", .{self.year % 100}),
|
|
.YYY => try writer.print("{d}", .{self.year}),
|
|
.YYYY => {
|
|
if (self.year < 0) {
|
|
try writer.writeAll("-");
|
|
}
|
|
try writer.print("{d:0>4}", .{@abs(self.year)});
|
|
},
|
|
|
|
.A => try writeTwelveHour(self.hour, .upper),
|
|
.a => try writeTwelveHour(self.hour, .lower),
|
|
|
|
.H => try writer.print("{}", .{self.hour}),
|
|
.HH => try writer.print("{:0>2}", .{self.hour}),
|
|
.h => try writer.print("{}", .{wrap(self.hour, 12)}),
|
|
.hh => try writer.print("{:0>2}", .{wrap(self.hour, 12)}),
|
|
.k => try writer.print("{}", .{wrap(self.hour, 24)}),
|
|
.kk => try writer.print("{:0>2}", .{wrap(self.hour, 24)}),
|
|
|
|
.m => try writer.print("{}", .{self.minute}),
|
|
.mm => try writer.print("{:0>2}", .{self.minute}),
|
|
|
|
.s => try writer.print("{d}", .{self.second}),
|
|
.ss => try writer.print("{d:0>2}", .{self.second}),
|
|
|
|
.S => try writer.print("{d:>1}", .{self.nanosecond / tenthsPerNanoSecond}),
|
|
.SS => try writer.print("{d:0>2}", .{self.nanosecond / hundredthsPerNanoSecond}),
|
|
.SSS => try writer.print("{d:0>3}", .{self.nanosecond / milliSecondsPerNanoSecond}),
|
|
.SSSS => try writer.print("{d:0>4}", .{self.nanosecond / (milliSecondsPerNanoSecond / 10)}),
|
|
.SSSSS => try writer.print("{d:0>5}", .{self.nanosecond / (milliSecondsPerNanoSecond / 100)}),
|
|
.SSSSSS => try writer.print("{d:0>6}", .{self.nanosecond / microSecondsPerNanoSecond}),
|
|
.SSSSSSS => try writer.print("{d:0>7}", .{self.nanosecond / (microSecondsPerNanoSecond / 10)}),
|
|
.SSSSSSSS => try writer.print("{d:0>8}", .{self.nanosecond / (microSecondsPerNanoSecond / 100)}),
|
|
.SSSSSSSSS => try writer.print("{d:0>9}", .{self.nanosecond}),
|
|
|
|
// .z => try writer.writeAll(@tagName(self.timezone)),
|
|
// .Z => try writer.writeAll("+00:00"),
|
|
// .ZZ => try writer.writeAll("+0000"),
|
|
|
|
// .x => try writer.print("{}", .{self.toUnixMilli()}),
|
|
// .X => try writer.print("{}", .{self.toUnix()}),
|
|
}
|
|
next = null;
|
|
s = i;
|
|
}
|
|
|
|
switch (c) {
|
|
',', ' ', ':', '-', '.', 'T', 'W' => {
|
|
try writer.writeAll(&.{c});
|
|
s = i + 1;
|
|
continue;
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn formatAlloc(self: Self, alloc: std.mem.Allocator, comptime fmt: []const u8) ![]const u8 {
|
|
var list = std.ArrayList(u8).init(alloc);
|
|
defer list.deinit();
|
|
|
|
try self.format(fmt, .{}, list.writer());
|
|
return list.toOwnedSlice();
|
|
}
|
|
|
|
const FormatSeq = enum {
|
|
M, // 1 2 ... 11 12 (month, numeric)
|
|
Mo, // 1st 2nd ... 11th 12th (month, numeric ordinal)
|
|
MO, // 1ˢᵗ 2ⁿᵈ 3ʳᵈ ... 11ᵗʰ 12ᵗʰ
|
|
MM, // 01 02 ... 11 12 (month, numeric ordinal)
|
|
Mm, // Ja, Fe, Ma ... No, De (very short month name)
|
|
MMM, // Jan Feb ... Nov Dec (short month name)
|
|
MMMM, // January February ... November December (long month name)
|
|
Q, // 1 2 3 4 (quarter)
|
|
Qo, // 1st 2nd 3rd 4th (quarter)
|
|
QO, // 1ˢᵗ 2ⁿᵈ 3ʳᵈ 4ᵗʰ (quarter)
|
|
D, // 1 2 ... 30 31 (day of the month)
|
|
Do, // 1st 2nd ... 30th 31st (day of the month, ordinal)
|
|
DO, // 1ˢᵗ 2ⁿᵈ 3ʳᵈ... 30ᵗʰ 31ˢᵗ (day of the month, ordinal)
|
|
DD, // 01 02 ... 30 31 (day of the month, zero padded)
|
|
DDD, // 1 2 ... 364 365
|
|
DDDo, // 1st 2nd ... 364th 365th (day of the year, ordinal)
|
|
DDDO, // 1ˢᵗ 2ⁿᵈ ... 364ᵗʰ 365ᵗʰ (day of the year, ordinal)
|
|
DDDD, // 001 002 ... 364 365 (day of the year)
|
|
d, // 0 1 ... 5 6 (day of the week)
|
|
do, // 0th 1st 2nd 3rd ... 5th 6th (day of the week, ordinal)
|
|
dO, // 0ᵗʰ 1ˢᵗ 2ⁿᵈ 3ʳᵈ ... 5ᵗʰ 6ᵗʰ (day of the week, ordinal)
|
|
dd, // Su Mo ... Fr Sa (day of the week, very short name)
|
|
ddd, // Sun Mon ... Fri Sat (day of the week, short name)
|
|
dddd, // Sunday Monday ... Friday Saturday (day of the week, long name)
|
|
e, // 0 1 ... 5 6 (locale)
|
|
E, // 1 2 ... 6 7 (ISO)
|
|
w, // 1 2 ... 52 53
|
|
wo, // 1st 2nd 3rd 4th ... 52nd 53rd
|
|
wO, // 1ˢᵗ 2ⁿᵈ 3ʳᵈ 4th ... 52ⁿᵈ 53ʳᵈ
|
|
ww, // 01 02 ... 52 53
|
|
YY, // 70 71 ... 29 30 (year, last two digits only)
|
|
YYY, // 1 2 ... 1970 1971 ... 2029 2030 (year)
|
|
YYYY, // 0001 0002 ... 1970 1971 ... 2029 2030 (year, zero padded to 4 digits)
|
|
// N, // BC AD
|
|
// NN, // Before Christ ... Anno Domini
|
|
A, // AM PM (ante/post meridian, upper case)
|
|
a, // am pm (ante/post meridian, lower case)
|
|
H, // 0 1 ... 22 23 (hour, zero padded)
|
|
HH, // 00 01 ... 22 23 (hour, zero padded)
|
|
h, // 1 2 ... 11 12 (hour, 12 hour clock)
|
|
hh, // 01 02 ... 11 12 (hour, 12 hour clock, zero padded)
|
|
k, // 1 2 ... 23 24
|
|
kk, // 01 02 ... 23 24
|
|
m, // 0 1 ... 58 59 (minute)
|
|
mm, // 00 01 ... 58 59 (minute, zero padded)
|
|
s, // 0 1 ... 58 59 (second)
|
|
ss, // 00 01 ... 58 59 (second, zero padded)
|
|
S, // 0 1 ... 8 9 (tenths of a second)
|
|
SS, // 00 01 ... 98 99(hundredths second fraction)
|
|
SSS, // 000 001 ... 998 999 (milliseconds)
|
|
SSSS, // 0000 0000 ... 9998 9999 (hundreds of microseconds)
|
|
SSSSS, // 00000 00000 ... 99998 99999 (tens of microseconds)
|
|
SSSSSS, // 000000 000000 ... 999998 999999 (microseconds)
|
|
SSSSSSS, // 0000000 00000000 ... 9999998 9999999 (hundreds of nanoseconds)
|
|
SSSSSSSS, // 00000000 000000000 ... 99999998 99999999 (tens of nanoseconds)
|
|
SSSSSSSSS, // 000000000 000000000 ... 999999998 999999999 (nanoseconds)
|
|
// z, // EST CST ... MST PST
|
|
// Z, // -07:00 -06:00 ... +06:00 +07:00
|
|
// ZZ, // -0700 -0600 ... +0600 +0700
|
|
// x, // unix milli
|
|
// X, // unix
|
|
};
|
|
|
|
pub fn dayOfThisYear(self: Self) u9 {
|
|
return self.month.daysBefore(self.year) + self.day;
|
|
}
|
|
};
|
|
|
|
test "formatTest" {
|
|
var buffer: [1024]u8 = undefined;
|
|
var fba = std.heap.FixedBufferAllocator.init(&buffer);
|
|
const allocator = fba.allocator();
|
|
|
|
const epoch = DateTime{
|
|
.nanosecond = 123456789,
|
|
.second = 0,
|
|
.minute = 0,
|
|
.hour = 0,
|
|
.day = 1,
|
|
.month = .Jan,
|
|
.year = 1970,
|
|
.weekday = .Fri,
|
|
};
|
|
const cases = [_]struct { datetime: DateTime, fmt: []const u8, result: []const u8 }{
|
|
.{
|
|
.datetime = epoch,
|
|
.fmt = "YYYY-MM-DDTHH:mm:ss.SSSSSSSSS",
|
|
.result = "1970-01-01T00:00:00.123456789",
|
|
},
|
|
.{
|
|
.datetime = epoch,
|
|
.fmt = "YYYY-MM-DDTHH:mm:ss.SSSSSSSS",
|
|
.result = "1970-01-01T00:00:00.12345678",
|
|
},
|
|
.{
|
|
.datetime = epoch,
|
|
.fmt = "YYYY-MM-DDTHH:mm:ss.SSSSSSS",
|
|
.result = "1970-01-01T00:00:00.1234567",
|
|
},
|
|
.{
|
|
.datetime = epoch,
|
|
.fmt = "YYYY-MM-DDTHH:mm:ss.SSSSSS",
|
|
.result = "1970-01-01T00:00:00.123456",
|
|
},
|
|
.{
|
|
.datetime = epoch,
|
|
.fmt = "YYYY-MM-DDTHH:mm:ss.SSSSS",
|
|
.result = "1970-01-01T00:00:00.12345",
|
|
},
|
|
.{
|
|
.datetime = epoch,
|
|
.fmt = "YYYY-MM-DDTHH:mm:ss.SSSS",
|
|
.result = "1970-01-01T00:00:00.1234",
|
|
},
|
|
.{
|
|
.datetime = epoch,
|
|
.fmt = "YYYY-MM-DDTHH:mm:ss.SSS",
|
|
.result = "1970-01-01T00:00:00.123",
|
|
},
|
|
.{
|
|
.datetime = epoch,
|
|
.fmt = "YYYY-MM-DDTHH:mm:ss.SS",
|
|
.result = "1970-01-01T00:00:00.12",
|
|
},
|
|
.{
|
|
.datetime = epoch,
|
|
.fmt = "YYYY-MM-DDTHH:mm:ss.S",
|
|
.result = "1970-01-01T00:00:00.1",
|
|
},
|
|
};
|
|
|
|
inline for (cases) |case| {
|
|
var array = std.ArrayList(u8).init(allocator);
|
|
defer array.deinit();
|
|
|
|
try case.datetime.format(case.fmt, .{}, array.writer());
|
|
|
|
std.testing.expect(std.mem.eql(u8, array.items, case.result)) catch |err| {
|
|
std.debug.print("{s} {s} {s}\n", .{
|
|
case.fmt,
|
|
case.result,
|
|
array.items,
|
|
});
|
|
return err;
|
|
};
|
|
}
|
|
}
|
|
|
|
pub const tenthsPerNanoSecond = hundredthsPerNanoSecond * 10;
|
|
pub const hundredthsPerNanoSecond = milliSecondsPerNanoSecond * 10;
|
|
pub const milliSecondsPerNanoSecond = microSecondsPerNanoSecond * 1_000;
|
|
pub const microSecondsPerNanoSecond = 1_000;
|
|
pub const nanoSecondsPerSecond = microSecondsPerNanoSecond * 1_000;
|
|
pub const microSecondsPerSecond = milliSecondsPerSecond * 1_000;
|
|
pub const milliSecondsPerSecond = 1_000;
|
|
pub const secondsPerMinute = 60;
|
|
pub const minutesPerHour = 60;
|
|
pub const secondsPerHour = minutesPerHour * secondsPerMinute;
|
|
pub const hoursPerDay = 24;
|
|
pub const secodsPerDay = hoursPerDay * minutesPerHour * secondsPerMinute;
|
|
|
|
// const daysSinceJan1st = [2][13]u32{
|
|
// [_]u32{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, // 365 days, non-leap
|
|
// [_]u32{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }, // 366 days, leap
|
|
// };
|
|
|
|
pub const Instant = struct {
|
|
timestamp: i128,
|
|
timezone: []const u8,
|
|
|
|
const Self = @This();
|
|
|
|
pub fn now() Self {
|
|
return Instant{
|
|
.timestamp = std.time.nanoTimestamp(),
|
|
.timezone = "UTC",
|
|
};
|
|
}
|
|
|
|
pub fn utc() Self {
|
|
return Instant{
|
|
.timestamp = std.time.nanoTimestamp(),
|
|
.timezone = "UTC",
|
|
};
|
|
}
|
|
|
|
pub fn fromNanoTimeStamp(timestamp: i128) Self {
|
|
return Self{
|
|
.timestamp = timestamp,
|
|
.timezone = "UTC",
|
|
};
|
|
}
|
|
|
|
pub fn asDateTime(self: Self) DateTime {
|
|
const nanosecond: Nanosecond = @intCast(@mod(
|
|
self.timestamp,
|
|
nanoSecondsPerSecond,
|
|
));
|
|
var seconds = @divTrunc(
|
|
self.timestamp,
|
|
nanoSecondsPerSecond,
|
|
);
|
|
const second: Second = @intCast(@mod(seconds, secondsPerMinute));
|
|
seconds -= second;
|
|
|
|
const minute: Minute = @intCast(@divTrunc(
|
|
@mod(seconds, secondsPerHour),
|
|
secondsPerMinute,
|
|
));
|
|
seconds -= @as(u32, minute) * secondsPerMinute;
|
|
|
|
const hour: Hour = @intCast(@divTrunc(
|
|
@mod(seconds, secodsPerDay),
|
|
secondsPerHour,
|
|
));
|
|
seconds -= @as(u32, hour) * secondsPerHour;
|
|
|
|
const days: i32 = @intCast(@divTrunc(
|
|
seconds,
|
|
secodsPerDay,
|
|
));
|
|
|
|
const date = civilFromDays(days);
|
|
|
|
return DateTime{
|
|
.year = date.year,
|
|
.month = date.month,
|
|
.day = date.day,
|
|
.hour = hour,
|
|
.minute = minute,
|
|
.second = second,
|
|
.nanosecond = nanosecond,
|
|
.weekday = weekdayFromDays(days),
|
|
};
|
|
}
|
|
// pub fn format(self: @This()) []const u8 {
|
|
// std.debug.print("{i} {i} {i} {i} {i} {i} {i}", .{ year, month, mday, hour, minute, second, nanosecond });
|
|
// }
|
|
};
|
|
|
|
pub fn daysFromCivil(year: Year, month: Month, d: Day) i32 {
|
|
std.debug.assert(d >= 1 and d <= month.lastDay(year));
|
|
|
|
const janOrFeb = month == .Jan or month == .Feb;
|
|
|
|
const m = month.monthNumber();
|
|
|
|
const y = year - (if (janOrFeb) @as(Year, 1) else @as(Year, 0));
|
|
|
|
const era = @divTrunc(
|
|
if (y >= 0) y else y - 399,
|
|
400,
|
|
);
|
|
|
|
const yoe = y - era * 400;
|
|
std.debug.assert(yoe >= 0 and yoe <= 399);
|
|
|
|
const doy = @divTrunc(
|
|
153 * (m + (if (!janOrFeb) @as(i32, -3) else @as(i32, 9))) + 2,
|
|
5,
|
|
) + d - 1;
|
|
std.debug.assert(doy >= 0 and doy <= 365);
|
|
|
|
const doe = yoe * 365 + @divTrunc(yoe, 4) - @divTrunc(yoe, 100) + doy;
|
|
std.debug.assert(doe >= 0 and doe <= 146096);
|
|
|
|
return era * 146097 + doe - 719468;
|
|
}
|
|
|
|
test "daysFromCivil" {
|
|
const tests = [_]struct { year: Year, month: Month, day: Day, result: i128 }{
|
|
.{ .year = 1970, .month = .Jan, .day = 1, .result = 0 },
|
|
.{ .year = 1970, .month = .Jan, .day = 2, .result = 1 },
|
|
.{ .year = 1969, .month = .Dec, .day = 31, .result = -1 },
|
|
};
|
|
|
|
for (tests) |case| {
|
|
const result = daysFromCivil(case.year, case.month, case.day);
|
|
try std.testing.expect(std.meta.eql(case.result, result));
|
|
}
|
|
}
|
|
|
|
const Date = struct {
|
|
year: Year,
|
|
month: Month,
|
|
day: Day,
|
|
};
|
|
|
|
pub fn civilFromDays(days: i32) Date {
|
|
const z = days + 719468;
|
|
|
|
const era = @divTrunc(if (z >= 0) z else z - 146096, 146097);
|
|
|
|
const doe = (z - era * 146097);
|
|
std.debug.assert(doe >= 0 and doe <= 146096);
|
|
|
|
const yoe = @divTrunc(doe - @divTrunc(doe, 1460) + @divTrunc(doe, 36524) - @divTrunc(doe, 146096), 365);
|
|
std.debug.assert(yoe >= 0 and yoe <= 399);
|
|
|
|
const y = yoe + era * 400;
|
|
|
|
const doy = doe - (365 * yoe + @divTrunc(yoe, 4) - @divTrunc(yoe, 100));
|
|
std.debug.assert(doy >= 0 and doy <= 365);
|
|
|
|
const mp = @divTrunc(5 * doy + 2, 153);
|
|
std.debug.assert(mp >= 0 and mp <= 11);
|
|
|
|
const d = doy - @divTrunc(153 * mp + 2, 5) + 1;
|
|
std.debug.assert(d >= 1 and d <= 31);
|
|
|
|
const m = mp + (if (mp < 10) @as(i32, 3) else @as(i32, -9));
|
|
std.debug.assert(m >= 1 and m <= 12);
|
|
|
|
return Date{
|
|
.year = y + (if (m <= 2) @as(Year, 1) else @as(Year, 0)),
|
|
.month = @enumFromInt(m),
|
|
.day = @intCast(d),
|
|
};
|
|
}
|
|
|
|
test "civilFromDays" {
|
|
const tests = [_]struct { days: i32, result: Date }{
|
|
.{
|
|
.days = 0,
|
|
.result = Date{
|
|
.year = 1970,
|
|
.month = .Jan,
|
|
.day = 1,
|
|
},
|
|
},
|
|
.{
|
|
.days = 1,
|
|
.result = Date{
|
|
.year = 1970,
|
|
.month = .Jan,
|
|
.day = 2,
|
|
},
|
|
},
|
|
.{
|
|
.days = -1,
|
|
.result = Date{
|
|
.year = 1969,
|
|
.month = .Dec,
|
|
.day = 31,
|
|
},
|
|
},
|
|
.{
|
|
.days = 19605,
|
|
.result = Date{
|
|
.year = 2023,
|
|
.month = .Sep,
|
|
.day = 5,
|
|
},
|
|
},
|
|
};
|
|
|
|
for (tests) |case| {
|
|
const result = civilFromDays(case.days);
|
|
try std.testing.expect(std.meta.eql(case.result, result));
|
|
}
|
|
}
|
|
|
|
const DayOfWeek = enum(u3) {
|
|
Sun = 0,
|
|
Mon = 1,
|
|
Tue = 2,
|
|
Wed = 3,
|
|
Thu = 4,
|
|
Fri = 5,
|
|
Sat = 6,
|
|
|
|
const Self = @This();
|
|
|
|
pub fn next(self: Self) Self {
|
|
return switch (self) {
|
|
.Sun => .Mon,
|
|
.Mon => .Tue,
|
|
.Tue => .Wed,
|
|
.Wed => .Thu,
|
|
.Thu => .Fri,
|
|
.Fri => .Sat,
|
|
.Sat => .Sun,
|
|
};
|
|
}
|
|
|
|
pub fn prev(self: Self) Self {
|
|
return switch (self) {
|
|
.Sun => .Sat,
|
|
.Mon => .Sun,
|
|
.Tue => .Mon,
|
|
.Wed => .Tue,
|
|
.Thu => .Wed,
|
|
.Fri => .Thu,
|
|
.Sat => .Fri,
|
|
};
|
|
}
|
|
|
|
pub fn veryShortName(self: Self) []const u8 {
|
|
return switch (self) {
|
|
.Sun => "Su",
|
|
.Mon => "Mo",
|
|
.Tue => "Tu",
|
|
.Wed => "We",
|
|
.Thu => "Th",
|
|
.Fri => "Fr",
|
|
.Sat => "Sa",
|
|
};
|
|
}
|
|
|
|
pub fn shortName(self: Self) []const u8 {
|
|
return switch (self) {
|
|
.Sun => "Sun",
|
|
.Mon => "Mon",
|
|
.Tue => "Tue",
|
|
.Wed => "Wed",
|
|
.Thu => "Thu",
|
|
.Fri => "Fri",
|
|
.Sat => "Sat",
|
|
};
|
|
}
|
|
|
|
pub fn longName(self: Self) []const u8 {
|
|
return switch (self) {
|
|
.Sun => "Sunday",
|
|
.Mon => "Monday",
|
|
.Tue => "Tuesday",
|
|
.Wed => "Wednesday",
|
|
.Thu => "Thursday",
|
|
.Fri => "Friday",
|
|
.Sat => "Saturday",
|
|
};
|
|
}
|
|
|
|
pub fn weekdayNumber(self: Self) u3 {
|
|
return @intFromEnum(self);
|
|
// return switch (self) {
|
|
// .Sun => 0,
|
|
// .Mon => 1,
|
|
// .Tue => 2,
|
|
// .Wed => 3,
|
|
// .Thu => 4,
|
|
// .Fri => 5,
|
|
// .Sat => 6,
|
|
// };
|
|
}
|
|
pub fn isoWeekdayNumber(self: Self) u3 {
|
|
return if (self == .Sun) 7 else @intFromEnum(self);
|
|
// return switch (self) {
|
|
// .Sun => 7,
|
|
// .Mon => 1,
|
|
// .Tue => 2,
|
|
// .Wed => 3,
|
|
// .Thu => 4,
|
|
// .Fri => 5,
|
|
// .Sat => 6,
|
|
// };
|
|
}
|
|
};
|
|
|
|
pub fn weekdayDifference(start: DayOfWeek, end: DayOfWeek) u3 {
|
|
const d = @as(i4, end.weekdayNumber()) - @as(i4, start.weekdayNumber());
|
|
return if (d >= 0) @intCast(d) else @intCast(d + 7);
|
|
}
|
|
|
|
test "weekdayDifference" {
|
|
const difference = [7][7]u3{
|
|
[_]u3{ 0, 1, 2, 3, 4, 5, 6 },
|
|
[_]u3{ 6, 0, 1, 2, 3, 4, 5 },
|
|
[_]u3{ 5, 6, 0, 1, 2, 3, 4 },
|
|
[_]u3{ 4, 5, 6, 0, 1, 2, 3 },
|
|
[_]u3{ 3, 4, 5, 6, 0, 1, 2 },
|
|
[_]u3{ 2, 3, 4, 5, 6, 0, 1 },
|
|
[_]u3{ 1, 2, 3, 4, 5, 6, 0 },
|
|
};
|
|
for (0..6) |start| {
|
|
for (0..6) |end| {
|
|
const result = weekdayDifference(
|
|
@as(DayOfWeek, @enumFromInt(start)),
|
|
@as(DayOfWeek, @enumFromInt(end)),
|
|
);
|
|
// try std.debug.print("{any} {any} {any}\n", .{})
|
|
try std.testing.expect(result == difference[start][end]);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn weekdayFromDays(z: i32) DayOfWeek {
|
|
return @as(
|
|
DayOfWeek,
|
|
@enumFromInt(if (z >= -4) @rem(z + 4, 7) else @rem(z + 5, 7) + 6),
|
|
);
|
|
}
|
|
|
|
test "weekdayFromDays" {
|
|
const tests = [_]struct { days: i32, result: DayOfWeek }{
|
|
.{
|
|
.days = -1,
|
|
.result = .Wed,
|
|
},
|
|
.{
|
|
.days = 0,
|
|
.result = .Thu,
|
|
},
|
|
.{
|
|
.days = 1,
|
|
.result = .Fri,
|
|
},
|
|
.{
|
|
.days = 19605,
|
|
.result = .Tue,
|
|
},
|
|
};
|
|
|
|
for (tests) |case| {
|
|
const result = weekdayFromDays(case.days);
|
|
try std.testing.expect(std.meta.eql(case.result, result));
|
|
}
|
|
}
|
|
|
|
fn printOrdinal(writer: anytype, num: u16) !void {
|
|
try writer.print("{}", .{num});
|
|
try writer.writeAll(if (num >= 11 and num <= 13) "th" else switch (num % 10) {
|
|
1 => "st",
|
|
2 => "nd",
|
|
3 => "rd",
|
|
else => "th",
|
|
});
|
|
}
|
|
|
|
test "testPrintOrdinal" {
|
|
var buffer: [1024]u8 = undefined;
|
|
var fba = std.heap.FixedBufferAllocator.init(&buffer);
|
|
const allocator = fba.allocator();
|
|
|
|
const cases = [_]struct { number: u16, result: []const u8 }{
|
|
.{
|
|
.number = 0,
|
|
.result = "0th",
|
|
},
|
|
.{
|
|
.number = 1,
|
|
.result = "1st",
|
|
},
|
|
.{
|
|
.number = 2,
|
|
.result = "2nd",
|
|
},
|
|
.{
|
|
.number = 3,
|
|
.result = "3rd",
|
|
},
|
|
.{
|
|
.number = 4,
|
|
.result = "4th",
|
|
},
|
|
.{
|
|
.number = 11,
|
|
.result = "11th",
|
|
},
|
|
.{
|
|
.number = 12,
|
|
.result = "12th",
|
|
},
|
|
.{
|
|
.number = 13,
|
|
.result = "13th",
|
|
},
|
|
.{
|
|
.number = 14,
|
|
.result = "14th",
|
|
},
|
|
.{
|
|
.number = 31,
|
|
.result = "31st",
|
|
},
|
|
};
|
|
|
|
inline for (cases) |case| {
|
|
var array = std.ArrayList(u8).init(allocator);
|
|
defer array.deinit();
|
|
|
|
try printOrdinal(array.writer(), case.number);
|
|
std.testing.expect(std.mem.eql(u8, array.items, case.result)) catch |err| {
|
|
std.debug.print("{d} failed {s} != {s}\n", .{ case.number, array.items, case.result });
|
|
return err;
|
|
};
|
|
}
|
|
}
|
|
|
|
fn printOrdinalSuperscript(writer: anytype, num: u16) !void {
|
|
try writer.print("{}", .{num});
|
|
try writer.writeAll(if (num >= 11 and num <= 13) "ᵗʰ" else switch (num % 10) {
|
|
1 => "ˢᵗ",
|
|
2 => "ⁿᵈ",
|
|
3 => "ʳᵈ",
|
|
else => "ᵗʰ",
|
|
});
|
|
}
|
|
|
|
test "testPrintOrdinalSuperscript" {
|
|
// var buffer: [1024]u8 = undefined;
|
|
// var fbs = std.io.fixedBufferStream(buffer);
|
|
// const allocator = fba.allocator();
|
|
|
|
const cases = [_]struct { number: u16, result: []const u8 }{
|
|
.{
|
|
.number = 0,
|
|
.result = "0ᵗʰ",
|
|
},
|
|
.{
|
|
.number = 1,
|
|
.result = "1ˢᵗ",
|
|
},
|
|
.{
|
|
.number = 2,
|
|
.result = "2ⁿᵈ",
|
|
},
|
|
.{
|
|
.number = 3,
|
|
.result = "3ʳᵈ",
|
|
},
|
|
.{
|
|
.number = 4,
|
|
.result = "4ᵗʰ",
|
|
},
|
|
.{
|
|
.number = 11,
|
|
.result = "11ᵗʰ",
|
|
},
|
|
.{
|
|
.number = 12,
|
|
.result = "12ᵗʰ",
|
|
},
|
|
.{
|
|
.number = 13,
|
|
.result = "13ᵗʰ",
|
|
},
|
|
.{
|
|
.number = 14,
|
|
.result = "14ᵗʰ",
|
|
},
|
|
.{
|
|
.number = 31,
|
|
.result = "31ˢᵗ",
|
|
},
|
|
};
|
|
|
|
inline for (cases) |case| {
|
|
var buffer: [16]u8 = undefined;
|
|
var stream = std.io.fixedBufferStream(&buffer);
|
|
var writer = stream.writer();
|
|
|
|
try printOrdinalSuperscript(writer, case.number);
|
|
std.testing.expect(std.mem.eql(u8, stream.getWritten(), case.result)) catch |err| {
|
|
std.debug.print("{d} failed {s} != {s}\n", .{ case.number, stream.getWritten(), case.result });
|
|
return err;
|
|
};
|
|
}
|
|
}
|
|
|
|
fn printLongName(writer: anytype, index: u16, names: []const []const u8) !void {
|
|
try writer.writeAll(names[index]);
|
|
}
|
|
|
|
fn wrap(val: u16, at: u16) u16 {
|
|
var tmp = val % at;
|
|
return if (tmp == 0) at else tmp;
|
|
}
|
|
|
|
test "asDateTime" {
|
|
_ = Instant.utc().asDateTime();
|
|
}
|
|
|
|
fn isLeap(year: i32) bool {
|
|
return @rem(year, 4) == 0 and (@rem(year, 100) != 0 or @rem(year, 400) == 0);
|
|
}
|
|
|
|
test "isLeap" {
|
|
try std.testing.expectEqual(false, isLeap(2005));
|
|
try std.testing.expectEqual(true, isLeap(2096));
|
|
try std.testing.expectEqual(false, isLeap(2100));
|
|
try std.testing.expectEqual(true, isLeap(2400));
|
|
}
|
|
|
|
test "bigTest" {
|
|
const ystart = -1000000;
|
|
var prev_z: i32 = daysFromCivil(ystart, .Jan, 1) - 1;
|
|
try std.testing.expect(prev_z < 0);
|
|
var prev_wd = weekdayFromDays(prev_z);
|
|
try std.testing.expect(0 <= @intFromEnum(prev_wd) and @intFromEnum(prev_wd) <= 6);
|
|
var y: Year = ystart;
|
|
while (y <= -ystart) {
|
|
for ([_]Month{ .Jan, .Feb, .Mar, .Apr, .May, .Jun, .Jul, .Aug, .Sep, .Oct, .Nov, .Dec }) |m| {
|
|
var d: Day = 1;
|
|
const e = m.lastDay(y);
|
|
while (d <= e) {
|
|
// std.debug.print("{d} {d} {d}\n", .{ y, @intFromEnum(m), d });
|
|
const z = daysFromCivil(y, m, d);
|
|
// std.debug.print("{d} {d}\n", .{ prev_z, z });
|
|
try std.testing.expect(prev_z < z);
|
|
try std.testing.expect(z == prev_z + 1);
|
|
const date = civilFromDays(z);
|
|
try std.testing.expect(y == date.year);
|
|
try std.testing.expect(m == date.month);
|
|
try std.testing.expect(d == date.day);
|
|
const wd = weekdayFromDays(z);
|
|
try std.testing.expect(0 <= @intFromEnum(wd) and @intFromEnum(wd) <= 6);
|
|
try std.testing.expect(wd == prev_wd.next());
|
|
try std.testing.expect(prev_wd == wd.prev());
|
|
prev_z = z;
|
|
prev_wd = wd;
|
|
d += 1;
|
|
}
|
|
}
|
|
y += 1;
|
|
}
|
|
}
|