Why Your Unix Timestamp Is 1000 Times Off
Ten digits is seconds, thirteen is milliseconds. A date in 1970 means you mixed them one way; a date in the year 56000 means you mixed them the other.
Your timestamp decodes to 1970, or to the year 56000. Both are the same bug: something counted seconds and something else counted milliseconds.
The two conventions
A Unix timestamp is the number of time units since 1 January 1970, 00:00:00 UTC. The original definition uses seconds. JavaScript uses milliseconds. Both are called “Unix timestamps” constantly.
| Unit | A moment in late 2026 looks like | Digits | Used by |
|---|---|---|---|
| Seconds | 1788000000 |
10 | Unix, PHP time(), Python, Go, JWT claims, most APIs |
| Milliseconds | 1788000000000 |
13 | Date.now(), Java, Kotlin, most JSON from browsers |
Count the digits. Ten digits is seconds. Thirteen is milliseconds. That single check catches almost every instance of this, and it stays reliable until the year 2286.
What each mistake looks like
The symptom tells you which direction you went.
- You get 1970, or a date in January of that year. You passed milliseconds to something expecting seconds. Dividing by 1000 was skipped, so a real date collapsed to a moment a few days after the epoch.
- You get a date tens of thousands of years away. You passed seconds to something expecting milliseconds. The number got multiplied by 1000 in effect, and the date ran off the end of the calendar.
- Everything is 1000× too short. A duration calculated in one unit and formatted in the other. A three-hour job reports as eleven seconds.
Paste the number into our Epoch & Unix Timestamp Converter and it tells you which unit it is before converting, so you find out you had the wrong one rather than getting a confidently wrong date.
Where it bites hardest: token expiry
The exp, iat and nbf claims in a JWT are all in seconds. That’s fixed by the spec. Meanwhile the natural way to produce “now” in JavaScript is Date.now(), which is milliseconds.
Write exp: Date.now() + 3600000 and you’ve created a token that expires in the year 58,000. It will never be rejected. That’s not a small bug — it’s an authentication token with no working expiry, and nothing about it looks wrong until someone reads the number.
The correct form is Math.floor(Date.now() / 1000) + 3600. If you’re debugging a token that behaves oddly, the JWT Decoder renders the time claims as real dates, which makes a year-58000 expiry immediately obvious. There’s more on what else is in there in what’s actually inside a JWT.
Converting, in the languages you’ll meet
| Language | Seconds now | Milliseconds now |
|---|---|---|
| JavaScript | Math.floor(Date.now() / 1000) |
Date.now() |
| PHP | time() |
(int) (microtime(true) * 1000) |
| Python | int(time.time()) |
int(time.time() * 1000) |
| SQL (MySQL) | UNIX_TIMESTAMP() |
UNIX_TIMESTAMP() * 1000 |
Use Math.floor, not Math.round. Rounding can put your timestamp up to half a second in the future, which is enough for a strict nbf check to reject a token you just issued.
Two things that are not the same bug
Timezones. A Unix timestamp has no timezone. It’s an absolute count from a fixed instant in UTC. If your date is out by a whole number of hours, that’s a formatting problem at the display end, not a units problem. Different question — see the Time Zone Converter.
2038. A signed 32-bit integer holding seconds overflows on 19 January 2038. That’s a storage width problem, not a units problem, and it’s a real deadline for anything still using a 32-bit time type. Milliseconds in a 64-bit integer are fine for longer than the question is interesting.
Stop it happening again
- Put the unit in the name.
expiresAtSecandcreatedAtMscost nothing and end the ambiguity permanently. - Convert once, at the edge of your system, and use one unit internally.
- Sanity-check on read: a timestamp under 1010 is seconds, above it is milliseconds. Three lines, and it fails loudly instead of storing a date in the year 56000.
The short version
Ten digits is seconds, thirteen is milliseconds. 1970 means you passed milliseconds where seconds were wanted; a far-future date means the reverse. JWT time claims are always seconds, and Date.now() is always milliseconds.
Tools in this guide
Epoch & Unix Timestamp Converter
Unix timestamps to dates and back, in any unit.
JWT Decoder
Decode a JWT and see exactly when it expires.
Time Zone Converter
Convert between zones and UTC, with a live UTC clock.
All of them run in your browser. Nothing uploaded.
Last updated: September 4, 2026

