Unix Timestamps Explained
A Unix timestamp is one of the simplest ideas in computing wearing a slightly intimidating number. It is a single integer that says how many seconds have passed since one fixed instant in 1970, and from that one rule everything else follows: why it needs no timezone, why the same value means the same moment on every machine, and why so many "my date is in 1970" bugs come down to a stray factor of 1000. This guide builds the concept from first principles, works one conversion in each direction by hand, and is honest about the one place the whole scheme runs out of road — the year 2038.
The epoch: a single fixed starting point
Every Unix timestamp is measured from the same instant, called the Unix epoch: 1970-01-01 00:00:00 UTC. That is the zero point. A timestamp is nothing more than the count of seconds elapsed since then, so 0 is the epoch itself, 60 is one minute after it, and 86400 (the number of seconds in a day) is 1970-01-02 00:00:00 UTC. There is no calendar arithmetic baked into the number — no months, no leap-year rules, no timezone. It is a plain running tally, which is exactly why computers like it: comparing two moments is just comparing two integers, and finding the gap between them is a subtraction.
The 1970 choice is historical rather than fundamental. Unix was being built at Bell Labs around then, and the designers needed an arbitrary but recent origin that fit comfortably in the integer sizes of the day. Any epoch would have worked; 1970 is simply the one the world standardised on. (Other systems picked other origins — classic Mac OS counted from 1904, and some spreadsheet software counts days from 1900 — which is one reason timestamps are not blindly portable between ecosystems.)
Worked example: epoch → human-readable UTC
Take the timestamp 1700000000 and turn it into a date a human can read. The mechanical way is to divide it down into the units of a calendar, all in UTC:
The day count lands you on the date and the leftover seconds give the time of day. (The hand arithmetic above is deliberately simplified; the exact UTC breakdown of the remainder is 22:13:20, which any library computes for you.) The key point is conceptual: the conversion is pure division and remainder against fixed constants, because a Unix timestamp treats every day as exactly 86,400 seconds. It does not account for leap seconds — the occasional extra second added to UTC to track the Earth's rotation — which is a small, deliberate lie that keeps the arithmetic clean and is almost always the right trade-off.
Worked example: date → epoch (the other direction)
Now go backwards. Suppose you have the instant 14 November 2023, 22:13:20 UTC and want its timestamp. You count seconds from the epoch to that moment: whole days since 1970-01-01 multiplied by 86,400, plus the seconds into the final day.
The two examples are exact inverses, which is the whole point: a timestamp and a UTC calendar time are two encodings of the same instant, and you can always convert losslessly between them. Notice that at no stage did a timezone appear. That is not an accident — it is the next idea.
Why timestamps are timezone-agnostic
This trips up more people than anything else, so it is worth stating flatly: a Unix timestamp has no timezone. It is always a count from the UTC epoch, and the same integer denotes the same physical instant everywhere on Earth. When it is 1700000000, it is 1700000000 in Tokyo, London and São Paulo simultaneously — the number does not change as you cross a border.
The timezone only enters when you display the timestamp to a human. Formatting 1700000000 "in New York" and "in Berlin" produces two different wall-clock strings, but both describe the identical moment — they just add a different UTC offset on the way out. The mental model to keep is a strict two-layer split: storage and comparison happen in UTC integers; timezone is a presentation concern applied at the last moment. Almost every timezone bug in real systems comes from smearing those two layers together — storing a "local" timestamp, or attaching an offset to the number itself. Store the epoch integer, convert to local only when a person needs to read it, and the whole class of bugs disappears.
Seconds vs milliseconds: the ×1000 gotcha
Unix time is defined in seconds, but a great deal of software — JavaScript's Date.now(), Java's System.currentTimeMillis(), most log pipelines — reports milliseconds since the epoch instead. Same epoch, same idea, just multiplied by 1000. Mixing the two is the single most common timestamp bug, and the symptom is unmistakable: a date that lands in January 1970 (a seconds value read as milliseconds) or roughly 50,000 years in the future (a milliseconds value read as seconds).
The quickest field test is to count the digits. For any date in the current era, a seconds timestamp is 10 digits and the same instant in milliseconds is 13:
| Value | Digits | Unit | Interprets as |
|---|---|---|---|
| 1700000000 | 10 | seconds | 14 Nov 2023, 22:13:20 UTC |
| 1700000000000 | 13 | milliseconds | 14 Nov 2023, 22:13:20 UTC |
| 1700000000 | 10 | read as ms | 20 Jan 1970 (wrong) |
| 1700000000000 | 13 | read as s | ~year 55,829 (wrong) |
The rule of thumb — 10 digits is seconds, 13 is milliseconds — holds for every date from September 2001 (when second counts first reached ten digits) until November 2286 (when they reach eleven). To convert, you multiply or divide by exactly 1000: seconds × 1000 = milliseconds. Some systems go finer still — microseconds (16 digits) and nanoseconds (19 digits) — but the same digit-counting instinct catches them.
The Year 2038 problem — and the 64-bit fix
Here is where the clean scheme meets a hard wall. For decades, Unix time was stored in a signed 32-bit integer (the C type time_t on 32-bit systems). A signed 32-bit integer can hold values up to 2,147,483,647 — and that many seconds after the epoch is exactly 03:14:07 UTC on 19 January 2038. The very next second cannot be represented: it overflows past the maximum and wraps around to the most negative value, so the clock jumps from 2038 back to 13 December 1901. This is the Year 2038 problem, and it is the direct structural cousin of the Year 2000 bug — a fixed-width field running out of room.
The fix is unglamorous and effective: use a 64-bit signed integer. That widens the ceiling to about 292 billion years — comfortably longer than the age of the universe — so a 64-bit time_t is, for every practical purpose, overflow-proof. Modern 64-bit operating systems, current language runtimes and mainstream databases already use 64-bit time. The remaining risk lives in the corners: old embedded firmware that will still be running in 2038, some binary file and network formats that hard-coded a 32-bit field, and legacy database columns. The honest takeaway is that the problem is real but bounded — it bites specific legacy code, not the whole world — so before scheduling anything far into the future, it is worth knowing which width the system underneath you actually uses.
ISO 8601 vs epoch: two ways to write an instant
A Unix timestamp is compact and perfect for machines, but unreadable to people — 1700000000 tells a human nothing at a glance. The usual human-and-interchange-friendly alternative is ISO 8601, the calendar-string format like 2023-11-14T22:13:20Z. The two describe the same instant with opposite trade-offs:
| Aspect | Unix epoch | ISO 8601 |
|---|---|---|
| Example | 1700000000 | 2023-11-14T22:13:20Z |
| Human-readable | No | Yes |
| Sorts chronologically as text | Yes (numeric) | Yes (lexicographic) |
| Timezone in the value | Never (implicit UTC) | Explicit (Z or offset) |
| Arithmetic (gap, add time) | Trivial (integer math) | Needs parsing |
In an ISO 8601 string the trailing Z means UTC ("Zulu"); an offset such as +09:00 can appear instead to record the local wall-clock reading and its distance from UTC. That is the crucial difference from epoch time: ISO 8601 can carry an offset in the value, whereas a raw timestamp never does. A common, robust convention is to store the epoch integer internally (cheap to compare and do maths on) and emit ISO 8601 at the edges — in APIs, logs and anything a person or another system reads — so you get machine-friendly storage and human-friendly interchange at once.
Paste an epoch value (seconds or milliseconds, auto-detected) or a date and see the instant in UTC, ISO 8601 and your local timezone — live, entirely in your browser, nothing sent anywhere.
A few landmarks worth remembering
Some epoch values are handy to recognise on sight, because they anchor your sense of scale when you are eyeballing a log:
| Timestamp | Instant (UTC) | Why it matters |
|---|---|---|
| 0 | 1970-01-01 00:00:00 | The epoch itself |
| -1 | 1969-12-31 23:59:59 | Time before 1970 is negative |
| 1000000000 | 2001-09-09 01:46:40 | First 10-digit second count |
| 2147483647 | 2038-01-19 03:14:07 | 32-bit signed ceiling |
That the count runs negative before 1970 is a genuinely useful fact: epoch time is not clamped at zero, so a birthday in 1965 or a historical event is simply a negative integer. Portability is uneven there — some databases and runtimes reject or mishandle negative epoch values — so treat pre-1970 timestamps as a mild cross-system risk, but the definition itself extends cleanly in both directions.
Open the timestamp converter → to run any of these conversions live in both directions, or browse all Polyatic tools.