Skip to content

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

/**
 * Unix epoch (seconds & milliseconds) <-> human forms: ISO 8601, UTC
 * string, local date string, and relative time ("3 hr ago" / "in 2 day").
 *
 * Language: JavaScript (ES module)
 * CosmoDev polyglot showcase port of the `timestamp` tool.
 * Ported from src/tools/TimestampConverter.tsx - display source, part of
 * CosmoDev's polyglot tool pages.
 *
 * The island's pure logic, extracted without JSX or React state: every
 * form derives from a single epoch-seconds value via the standard Date.
 * relativeTime() buckets |now - t| into sec/min/hr/day/month/yr using the
 * same 60s / 60m / 24h / 30d / 365d thresholds, rounds to the nearest unit,
 * and signs the phrase ("ago" vs "in …"). nowMs is a parameter so the
 * function is deterministic and unit-testable; it defaults to Date.now().
 */

/** Em dash used by the UI for invalid instants. */
const INVALID = '-';

/** True when epoch-seconds resolve to a real instant (not NaN/Infinity). */
export function isValid(secs) {
  return Number.isFinite(new Date(secs * 1000).getTime());
}

/**
 * Human-readable relative time, mirroring the island exactly.
 *
 * @param ms    Target instant in epoch milliseconds.
 * @param nowMs Reference instant ("now"); defaults to Date.now().
 * @returns "42 sec ago", "in 3 hr", "2 day ago", etc.
 */
export function relativeTime(ms, nowMs = Date.now()) {
  const diff = ms - nowMs;
  const abs = Math.abs(diff);
  const min = 60_000;
  const hr = 3_600_000;
  const day = 86_400_000;

  let n, unit;
  if (abs < min) {
    n = Math.round(abs / 1000);
    unit = 'sec';
  } else if (abs < hr) {
    n = Math.round(abs / min);
    unit = 'min';
  } else if (abs < day) {
    n = Math.round(abs / hr);
    unit = 'hr';
  } else if (abs < day * 30) {
    n = Math.round(abs / day);
    unit = 'day';
  } else if (abs < day * 365) {
    n = Math.round(abs / (day * 30));
    unit = 'month';
  } else {
    n = Math.round(abs / (day * 365));
    unit = 'yr';
  }

  const s = `${n} ${unit}`;
  return diff < 0 ? `${s} ago` : `in ${s}`;
}

/**
 * Render every form the tool exposes from a single epoch-seconds value.
 *
 * Matches the island's row table verbatim: epoch (s), epoch (ms), ISO 8601,
 * UTC (Date.toUTCString), local (Date.toLocaleString), and relative time.
 * Invalid instants render an em dash, exactly like the live UI.
 */
export function formatTimestamp(secs, nowMs = Date.now()) {
  const ms = secs * 1000;
  const date = new Date(ms);
  const ok = Number.isFinite(date.getTime());
  return {
    epochSeconds: String(secs),
    epochMillis: String(ms),
    iso8601: ok ? date.toISOString() : INVALID,
    utc: ok ? date.toUTCString() : INVALID,
    local: ok ? date.toLocaleString() : INVALID,
    relative: relativeTime(ms, nowMs),
  };
}

/**
 * Parse an ISO 8601 / datetime-local ("YYYY-MM-DDTHH:mm") string into
 * epoch seconds. Mirrors the island's <input type="datetime-local">
 * handler: feed the string to the Date constructor and floor to seconds.
 * Returns null for unparseable input (the UI silently no-ops).
 */
export function fromISO(iso) {
  const t = new Date(iso).getTime();
  return Number.isFinite(t) ? Math.floor(t / 1000) : null;
}

/**
 * Render the local "YYYY-MM-DDTHH:mm" value used to seed a
 * datetime-local input so the user can edit the instant in their own
 * timezone - the same offset trick the island uses.
 */
export function toLocalInput(secs) {
  const d = new Date(secs * 1000 - new Date().getTimezoneOffset() * 60000);
  return d.toISOString().slice(0, 16);
}

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 →