Skip to content

Timestamp Converter — Rust 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 Rust implementation — the same logic the interactive tool runs, in a shareable, citable form.

//! Unix epoch (seconds & milliseconds) <-> human forms: ISO 8601, UTC
//! string, and relative time ("3 hr ago" / "in 2 day").
//!
//! Language: Rust
//! CosmoDev polyglot showcase port of the `timestamp` tool.
//! Ported from src/tools/TimestampConverter.tsx — display source, part of
//! CosmoDev's polyglot tool pages.
//!
//! Every form derives from a single epoch-seconds value. Rust's standard
//! library ships no calendar — so this port uses `std::time` for the
//! clock and Howard Hinnant's civil-from-days algorithm (the well-known
//! days <-> (y, m, d) transform) to render ISO 8601 / the UTC string by
//! hand. relative_time() buckets |now - t| into sec/min/hr/day/month/yr
//! with the same thresholds as the island and signs the phrase.
//!
//! Parity note: with no external crates, the host *local* timezone is not
//! readable from std, so the "Local" row falls back to UTC (matching JS
//! behaviour outside a browser tz); the epoch/ISO/UTC/relative rows are
//! fully faithful.

use std::time::{SystemTime, UNIX_EPOCH};

const WEEKDAYS: [&str; 7] = ["Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed"];
const MONTHS: [&str; 12] = [
    "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
];

/// Calendar instant decoded from a Unix-second timestamp.
struct Civil {
    year: i64,
    month: usize, // 1..=12
    day: u32,
    hour: u32,
    min: u32,
    sec: u32,
    weekday: usize, // index into WEEKDAYS; days=0 is Thursday (1970-01-01)
}

/// Howard Hinnant's civil-from-days algorithm — maps a day count since
/// the Unix epoch to a proleptic Gregorian (year, month, day).
fn civil_from_unix(secs: i64) -> Civil {
    let days = secs.div_euclid(86_400);
    let clock = secs.rem_euclid(86_400);
    let weekday = days.rem_euclid(7) as usize;

    let z = days + 719_468;
    let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
    let doe = z - era * 146_097; // [0, 146_096]
    let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365; // [0, 399]
    let y = yoe + era * 400;
    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365]
    let mp = (5 * doy + 2) / 153; // [0, 11]
    let d = (doy - (153 * mp + 2) / 5 + 1) as u32; // [1, 31]
    let m = if mp < 10 { mp + 3 } else { mp - 9 } as usize; // [1, 12]
    let year = if m <= 2 { y + 1 } else { y };

    Civil {
        year,
        month: m,
        day: d,
        hour: (clock / 3600) as u32,
        min: ((clock % 3600) / 60) as u32,
        sec: (clock % 60) as u32,
        weekday,
    }
}

/// Render every UTC form the tool exposes from epoch seconds.
fn format_timestamp(secs: i64) -> (String, String) {
    let c = civil_from_unix(secs);
    let iso = format!(
        "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.000Z",
        c.year, c.month, c.day, c.hour, c.min, c.sec
    );
    let utc = format!(
        "{}, {:02} {} {:04} {:02}:{:02}:{:02} GMT",
        WEEKDAYS[c.weekday], c.day, MONTHS[c.month - 1], c.year, c.hour, c.min, c.sec
    );
    (iso, utc)
}

/// "3 hr ago" / "in 2 day" / "42 sec ago", mirroring the island.
/// now_ms is a parameter so the result is deterministic and testable.
fn relative_time(ms: i64, now_ms: i64) -> String {
    let diff = ms - now_ms;
    let abs = diff.unsigned_abs() as i64;
    const MIN: i64 = 60_000;
    const HR: i64 = 3_600_000;
    const DAY: i64 = 86_400_000;

    let (n, unit) = if abs < MIN {
        ((abs + 500) / 1000, "sec")
    } else if abs < HR {
        ((abs + MIN / 2) / MIN, "min")
    } else if abs < DAY {
        ((abs + HR / 2) / HR, "hr")
    } else if abs < DAY * 30 {
        ((abs + DAY / 2) / DAY, "day")
    } else if abs < DAY * 365 {
        ((abs + DAY * 15) / (DAY * 30), "month")
    } else {
        ((abs + DAY * 182) / (DAY * 365), "yr")
    };

    let phrase = format!("{n} {unit}");
    if diff < 0 {
        format!("{phrase} ago")
    } else {
        format!("in {phrase}")
    }
}

fn main() {
    let now_ms = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_millis() as i64)
        .unwrap_or(0);
    let secs = now_ms / 1000;

    println!("Epoch (s):  {secs}");
    println!("Epoch (ms): {}", secs * 1000);
    let (iso, utc) = format_timestamp(secs);
    println!("ISO 8601:   {iso}");
    println!("UTC:        {utc}");
    println!("Relative:   {}", relative_time(secs * 1000, now_ms));
}

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 →