Base date type
This commit is contained in:
parent
783697d4df
commit
69069d39db
499
src/types/date.zig
Normal file
499
src/types/date.zig
Normal file
@ -0,0 +1,499 @@
|
|||||||
|
// Thanks to: https://github.com/nektro/zig-time/blob/master/time.zig
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const string = []const u8;
|
||||||
|
const extras = @import("extras");
|
||||||
|
const time = @This();
|
||||||
|
|
||||||
|
pub const DateTime = struct {
|
||||||
|
ms: u16,
|
||||||
|
seconds: u8,
|
||||||
|
minutes: u8,
|
||||||
|
hours: u8,
|
||||||
|
days: u8,
|
||||||
|
months: u8,
|
||||||
|
years: u16,
|
||||||
|
timezone: TimeZone,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn initUnixMs(unix: u64) Self {
|
||||||
|
return epoch_unix.addMs(unix);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initUnix(unix: u64) Self {
|
||||||
|
return epoch_unix.addSecs(unix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Caller asserts that this is > epoch
|
||||||
|
pub fn init(year: u16, month: u16, day: u16, hr: u16, min: u16, sec: u16) Self {
|
||||||
|
return epoch_unix
|
||||||
|
.addYears(year - epoch_unix.years)
|
||||||
|
.addMonths(month)
|
||||||
|
.addDays(day)
|
||||||
|
.addHours(hr)
|
||||||
|
.addMins(min)
|
||||||
|
.addSecs(sec);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn now() Self {
|
||||||
|
return initUnixMs(@intCast(std.time.milliTimestamp()));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const epoch_unix = Self{
|
||||||
|
.ms = 0,
|
||||||
|
.seconds = 0,
|
||||||
|
.minutes = 0,
|
||||||
|
.hours = 0,
|
||||||
|
.days = 0,
|
||||||
|
.months = 0,
|
||||||
|
.years = 1970, // Why ?
|
||||||
|
.timezone = .UTC,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn eql(self: Self, other: Self) bool {
|
||||||
|
return self.ms == other.ms and
|
||||||
|
self.seconds == other.seconds and
|
||||||
|
self.minutes == other.minutes and
|
||||||
|
self.hours == other.hours and
|
||||||
|
self.days == other.days and
|
||||||
|
self.months == other.months and
|
||||||
|
self.years == other.years and
|
||||||
|
self.timezone == other.timezone and
|
||||||
|
self.weekday == other.weekday;
|
||||||
|
}
|
||||||
|
|
||||||
|
// So as long as the count / unit, it continue adding the next unit, smart
|
||||||
|
pub fn addMs(self: Self, count: u64) Self {
|
||||||
|
if (count == 0) return self;
|
||||||
|
var result = self;
|
||||||
|
result.ms += @intCast(count % 1000);
|
||||||
|
return result.addSecs(count / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addSecs(self: Self, count: u64) Self {
|
||||||
|
if (count == 0) return self;
|
||||||
|
var result = self;
|
||||||
|
result.seconds += @intCast(count % 60);
|
||||||
|
return result.addMins(count / 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addMins(self: Self, count: u64) Self {
|
||||||
|
if (count == 0) return self;
|
||||||
|
var result = self;
|
||||||
|
result.minutes += @intCast(count % 60);
|
||||||
|
return result.addHours(count / 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addHours(self: Self, count: u64) Self {
|
||||||
|
if (count == 0) return self;
|
||||||
|
var result = self;
|
||||||
|
result.hours += @intCast(count % 24);
|
||||||
|
return result.addDays(count / 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addDays(self: Self, count: u64) Self {
|
||||||
|
if (count == 0) return self;
|
||||||
|
var result = self;
|
||||||
|
var input = count;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const year_len = result.daysThisYear();
|
||||||
|
if (input >= year_len) {
|
||||||
|
result.years += 1;
|
||||||
|
input -= year_len;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
const month_len = result.daysThisMonth();
|
||||||
|
if (input >= month_len) {
|
||||||
|
result.months += 1;
|
||||||
|
input -= month_len;
|
||||||
|
|
||||||
|
if (result.months == 12) {
|
||||||
|
result.years += 1;
|
||||||
|
result.months = 0;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const month_len = result.daysThisMonth();
|
||||||
|
if (result.days + input > month_len) {
|
||||||
|
const left = month_len - result.days;
|
||||||
|
input -= left;
|
||||||
|
result.months += 1;
|
||||||
|
result.days = 0;
|
||||||
|
}
|
||||||
|
result.days += @intCast(input);
|
||||||
|
|
||||||
|
if (result.days == result.daysThisMonth()) {
|
||||||
|
result.months += 1;
|
||||||
|
result.days = 0;
|
||||||
|
}
|
||||||
|
if (result.months == 12) {
|
||||||
|
result.years += 1;
|
||||||
|
result.months = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std.debug.assert(result.ms < 1000);
|
||||||
|
std.debug.assert(result.seconds < 60);
|
||||||
|
std.debug.assert(result.minutes < 60);
|
||||||
|
std.debug.assert(result.hours < 24);
|
||||||
|
std.debug.assert(result.days < result.daysThisMonth());
|
||||||
|
std.debug.assert(result.months < 12);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addMonths(self: Self, count: u64) Self {
|
||||||
|
if (count == 0) return self;
|
||||||
|
var result = self;
|
||||||
|
var input = count;
|
||||||
|
while (input > 0) {
|
||||||
|
const new = result.addDays(result.daysThisMonth());
|
||||||
|
result = new;
|
||||||
|
input -= 1;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addYears(self: Self, count: u64) Self {
|
||||||
|
var result = self;
|
||||||
|
for (0..count) |_| {
|
||||||
|
result = result.addDays(result.daysThisYear());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isLeapYear(self: Self) bool {
|
||||||
|
return time.isLeapYear(self.years);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn daysThisYear(self: Self) u16 {
|
||||||
|
return time.daysInYear(self.years);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn daysThisMonth(self: Self) u16 {
|
||||||
|
return self.daysInMonth(self.months);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn daysInMonth(self: Self, month: u16) u16 {
|
||||||
|
return time.daysInMonth(self.years, month);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dayOfThisYear(self: Self) u16 {
|
||||||
|
var ret: u16 = 0;
|
||||||
|
for (0..self.months) |item| {
|
||||||
|
ret += self.daysInMonth(@intCast(item));
|
||||||
|
}
|
||||||
|
ret += self.days;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toUnix(self: Self) u64 {
|
||||||
|
const x = self.toUnixMilli();
|
||||||
|
return x / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toUnixMilli(self: Self) u64 {
|
||||||
|
var res: u64 = 0;
|
||||||
|
res += self.ms;
|
||||||
|
res += @as(u64, self.seconds) * std.time.ms_per_s;
|
||||||
|
res += @as(u64, self.minutes) * std.time.ms_per_min;
|
||||||
|
res += @as(u64, self.hours) * std.time.ms_per_hour;
|
||||||
|
res += self.daysSinceEpoch() * std.time.ms_per_day;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn daysSinceEpoch(self: Self) u64 {
|
||||||
|
var res: u64 = 0;
|
||||||
|
res += self.days;
|
||||||
|
for (0..self.years - epoch_unix.years) |i| res += time.daysInYear(@intCast(i));
|
||||||
|
for (0..self.months) |i| res += self.daysInMonth(@intCast(i));
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// fmt is based on https://momentjs.com/docs/#/displaying/format/
|
||||||
|
pub fn format(self: Self, comptime fmt: string, 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;
|
||||||
|
|
||||||
|
if (comptime std.meta.stringToEnum(FormatSeq, fmt[s..e])) |tag| {
|
||||||
|
next = tag;
|
||||||
|
if (i < fmt.len - 1) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next) |tag| {
|
||||||
|
switch (tag) {
|
||||||
|
.MM => try writer.print("{:0>2}", .{self.months + 1}),
|
||||||
|
.M => try writer.print("{}", .{self.months + 1}),
|
||||||
|
.Mo => try printOrdinal(writer, self.months + 1),
|
||||||
|
.MMM => try printLongName(writer, self.months, &[_]string{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }),
|
||||||
|
.MMMM => try printLongName(writer, self.months, &[_]string{ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }),
|
||||||
|
|
||||||
|
.Q => try writer.print("{}", .{self.months / 3 + 1}),
|
||||||
|
.Qo => try printOrdinal(writer, self.months / 3 + 1),
|
||||||
|
|
||||||
|
.D => try writer.print("{}", .{self.days + 1}),
|
||||||
|
.Do => try printOrdinal(writer, self.days + 1),
|
||||||
|
.DD => try writer.print("{:0>2}", .{self.days + 1}),
|
||||||
|
|
||||||
|
.DDD => try writer.print("{}", .{self.dayOfThisYear() + 1}),
|
||||||
|
.DDDo => try printOrdinal(writer, self.dayOfThisYear() + 1),
|
||||||
|
.DDDD => try writer.print("{:0>3}", .{self.dayOfThisYear() + 1}),
|
||||||
|
|
||||||
|
.d => try writer.print("{}", .{@intFromEnum(self.weekday())}),
|
||||||
|
.do => try printOrdinal(writer, @intFromEnum(self.weekday())),
|
||||||
|
.dd => try writer.writeAll(@tagName(self.weekday())[0..2]),
|
||||||
|
.ddd => try writer.writeAll(@tagName(self.weekday())),
|
||||||
|
.dddd => try printLongName(writer, @intFromEnum(self.weekday()), &[_]string{ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }),
|
||||||
|
.e => try writer.print("{}", .{@intFromEnum(self.weekday())}),
|
||||||
|
.E => try writer.print("{}", .{@intFromEnum(self.weekday()) + 1}),
|
||||||
|
|
||||||
|
.w => try writer.print("{}", .{self.dayOfThisYear() / 7 + 1}),
|
||||||
|
.wo => try printOrdinal(writer, self.dayOfThisYear() / 7 + 1),
|
||||||
|
.ww => try writer.print("{:0>2}", .{self.dayOfThisYear() / 7 + 1}),
|
||||||
|
|
||||||
|
.Y => try writer.print("{}", .{self.years + 10000}),
|
||||||
|
.YY => try writer.print("{:0>2}", .{self.years % 100}),
|
||||||
|
.YYY => try writer.print("{}", .{self.years}),
|
||||||
|
.YYYY => try writer.print("{:0>4}", .{self.years}),
|
||||||
|
|
||||||
|
.N => try writer.writeAll(@tagName(self.era())),
|
||||||
|
.NN => try writer.writeAll("Anno Domini"),
|
||||||
|
|
||||||
|
.A => try printLongName(writer, self.hours / 12, &[_]string{ "AM", "PM" }),
|
||||||
|
.a => try printLongName(writer, self.hours / 12, &[_]string{ "am", "pm" }),
|
||||||
|
|
||||||
|
.H => try writer.print("{}", .{self.hours}),
|
||||||
|
.HH => try writer.print("{:0>2}", .{self.hours}),
|
||||||
|
.h => try writer.print("{}", .{wrap(self.hours, 12)}),
|
||||||
|
.hh => try writer.print("{:0>2}", .{wrap(self.hours, 12)}),
|
||||||
|
.k => try writer.print("{}", .{wrap(self.hours, 24)}),
|
||||||
|
.kk => try writer.print("{:0>2}", .{wrap(self.hours, 24)}),
|
||||||
|
|
||||||
|
.m => try writer.print("{}", .{self.minutes}),
|
||||||
|
.mm => try writer.print("{:0>2}", .{self.minutes}),
|
||||||
|
|
||||||
|
.s => try writer.print("{}", .{self.seconds}),
|
||||||
|
.ss => try writer.print("{:0>2}", .{self.seconds}),
|
||||||
|
|
||||||
|
.S => try writer.print("{}", .{self.ms / 100}),
|
||||||
|
.SS => try writer.print("{:0>2}", .{self.ms / 10}),
|
||||||
|
.SSS => try writer.print("{:0>3}", .{self.ms}),
|
||||||
|
|
||||||
|
.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: string) !string {
|
||||||
|
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
|
||||||
|
Mo, // 1st 2nd ... 11th 12th
|
||||||
|
MM, // 01 02 ... 11 12
|
||||||
|
MMM, // Jan Feb ... Nov Dec
|
||||||
|
MMMM, // January February ... November December
|
||||||
|
Q, // 1 2 3 4
|
||||||
|
Qo, // 1st 2nd 3rd 4th
|
||||||
|
D, // 1 2 ... 30 31
|
||||||
|
Do, // 1st 2nd ... 30th 31st
|
||||||
|
DD, // 01 02 ... 30 31
|
||||||
|
DDD, // 1 2 ... 364 365
|
||||||
|
DDDo, // 1st 2nd ... 364th 365th
|
||||||
|
DDDD, // 001 002 ... 364 365
|
||||||
|
d, // 0 1 ... 5 6
|
||||||
|
do, // 0th 1st ... 5th 6th
|
||||||
|
dd, // Su Mo ... Fr Sa
|
||||||
|
ddd, // Sun Mon ... Fri Sat
|
||||||
|
dddd, // Sunday Monday ... Friday Saturday
|
||||||
|
e, // 0 1 ... 5 6 (locale)
|
||||||
|
E, // 1 2 ... 6 7 (ISO)
|
||||||
|
w, // 1 2 ... 52 53
|
||||||
|
wo, // 1st 2nd ... 52nd 53rd
|
||||||
|
ww, // 01 02 ... 52 53
|
||||||
|
Y, // 11970 11971 ... 19999 20000 20001 (Holocene calendar)
|
||||||
|
YY, // 70 71 ... 29 30
|
||||||
|
YYY, // 1 2 ... 1970 1971 ... 2029 2030
|
||||||
|
YYYY, // 0001 0002 ... 1970 1971 ... 2029 2030
|
||||||
|
N, // BC AD
|
||||||
|
NN, // Before Christ ... Anno Domini
|
||||||
|
A, // AM PM
|
||||||
|
a, // am pm
|
||||||
|
H, // 0 1 ... 22 23
|
||||||
|
HH, // 00 01 ... 22 23
|
||||||
|
h, // 1 2 ... 11 12
|
||||||
|
hh, // 01 02 ... 11 12
|
||||||
|
k, // 1 2 ... 23 24
|
||||||
|
kk, // 01 02 ... 23 24
|
||||||
|
m, // 0 1 ... 58 59
|
||||||
|
mm, // 00 01 ... 58 59
|
||||||
|
s, // 0 1 ... 58 59
|
||||||
|
ss, // 00 01 ... 58 59
|
||||||
|
S, // 0 1 ... 8 9 (second fraction)
|
||||||
|
SS, // 00 01 ... 98 99
|
||||||
|
SSS, // 000 001 ... 998 999
|
||||||
|
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 since(self: Self, other_in_the_past: Self) Duration {
|
||||||
|
return Duration{
|
||||||
|
.ms = self.toUnixMilli() - other_in_the_past.toUnixMilli(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn era(self: Self) Era {
|
||||||
|
if (self.years >= 0) return .AD;
|
||||||
|
@compileError("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn weekday(self: Self) WeekDay {
|
||||||
|
var i = self.daysSinceEpoch() % 7;
|
||||||
|
var result = WeekDay.Thu; // weekday of epoch_unix
|
||||||
|
while (i > 0) : (i -= 1) {
|
||||||
|
result = result.next();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const format = struct {
|
||||||
|
pub const LT = "h:mm A";
|
||||||
|
pub const LTS = "h:mm:ss A";
|
||||||
|
pub const L = "MM/DD/YYYY";
|
||||||
|
pub const l = "M/D/YYY";
|
||||||
|
pub const LL = "MMMM D, YYYY";
|
||||||
|
pub const ll = "MMM D, YYY";
|
||||||
|
pub const LLL = LL ++ " " ++ LT;
|
||||||
|
pub const lll = ll ++ " " ++ LT;
|
||||||
|
pub const LLLL = "dddd, " ++ LLL;
|
||||||
|
pub const llll = "ddd, " ++ lll;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const TimeZone = enum {
|
||||||
|
UTC,
|
||||||
|
|
||||||
|
usingnamespace extras.TagNameJsonStringifyMixin(@This());
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const WeekDay = enum {
|
||||||
|
Sun,
|
||||||
|
Mon,
|
||||||
|
Tue,
|
||||||
|
Wed,
|
||||||
|
Thu,
|
||||||
|
Fri,
|
||||||
|
Sat,
|
||||||
|
|
||||||
|
pub fn next(self: WeekDay) WeekDay {
|
||||||
|
return switch (self) {
|
||||||
|
.Sun => .Mon,
|
||||||
|
.Mon => .Tue,
|
||||||
|
.Tue => .Wed,
|
||||||
|
.Wed => .Thu,
|
||||||
|
.Thu => .Fri,
|
||||||
|
.Fri => .Sat,
|
||||||
|
.Sat => .Sun,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
usingnamespace extras.TagNameJsonStringifyMixin(@This());
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Era = enum {
|
||||||
|
// BC,
|
||||||
|
AD,
|
||||||
|
|
||||||
|
usingnamespace extras.TagNameJsonStringifyMixin(@This());
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn isLeapYear(year: u16) bool {
|
||||||
|
var ret = false;
|
||||||
|
if (year % 4 == 0) ret = true;
|
||||||
|
if (year % 100 == 0) ret = false;
|
||||||
|
if (year % 400 == 0) ret = true;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn daysInYear(year: u16) u16 {
|
||||||
|
return if (isLeapYear(year)) 366 else 365;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn daysInMonth(year: u16, month: u16) u16 {
|
||||||
|
const norm = [12]u16{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
||||||
|
const leap = [12]u16{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
||||||
|
const month_days = if (!isLeapYear(year)) norm else leap;
|
||||||
|
return month_days[month];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn printOrdinal(writer: anytype, num: u16) !void {
|
||||||
|
try writer.print("{}", .{num});
|
||||||
|
try writer.writeAll(switch (num) {
|
||||||
|
1 => "st",
|
||||||
|
2 => "nd",
|
||||||
|
3 => "rd",
|
||||||
|
else => "th",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn printLongName(writer: anytype, index: u16, names: []const string) !void {
|
||||||
|
try writer.writeAll(names[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap(val: u16, at: u16) u16 {
|
||||||
|
const tmp = val % at;
|
||||||
|
return if (tmp == 0) at else tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Duration = struct {
|
||||||
|
ms: u64,
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user