Telling seconds from milliseconds in a timestamp
Count the digits. For any date in the current era, a timestamp in seconds has ten digits, milliseconds has thirteen, and microseconds has sixteen. There is no flag in the value itself and no convention that all systems follow, so the magnitude is the only signal — which is why a value that is a thousand times too large produces a date around the year 55000 rather than an error.
Why there are three units at all
Unix time was defined in seconds, and that is what POSIX, most C APIs, JWT claims, and database epoch columns still use. JavaScript's Date.now() returns milliseconds, so anything that passed through a browser or Node tends to be in milliseconds. Logging and tracing systems often want finer resolution and use microseconds or nanoseconds.
None of these are wrong. The problem is that they all get called "a timestamp", and the value carries no indication of which one it is. A number is just a number.
Where it actually breaks
JWT is the common case. RFC 7519 defines exp, iat, and nbf as NumericDate, which is seconds. Issue a token with Date.now() and the expiry is a thousand times too far away — the token never expires, and it will not look broken until someone checks why a revoked session still works.
The reverse produces the more visible failure: a millisecond value divided somewhere and read as seconds lands in 1970. A date of 1970-01-01 in a record is almost never real data; it is a zero, a missing value, or a unit mismatch.
Sorting is the quiet one. Mix seconds and milliseconds in the same column and every millisecond value sorts after every second value regardless of when either happened, because 1700000000000 is larger than 1799999999.
What to do about it
At a boundary you control, put the unit in the name: expires_at_seconds, created_at_ms. This costs nothing and removes the question permanently.
Better still, send an ISO 8601 string — 2026-09-05T12:00:00Z — where the format itself says what the value is and no arithmetic can silently reinterpret it. Use an epoch integer where you need to compare or store compactly, and a string where a human or another system has to read it.
When you are receiving a value you did not choose, check the digit count before doing anything with it, and reject values that fall outside a plausible range rather than converting them. A timestamp from 1970 or from the year 50000 is a bug report, not a date.
Tools for this
- Unix Timestamp ConverterTurn an epoch number into a real date — and tell seconds from milliseconds.
- JWT DecoderSplit a JSON Web Token into header, claims, and signature — with expiry read as a real time.
- JSON ViewerValidate, format, and explore JSON as a tree or a table. Large API responses stay foldable.
Specifications this follows
- POSIX.1-2024 — Seconds Since the Epoch — The original definition, in seconds.
- RFC 7519 §2 — NumericDate — JWT time claims are seconds, which is where the millisecond mistake shows up most.
- MDN — Date.now() — Returns milliseconds — the other half of the confusion.
- RFC 3339 — Date and Time on the Internet — The string format that removes the ambiguity entirely.
Updated 2026-09-05