Every developer has hit this at least once: a timestamp that renders as 1970-01-01 or, even weirder, as the year 5138. The system isn't broken — you're mixing seconds and milliseconds.
The core distinction
A Unix timestamp counts seconds since the epoch (1970-01-01 UTC). Seconds values are 10 digits (1728000000). Milliseconds values are 13 digits (1728000000000).
The trap: JavaScript, Java, and .NET natively produce milliseconds (Date.now()), while Go, Python, PHP, and nearly every database work in seconds. Pass a 13-digit value where 10 digits are expected and you've just asked the API to interpret a date 1000× too far in the future.
Where this bites in practice
JWT expiry. JWT claims carry exp and iat in seconds. Generate them with Date.now() + 3600000 instead of Math.floor(Date.now()/1000) + 3600 and your token expires... sometime around the year 5138, which most servers treat as already-expired or never-expiring depending on validation logic. Auth bugs that make no sense usually trace back here.
API timestamps. Many JSON APIs return raw Unix seconds. When debugging, logging "created_at": 1756789200 to a console that doesn't auto-format is useless — you have to convert it in your head or open a converter.
Database queries. MySQL's UNIX_TIMESTAMP() returns seconds; if your app layer sends milliseconds, your range queries silently return nothing for "today."
A fast mental check
Count the digits. 10 digits = seconds, 13 = milliseconds. If a value has 12 digits, it's almost certainly milliseconds from a truncated or rounded source — treat it with suspicion.
If a timestamp converts to a date in 1970, it's too small for milliseconds (someone divided by 1000, or it's genuinely seconds read as ms). If it lands in 5138, it's too big for seconds — a milliseconds value read as seconds.
The practical fix
Pick seconds as your wire format and be explicit about it: Math.floor(Date.now() / 1000) in JS, int(time.time()) in Python, time.Now().Unix() in Go. Never send raw Date.now() to a backend that expects seconds — write the division into a named helper so nobody "fixes" it later.
And when you're debugging a log line full of raw integers, a converter that handles auto-detection saves the day: paste the value, get both local and UTC time instantly. I keep CodeToolbox's Unix Timestamp Converter bookmarked for exactly this — it auto-detects seconds vs milliseconds, so the 1970/5138 confusion never even starts. It runs entirely in the browser, which matters when the timestamp you're debugging belongs to a customer's private data.
Got a timestamp horror story? The "5138 bug" and the "1970 bug" are the same mistake from opposite directions — drop yours in the comments.
Top comments (0)