Unix Time, Explained
One number for any instant — why computers count seconds from 1970, and the quirks every developer should know.
One number to rule all time
Human dates are a mess for computers: they involve timezones, daylight saving, months of different lengths, leap years, and a dozen written formats. So computers mostly avoid all of that internally and store time as a single number — the Unix timestamp: the count of seconds since a fixed reference moment called the epoch, which is midnight UTC on 1 January 1970. Because it is one UTC-based integer, it represents an exact instant with zero ambiguity, and comparing or sorting times becomes simple arithmetic. Convert any timestamp to a readable date in theUnix Timestamp Converter.
Why 1970?
The choice of 1970 is a quirk of history. Unix was being developed at Bell Labs around 1969–1971, and the engineers needed a convenient starting point for the system clock. They picked the start of 1970 as a round, recent date. It was never meant to be profound — it was just a sensible zero. That arbitrary decision then spread with Unix, C, and eventually almost every operating system and programming language, so today an enormous share of the world's software counts time from that one January midnight.
Timezones: there aren't any
This is the feature that makes Unix time so useful and so often misunderstood: a Unix timestamp has no timezone. It is always UTC. The same instant is the same number everywhere on Earth — the moment your API records an event, a server in Tokyo and a server in New York agree on the exact same timestamp. Timezones only enter the picture when you display the time to a person, by converting that universal number into their local wall-clock time. Storing UTC and converting only for display is the golden rule of handling time in software, and it sidesteps almost every timezone bug.
Seconds vs milliseconds
A common trip-up is the unit. The classic Unix timestamp counts seconds, which is what most databases, APIs and the date command use. But JavaScript's Date.now() returns milliseconds, and so do many web APIs. Mixing them up multiplies or divides your time by a thousand — turning a date in 2024 into one in the year 56,000, or vice versa.
| Unit | Digits (today) | Used by |
|---|---|---|
| Seconds | ~10 | Most APIs, databases, Unix |
| Milliseconds | ~13 | JavaScript, many web APIs |
A quick sanity check: a seconds timestamp for the present is around ten digits; a milliseconds one is around thirteen. The converter auto-detects by magnitude, and lets you force the unit when you need to.
ISO 8601: the human-readable standard
When you do need a text date that both humans and machines can read unambiguously, the standard is ISO 8601 — for example 2026-07-28T14:30:00Z. It writes the year first, uses fixed field widths, and the trailing Z means UTC ("Zulu" time). Its great virtue is that ISO strings sort correctly as plain text, and there is no ambiguity between, say, the American and European ways of writing a date. Unix time for storage and comparison, ISO 8601 for readable exchange, and local formatting only for the end user — that trio covers almost every need.
The year 2038 problem
For decades, many systems stored the Unix timestamp in a signed 32-bit integer. That data type can only hold numbers up to about 2.15 billion — and the timestamp will reach that value at 03:14:07 UTC on 19 January 2038. One second later it overflows and wraps around to a large negative number, which those systems would interpret as a date back in 1901. It is the direct descendant of the Y2K bug, and it threatens old embedded devices, legacy databases, and anything still using 32-bit time. The fix is straightforward and already widespread: use a 64-bit timestamp, which extends the range so far into the future (hundreds of billions of years) that it will never realistically matter. Modern languages and operating systems have largely moved over already.
Everyday uses
- Debugging logs: a log line shows
1716239022— convert it to see exactly when the event happened. - Token expiry: a JWT's
expclaim is a Unix timestamp; convert it to check if a token is still valid. - Scheduling: store "run at" times as timestamps and compare against the current one.
- Cache and file times: last-modified headers and file metadata are often epoch-based.
Getting the timestamp in any language
Every environment exposes the current epoch time, but the units differ — which is the source of endless off-by-a-thousand bugs. It is worth knowing which of your tools speak seconds and which speak milliseconds.
| Environment | Call | Unit |
|---|---|---|
| JavaScript | Date.now() | Milliseconds |
| Python | time.time() | Seconds (float) |
| PHP | time() | Seconds |
| Java | Instant.now().getEpochSecond() | Seconds |
| Shell / SQL | date +%s / UNIX_TIMESTAMP() | Seconds |
JavaScript is the notable odd one out, which is precisely why so many bugs appear at the boundary between a browser and a backend. A timestamp that renders as a date in 1970 almost always means milliseconds were passed where seconds were expected; one that renders tens of thousands of years in the future means the reverse. The magnitude check — ten digits versus thirteen — catches this instantly.
Where you will meet timestamps
Epoch time is quietly everywhere once you start looking. Server logs are frequently timestamped in epoch seconds because it sorts and diffs trivially. JWTs use it for the iat, exp and nbf claims, so checking whether a token is expired means converting a raw number. HTTP caching headers, cookie expiry, and file metadata (created, modified, accessed) are all epoch-based under the hood.
Databases handle it differently and the distinction matters. MySQL's TIMESTAMP type stores UTC internally and converts on retrieval, while DATETIME stores exactly what you gave it with no timezone awareness at all — a difference that silently produces wrong results when a server's timezone changes. PostgreSQL draws the same line between timestamptz and timestamp. The safe default is almost always the timezone-aware type, or an explicit integer epoch column.
Leap seconds and the smear
There is one wrinkle in the "seconds since 1970" definition that trips up the unwary. The Earth's rotation is slightly irregular, so occasionally a leap second is added to keep atomic time aligned with solar time. But Unix time is defined to have exactly 86,400 seconds per day, always — which means it cannot represent a leap second properly.
In practice systems handle this by repeating or skipping a second, and historically that has caused real outages when software encountered a timestamp that appeared to go backwards. The modern industry solution is the leap smear: rather than jolting the clock by a full second, major cloud providers spread the adjustment across many hours, slowing each second imperceptibly. The practical lesson for developers is simply to never assume that the difference between two timestamps is a perfectly exact physical duration, and to avoid writing code that breaks if time appears to stand still for a moment.
Handling time well: a short checklist
- Store UTC, display local. Never store a bare local time; convert only at the point of rendering for a specific user.
- Be explicit about units. Name variables
expiresAtMsorcreatedAtSecrather than justtime— the name prevents the bug. - Use 64-bit storage. It removes the 2038 problem entirely and costs nothing today.
- Prefer ISO 8601 for interchange. When humans or other systems read your data, an ISO string with an offset is unambiguous and sorts correctly as text.
- Never trust the client clock. User devices are frequently minutes or hours out; validate expiry against server time.
- Use a real library for date maths. "Add one month" and "days between" are far harder than they look once months, leap years and DST are involved.
Some timestamps worth recognising
A few epoch values show up often enough to be worth knowing on sight. 0 is the epoch itself — 1 January 1970 — and seeing a date of "1 Jan 1970" in an interface almost always means a missing or zero timestamp rather than a genuine date. 1000000000 passed in September 2001; 1234567890 in February 2009, which programmers celebrated as a minor milestone; and 2000000000 arrives in May 2033.
The one to have circled is 2147483647 — the largest value a signed 32-bit integer can hold, corresponding to 19 January 2038. If you ever see that exact number appear as a date in a system, you are almost certainly looking at a 32-bit overflow or a "never expires" sentinel value rather than a real timestamp. Recognising these on sight turns a confusing log line into an obvious diagnosis.
Common timestamp bugs
Time bugs are notorious for surviving code review and appearing in production months later. A few patterns account for the overwhelming majority.
| Symptom | Cause |
|---|---|
| Date shows 1 Jan 1970 | Timestamp is null/zero, or seconds passed as milliseconds |
| Date thousands of years ahead | Milliseconds passed where seconds expected |
| Off by a whole number of hours | Local time stored as if it were UTC |
| Wrong only part of the year | Daylight saving; a fixed offset was hard-coded |
| Token "expired" for some users | Client clock skew; validate against server time |
The "off by a whole number of hours" case is the most insidious, because it usually looks correct to whoever wrote it — they are in the same timezone as the server. It only surfaces when a user in another region reports that their timestamps are wrong, by which point bad data may already be stored. Converting a raw value in a decoder and comparing it against what the interface displays is the fastest way to confirm which layer introduced the error.
Negative timestamps and dates before 1970
A detail that surprises people: Unix timestamps can be negative. Because the epoch is a fixed point rather than a floor, any moment before 1 January 1970 is simply a negative number — the moon landing in July 1969 is roughly −14,182,000. Most modern languages handle this correctly, but plenty of systems, database columns and validation rules assume timestamps are positive, which quietly breaks historical data such as birth dates for anyone born before 1970. If your application handles dates that reach back that far, it is worth testing explicitly with a negative value rather than assuming the stack copes.
Monotonic time: when the clock is the wrong tool
There is one job Unix time is genuinely unsuited to, and it catches out experienced developers: measuring elapsed time. The system clock can jump — forwards or backwards — when it syncs with a time server, when a user changes it, or when a virtual machine resumes. Subtract two wall-clock timestamps and you can get a negative duration, or a duration that silently includes a correction of several seconds.
For measuring how long something took, use a monotonic clock instead: performance.now() in browsers, time.monotonic() in Python, System.nanoTime() in Java. These count steadily from an arbitrary start point and never jump backwards, which is exactly what you want for timing an operation or implementing a timeout. The rule of thumb is clean: use Unix time for when something happened, and a monotonic clock for how long something took. Mixing them up produces bugs that only appear when a clock happens to sync mid-operation — rare, unreproducible, and maddening.
Time is one of the few areas where a small, consistent set of habits eliminates almost every bug you would otherwise spend hours chasing. In short: store instants as UTC epoch values, be explicit about whether you mean seconds or milliseconds, use 64-bit storage, and reach for a monotonic clock whenever you are measuring a duration rather than recording a moment. Unix time is one of computing's most elegant simplifications: collapse all the complexity of calendars and timezones into a single ever-increasing number. Keep it in UTC, mind your seconds versus milliseconds, and reach for theUnix Timestamp Converterwhenever you need to turn that number back into a moment you can read.