Skip to content

Timestamp Converter — Zig source

Convert Unix epoch timestamps to human-readable dates and back. See seconds, milliseconds, ISO 8601, UTC, local and relative time at once.

This is the Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.

// timestamp — Unix epoch (seconds & milliseconds) <-> ISO 8601 / UTC strings and relative time ("3 hr ago" / "in 2 day"). Language: Zig (0.13, standard library only). Port of src/tools/TimestampConverter.tsx — same logic as this dir's typescript.ts; local-time rendering (no tz database in std) and fromISO parsing omitted for the 80-line budget (see typescript.ts / python.py).
const std = @import("std");

const DAYS = [_][]const u8{ "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
const MONTHS = [_][]const u8{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

/// Howard Hinnant's civil_from_days: days since 1970-01-01 -> (y, m, d).
fn civilFromDays(z0: i64) struct { y: i64, m: usize, d: usize } {
    const z = z0 + 719468;
    const era = @divFloor(z, 146097); // 400-year Gregorian era (floor handles pre-1970)
    const doe = z - era * 146097; // [0, 146096] day of era
    const yoe = @divTrunc(doe - @divTrunc(doe, 1460) + @divTrunc(doe, 36524) - @divTrunc(doe, 146096), 365);
    const doy = doe - (365 * yoe + @divTrunc(yoe, 4) - @divTrunc(yoe, 100)); // [0, 365]
    const mp = @divTrunc(5 * doy + 2, 153); // [0, 11] month starting March
    const d = doy - @divTrunc(153 * mp + 2, 5) + 1; // [1, 31]
    const m = if (mp < 10) mp + 3 else mp - 9; // [1, 12]
    return .{ .y = yoe + era * 400 + @as(i64, if (m <= 2) 1 else 0), .m = @intCast(m), .d = @intCast(d) };
}

/// Human relative time: bucket |now - t| into sec/min/hr/day/month/yr with
/// the island's 60s / 60m / 24h / 30d / 365d thresholds, round to the
/// nearest whole unit, then sign it ("ago" past / "in" future).
fn relativeTime(buf: []u8, ms: i64, now_ms: i64) []const u8 {
    const diff = ms - now_ms;
    const a: f64 = @as(f64, @floatFromInt(@abs(diff))) / 1000.0;
    // (threshold in sec, divisor in sec, unit) — the island's buckets.
    const BUCKETS = [_]struct { th: f64, div: f64, unit: []const u8 }{
        .{ .th = 60, .div = 1, .unit = "sec" },
        .{ .th = 3600, .div = 60, .unit = "min" },
        .{ .th = 86400, .div = 3600, .unit = "hr" },
        .{ .th = 86400 * 30, .div = 86400, .unit = "day" },
        .{ .th = 86400 * 365, .div = 86400 * 30, .unit = "month" },
    };
    var n: i64 = @intFromFloat(@round(a / (86400.0 * 365.0))); // "yr" fallback
    var unit: []const u8 = "yr";
    for (BUCKETS) |b| {
        if (a < b.th) {
            n = @intFromFloat(@round(a / b.div));
            unit = b.unit;
            break;
        }
    }
    const fmt = if (diff < 0) "{d} {s} ago" else "in {d} {s}";
    return std.fmt.bufPrint(buf, fmt, .{ n, unit }) catch "?";
}

/// Render every form the tool exposes from "now" — the island's row table:
/// epoch (s), epoch (ms), ISO 8601, UTC string, relative time. Weekday:
/// 1970-01-01 was a Thursday; DAYS is Mon-based, hence the +3 shift.
pub fn main() !void {
    var bw = std.io.bufferedWriter(std.io.getStdOut().writer());
    const out = bw.writer();

    const ms = std.time.milliTimestamp();
    const secs = @divTrunc(ms, 1000);
    const days = @divFloor(secs, 86400);
    const sod: u32 = @intCast(@mod(secs, 86400)); // seconds of day
    const c = civilFromDays(days);
    const wd: usize = @intCast(@mod(days + 3, 7));

    var rel: [40]u8 = undefined;
    try out.print("Epoch (s):  {d}\n", .{secs});
    try out.print("Epoch (ms): {d}\n", .{ms});
    try out.print("ISO 8601:  {d:0>4}-{d:0>2}-{d:0>2}T{d:0>2}:{d:0>2}:{d:0>2}Z\n", .{ c.y, c.m, c.d, sod / 3600, (sod / 60) % 60, sod % 60 });
    try out.print("UTC:       {s}, {d:0>2} {s} {d} {d:0>2}:{d:0>2}:{d:0>2} GMT\n", .{ DAYS[wd], c.d, MONTHS[c.m - 1], c.y, sod / 3600, (sod / 60) % 60, sod % 60 });
    try out.print("Relative:  {s}\n", .{relativeTime(&rel, ms, ms)});
    try out.print("In 3 hr:   {s}\n", .{relativeTime(&rel, ms + 3 * 3_600_000, ms)});
    try bw.flush();
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →