Created
March 18, 2025 01:40
-
-
Save dk949/fbf90bb4c69c75566e47a472cf1fa174 to your computer and use it in GitHub Desktop.
A very basic utility to manage date and time in UTC timezone
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! A very basic utility to manage date and time in UTC timezone. | |
//! | |
//! Usage: | |
//! | |
//! ```zig | |
//! // The following examples assume that the current date and time is 25th May 1977, at 08:48 PM | |
//! const now = DateTime.now(); | |
//! std.debug.print("{}\n", .{now}); // 1977-05-25 20:48:00 | |
//! std.debug.print("{%d %B %Y at %I}:{%M %p}\n", .{ now, now }); // 25 May 1977 at 08:48 PM | |
//! // NOTE: the format string has to be split in two, because `:` is a special character in fmt | |
//! | |
//! const start: DateTime = .epoch_start; | |
//! std.debug.print("{}\n", .{start}); // 1970-01-01 00:00:00 | |
//! | |
//! const init: DateTime = .{}; | |
//! std.debug.print("{}\n", .{init}); // 0001-01-01 00:00:0001 | |
//! | |
//! // Intended for use with values from std.time.timestamp and friends | |
//! const from_timestamp = DateTime.fromTimestamp(1234567890); | |
//! std.debug.print("{}\n", .{from_timestamp}); // 2009-02-13 23:31:30 | |
//! | |
//! // NOTE: `fromNanoTimestamp`, `fromMicroTimestamp` and `fromMilliTimestamp` | |
//! // are also provided for convenience, but they do not increase precision. | |
//! ``` | |
//! | |
//! ## Format specifiers | |
//! | |
//! These are borrowed from the C `strftime` function. But not all are | |
//! implemented and no timezone information is preserved. | |
//! | |
//! ### Supported specifiers | |
//! | |
//! %b The abbreviated month name in english. | |
//! %B The full month name in english | |
//! %d The day of the month as a decimal number (range 01 to 31) | |
//! %D Equivalent to %m/%d/%y. | |
//! %F Equivalent to %Y-%m-%d (the ISO 8601 date format) | |
//! %H The hour as a decimal number using a 24-hour clock (range 00 to 23) | |
//! %I The hour as a decimal number using a 12-hour clock (range 01 to 12) | |
//! %m The month as a decimal number (range 01 to 12) | |
//! %M The minute as a decimal number (range 00 to 59) | |
//! %n A newline character | |
//! %p Either "AM" or "PM" according to the given time value | |
//! %P Like %p but in lowercase: "am" or "pm" | |
//! %r The time in a.m. or p.m. notation (%I:%M:%S %p). | |
//! %R The time in 24-hour notation (%H:%M) | |
//! %S The second as a decimal number (range 00 to 60) | |
//! %t A tab character | |
//! %T The time in 24-hour notation (%H:%M:%S) | |
//! %y The year as a decimal number without a century (range 00 to 99). | |
//! %Y The year as a decimal number including the century. | |
//! %% A literal '%' character. | |
const std = @import("std"); | |
const DateTime = @This(); | |
pub const epoch_start = DateTime.fromTimestamp(0); | |
year: u16 = 1, | |
month: std.time.epoch.Month = .jan, | |
day: u5 = 1, | |
hour: u5 = 0, | |
minute: u6 = 0, | |
second: u6 = 0, | |
pub fn fromTimestamp(timestamp: i64) DateTime { | |
const epoch_seconds = std.time.epoch.EpochSeconds{ .secs = @intCast(timestamp) }; | |
const epoch_days = epoch_seconds.getEpochDay(); | |
const year_and_day = epoch_days.calculateYearDay(); | |
const month_and_day = year_and_day.calculateMonthDay(); | |
const day_seconds = epoch_seconds.getDaySeconds(); | |
return DateTime{ | |
.year = year_and_day.year, | |
.month = month_and_day.month, | |
.day = month_and_day.day_index + 1, | |
.hour = day_seconds.getHoursIntoDay(), | |
.minute = day_seconds.getMinutesIntoHour(), | |
.second = day_seconds.getSecondsIntoMinute(), | |
}; | |
} | |
pub fn fromNanoTimestamp(timestamp: i128) DateTime { | |
return fromTimestamp(timestamp / std.time.ns_per_s); | |
} | |
pub fn fromMicroTimestamp(timestamp: i64) DateTime { | |
return fromTimestamp(timestamp / std.time.us_per_s); | |
} | |
pub fn fromMilliTimestamp(timestamp: i64) DateTime { | |
return fromTimestamp(timestamp / std.time.ms_per_s); | |
} | |
pub fn now() DateTime { | |
return fromTimestamp(std.time.timestamp()); | |
} | |
pub fn format(datetime: @This(), comptime _fmt: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { | |
const fmt = if (_fmt.len > 0) _fmt else "%F %T"; | |
const new_fmt, const kinds = comptime blk: { | |
var new_fmt: []const u8 = ""; | |
var percent = false; | |
var kinds: []const Kind = &.{}; | |
for (fmt) |ch| { | |
if (percent) { | |
switch (ch) { | |
'b' => { | |
new_fmt = new_fmt ++ "{s}"; | |
kinds = kinds ++ &[_]Kind{.b}; | |
}, | |
'B' => { | |
new_fmt = new_fmt ++ "{s}"; | |
kinds = kinds ++ &[_]Kind{.B}; | |
}, | |
'd' => { | |
new_fmt = new_fmt ++ "{:02}"; | |
kinds = kinds ++ &[_]Kind{.d}; | |
}, | |
'D' => { | |
new_fmt = new_fmt ++ "{:02}/{:02}/{:04}"; | |
kinds = kinds ++ &[_]Kind{.d} ++ &[_]Kind{.m} ++ &[_]Kind{.Y}; | |
}, | |
'F' => { | |
new_fmt = new_fmt ++ "{:04}-{:02}-{:02}"; | |
kinds = kinds ++ &[_]Kind{.Y} ++ &[_]Kind{.m} ++ &[_]Kind{.d}; | |
}, | |
'H' => { | |
new_fmt = new_fmt ++ "{:02}"; | |
kinds = kinds ++ &[_]Kind{.H}; | |
}, | |
'I' => { | |
new_fmt = new_fmt ++ "{:02}"; | |
kinds = kinds ++ &[_]Kind{.I}; | |
}, | |
'm' => { | |
new_fmt = new_fmt ++ "{:02}"; | |
kinds = kinds ++ &[_]Kind{.m}; | |
}, | |
'M' => { | |
new_fmt = new_fmt ++ "{:02}"; | |
kinds = kinds ++ &[_]Kind{.M}; | |
}, | |
'n' => new_fmt = new_fmt ++ "\n", | |
'p' => { | |
new_fmt = new_fmt ++ "{s}"; | |
kinds = kinds ++ &[_]Kind{.p}; | |
}, | |
'P' => { | |
new_fmt = new_fmt ++ "{s}"; | |
kinds = kinds ++ &[_]Kind{.P}; | |
}, | |
'r' => { | |
new_fmt = new_fmt ++ "{:02}:{:02}:{:02} {s}"; | |
kinds = kinds ++ &[_]Kind{.I} ++ &[_]Kind{.M} ++ &[_]Kind{.S} ++ &[_]Kind{.p}; | |
}, | |
'R' => { | |
new_fmt = new_fmt ++ "{:02}:{:02}"; | |
kinds = kinds ++ &[_]Kind{.H} ++ &[_]Kind{.M}; | |
}, | |
'S' => { | |
new_fmt = new_fmt ++ "{:02}"; | |
kinds = kinds ++ &[_]Kind{.S}; | |
}, | |
't' => new_fmt = new_fmt ++ "\t", | |
'T' => { | |
new_fmt = new_fmt ++ "{:02}:{:02}:{:02}"; | |
kinds = kinds ++ &[_]Kind{.H} ++ &[_]Kind{.M} ++ &[_]Kind{.S}; | |
}, | |
'y' => { | |
new_fmt = new_fmt ++ "{:02}"; | |
kinds = kinds ++ &[_]Kind{.y}; | |
}, | |
'Y' => { | |
new_fmt = new_fmt ++ "{:04}"; | |
kinds = kinds ++ &[_]Kind{.Y}; | |
}, | |
'%' => new_fmt = new_fmt ++ "%", | |
else => new_fmt = new_fmt ++ ch, | |
} | |
percent = false; | |
} else if (ch == '%') | |
percent = true | |
else { | |
new_fmt = new_fmt ++ (&ch)[0..1]; | |
} | |
} | |
if (percent) new_fmt = new_fmt ++ "%"; | |
break :blk .{ new_fmt, kinds }; | |
}; | |
const Args = ArgTuple(kinds); | |
var new_args: Args = undefined; | |
inline for (kinds, 0..) |kind, i| { | |
new_args[i] = fieldFromKind(kind, datetime); | |
} | |
return writer.print(new_fmt, new_args); | |
} | |
const Kind = enum { Y, y, m, B, b, d, H, I, M, S, p, P }; | |
fn KindField(comptime kind: Kind) type { | |
return switch (kind) { | |
.b => []const u8, | |
.B => []const u8, | |
.d => u5, | |
.H => u5, | |
.I => u5, | |
.m => u4, | |
.M => u6, | |
.P => []const u8, | |
.p => []const u8, | |
.S => u6, | |
.Y => u16, | |
.y => u16, | |
}; | |
} | |
fn ArgTuple(comptime kinds: []const Kind) type { | |
comptime var types: [kinds.len]type = undefined; | |
for (kinds, 0..) |kind, i| { | |
types[i] = KindField(kind); | |
} | |
return std.meta.Tuple(&types); | |
} | |
fn fieldFromKind(comptime kind: Kind, datetime: DateTime) KindField(kind) { | |
return switch (kind) { | |
.b => monthToAbbref(datetime.month), | |
.B => monthToName(datetime.month), | |
.d => datetime.day, | |
.H => datetime.hour, | |
.I => datetime.hour % 12, | |
.m => datetime.month.numeric(), | |
.M => datetime.minute, | |
.P => if (datetime.hour < 12) "am" else "pm", | |
.p => if (datetime.hour < 12) "AM" else "PM", | |
.S => datetime.second, | |
.Y => datetime.year, | |
.y => datetime.year % 100, | |
}; | |
} | |
fn monthToName(mon: std.time.epoch.Month) []const u8 { | |
return switch (mon) { | |
.jan => "January", | |
.feb => "February", | |
.mar => "March", | |
.apr => "April", | |
.may => "May", | |
.jun => "June", | |
.jul => "July", | |
.aug => "August", | |
.sep => "September", | |
.oct => "October", | |
.nov => "November", | |
.dec => "December", | |
}; | |
} | |
fn monthToAbbref(mon: std.time.epoch.Month) []const u8 { | |
return switch (mon) { | |
.jan => "Jan", | |
.feb => "Feb", | |
.mar => "Mar", | |
.apr => "Apr", | |
.may => "May", | |
.jun => "Jun", | |
.jul => "Jul", | |
.aug => "Aug", | |
.sep => "Sep", | |
.oct => "Oct", | |
.nov => "Nov", | |
.dec => "Dec", | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment