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