zepoch is an explicit time library for Zig 0.16. It separates absolute instants,
wall time, fixed-offset views, and monotonic clocks into distinct types. External
capabilities enter through Zig 0.16 std.Io and std.Tz; the library never guesses
a time zone, reads global process state, or hides allocation.
| Type | Meaning | Supported operations |
|---|---|---|
Instant |
POSIX/Unix instant that ignores leap seconds | Timestamp conversion, exact duration arithmetic, ordering |
LocalDateTime |
Zone-free wall time | Gregorian validation, weekday, ordinal day, calendar arithmetic |
OffsetDateTime |
Instant with an explicit fixed UTC offset |
RFC 3339 and fixed-offset calendar views |
MonotonicInstant |
awake or boot monotonic clock reading |
Timeout and elapsed-time measurement |
TzifZone |
IANA transition rules from caller-provided TZif data | Instant conversion and DST gap/fold resolution |
There are no implicit conversions between these types. Instant stores no display
offset, LocalDateTime assumes no system time zone, and MonotonicInstant cannot be
formatted as calendar time. Operations return new values instead of mutating inputs.
Instant.now(io)andMonotonicInstant.now(io, clock)receivestd.Ioexplicitly.TzifZone.parse(allocator, identifier, bytes)receives the allocator, IANA identifier, and TZif bytes explicitly. The caller decides how to obtain them.- Formatting writes to a caller-provided
*std.Io.Writerwithout allocating a string. - Invalid dates, offsets, precision loss, and arithmetic overflow return errors.
- Month arithmetic requires
.reject,.clamp, or.preserve_end_of_month. LocalDateTime.addDaysmoves a calendar date while preserving wall-clock fields; it does not meanDuration.fromHours(24)across DST.- TZif designations are display data only. Lookups require regional IANA identifiers
such as
Asia/Shanghai,America/New_York, orEtc/UTC.
Instant.now(io) reads std.Io.Clock.now(.real, io).nanoseconds and normalizes it
into i64 unix_seconds plus a u32 nanosecond in [0, 1_000_000_000). Floor
division and a non-negative remainder are used for negative values, so -1 ns
becomes -1 s + 999_999_999 ns.
Like Zig's std.Io.Clock.real, Instant uses the Unix epoch and ignores leap
seconds. 2016-12-31T23:59:60Z returns error.InvalidSecond instead of being
normalized. TzifZone.leapSecondRecords() exposes TZif leap records but does not
apply them to POSIX time. TAI, GPS, UT1, and astronomical applications need a
separate time scale and an authoritative leap-second table.
LocalDateTime uses the proleptic Gregorian calendar with ISO 8601 year numbering,
including year zero. It does not model the 1582 Julian-to-Gregorian cutover.
TzifZone delegates RFC 8536 TZif parsing to the Zig standard library and does not
maintain a tzdb. To avoid silently ignoring future rules in the TZif POSIX footer,
results are available only between adjacent transitions:
.unique: one absolute instant;.ambiguous: two instants in an autumn fold;.nonexistent: a local-time gap caused by DST or a date-line change;.outside_coverage: the transition table cannot prove the result.
Instant uses i64 seconds plus u32 nanoseconds. Duration uses signed i128
nanoseconds, and timestamps never use floating point. There is no fromDays;
write Duration.fromHours(24) when an exact 24-hour duration is intended.
parse.rfc3339 accepts a four-digit year, T, seconds, an optional 1-9 digit
fraction, and Z or ±HH:MM. Invalid dates are never normalized. -00:00 returns
error.UnknownLocalOffset. Formatting a historical offset with second precision
returns error.OffsetPrecisionLoss because RFC 3339 cannot represent it.
The library does not provide translated month or weekday names, 12-hour clocks, or locale-specific week rules. Those policies belong in a caller-selected locale layer.
zig fetch --save https://github.com/GuangYiL/zepoch/archive/refs/heads/main.tar.gzconst dependency = build_system.dependency("zepoch", .{
.target = target,
.optimize = optimize,
});
executable.root_module.addImport("zepoch", dependency.module("zepoch"));const std = @import("std");
const zepoch = @import("zepoch");
pub fn main(init: std.process.Init) !void {
const now = try zepoch.Instant.now(init.io);
const utc_plus_eight = zepoch.OffsetDateTime{
.instant = now,
.offset = try zepoch.UtcOffset.fromSeconds(8 * 3_600),
};
var buffer: [64]u8 = undefined;
var writer = std.Io.Writer.fixed(&buffer);
try zepoch.format.writeRfc3339(utc_plus_eight, &writer);
}The caller obtains and caches TZif data for named time zones:
var zone = try zepoch.TzifZone.parse(
allocator,
"America/New_York",
tzif_bytes,
);
defer zone.deinit();
switch (try zone.resolveLocal(wall_time)) {
.unique => |instant| use(instant),
.ambiguous => |pair| chooseExplicitly(pair.earlier, pair.later),
.nonexistent => return error.NonexistentLocalTime,
.outside_coverage => return error.TimeZoneDataOutOfRange,
}zig fmt --check build.zig build.zig.zon src examples
zig build test
zig build
zig build runTests live at the bottom of their source files. Commit messages follow Conventional Commits 1.0.0:
<type>[optional scope]: <English description> / <Chinese description>
Commit bodies are also bilingual and start after one blank line.
Licensed under the MIT License (Expat), the same license used by Zig.
