Unix Timestamp & Epoch Time Explained

Every Unix timestamp is UTC, always — that's not a convention, it's the entire reason the format is useful. Here's why storing local "wall-clock" time instead causes bugs that only appear twice a year, what leap seconds do to the count, and how to get "now" as epoch across five languages.

reference guide UTC cross-language

// why "UTC by definition" is the whole point

A Unix timestamp has no timezone attached because it doesn't need one — it's a count of seconds from a single fixed instant, so 1735689600 means the exact same moment everywhere on Earth. The timezone only re-enters the picture when you convert it to a human-readable string for display. This is precisely why storing timestamps as epoch (or any UTC value) and converting to local time only at the display layer is the correct default — the alternative, storing "local wall-clock time," runs straight into a problem epoch time was designed to avoid entirely.

Local wall-clock time is ambiguous twice a year. During a "spring forward" DST transition, a clock jumps from 1:59 AM straight to 3:00 AM — the local time 2:30 AM never happens that day. During "fall back," 1:00–1:59 AM occurs twice — a stored local timestamp of 1:30 AM doesn't say which occurrence it means. Neither problem can happen to a Unix timestamp, because it isn't wall-clock time — it's a single, unambiguous instant. A scheduled job or event stored as local time can silently fire twice, not at all, or at the wrong hour on transition days; the same job stored as epoch/UTC never has this failure mode.
spring forwardlocal1:593:002:30 never happensepochfall backlocal1:301:30 againepoch
Local wall-clock time during a DST transition. In spring, the clock jumps from 1:59 straight to 3:00 — 2:30 never happens that day. In autumn, 1:00–1:59 occurs twice, so a stored local time of 1:30 doesn't say which occurrence it means. A Unix/epoch timestamp has neither problem: it's a single continuous count, so every real moment has exactly one value.

// getting "now" as epoch, by language

current timestamp
JavaScriptDate.now() — milliseconds
.NETDateTimeOffset.UtcNow.ToUnixTimeSeconds() / ToUnixTimeMilliseconds()
Pythontime.time() — float seconds, sub-second precision included
PostgreSQLEXTRACT(EPOCH FROM NOW()) — seconds, with a fractional part
MySQLUNIX_TIMESTAMP() — seconds
Linux shelldate +%s — seconds
Three of these return seconds and one (JavaScript) returns milliseconds by default — mixing a JS-generated timestamp with a backend expecting seconds is the single most common cause of a "date 47 years in the past" or "date in the year 56,000" bug. Always check the unit at each boundary a timestamp crosses, not just once.

// leap seconds: ignored by design, not by accident

Real UTC occasionally inserts a leap second to stay aligned with Earth's slightly irregular rotation. POSIX time explicitly does not count them — the standard defines every day as exactly 86,400 seconds, full stop. When a leap second is inserted in reality, Unix time either repeats a second or is adjusted by the OS ("smeared" across the surrounding hours) depending on the system — either way, the raw count quietly diverges from true elapsed seconds by design, not by bug. As of 2025, 27 leap seconds have accumulated since 1972; international standards bodies (BIPM) have agreed to stop inserting new ones by 2035, with a final vote expected in 2026, which will freeze this drift rather than reverse it.

In practice this almost never matters for application code — it's a fact worth knowing exists, not something to write handling logic for. It explains why "elapsed real-world seconds" and "difference between two Unix timestamps" aren't perfectly identical concepts at the margins, which occasionally matters for scientific/astronomical timing systems but essentially never for web applications.

// epoch vs. ISO 8601 — different tools for different jobs

trade-offs
Epoch (number)compact, sorts correctly as a number, not human-readable, unit ambiguity (s vs ms)
ISO 8601 (string)human-readable, sorts correctly as a plain string too, timezone-explicit, more bytes

ISO 8601's field order (year → month → day → hour...) means lexicographic string sorting matches chronological order — a rare and genuinely useful property, which is why log files and filenames often use it. Epoch numbers sort correctly too, but only when compared as actual numbers; naively comparing them as zero-padded strings of different lengths breaks the same way any numeric string comparison does. Pick epoch for compact storage and arithmetic (durations, comparisons), ISO 8601 for anything a human will read directly — logs, API responses meant for debugging, config files.

// try it yourself

Convert a Unix timestamp (seconds or milliseconds, auto-detected) to a readable date in both UTC and your local timezone, or go the other way, with the Unix Timestamp / Epoch Converter — including a live clock showing the current epoch value as it ticks.