Developer Notes

Why Every Developer Eventually Hits the Same Timestamp Bug

It's not a rare mistake. It's practically a rite of passage — and it's almost always the same one.

At some point, nearly everyone who works with timestamps hits this exact bug: a date that shows up as sometime in 1970, or a date thousands of years in the future, or a countdown timer that fires instantly instead of counting down for hours. The cause, almost every time, is the same one-word mixup: seconds versus milliseconds.

Why the mistake is so easy to make

Unix time was originally defined in seconds, and most server-side languages, databases, and command-line tools still work in seconds by default — Python's time.time(), PHP's time(), a plain date +%s in the shell. But JavaScript's Date.now() returns milliseconds, and that convention spread outward from the browser into JSON APIs, into Java's System.currentTimeMillis(), and into any system built to talk to a web frontend. There's no universal rule — only a set of local conventions that happen to disagree with each other by exactly a factor of 1,000.

That factor is the whole problem. A bug that's off by 1,000x doesn't look like garbage data. It looks like a plausible-but-wrong date, which is much harder to notice in a code review or a quick test than an obviously broken value would be.

How it actually shows up in practice

A few patterns come up constantly. A frontend passes a millisecond timestamp to a backend expecting seconds, and every date in the database ends up around 1970 (because a huge second-count, read as milliseconds, is still only a few days after the epoch — the reverse direction lands you in the far future instead). A countdown timer computes a difference in milliseconds but displays it as if it were seconds, so a countdown that should last three hours displays a number in the thousands and finishes instantly. A cache expiry set with the wrong unit expires almost immediately, or almost never.

The fastest way to catch it

The most reliable trick isn't memorizing which language uses which unit — it's checking the number of digits. Current Unix time in seconds has 10 digits. In milliseconds, it's 13. In microseconds, 16. In nanoseconds, 19. If a timestamp doesn't match the digit count you expect, that's your bug, immediately, before you've even converted it. This is also exactly how EpochKit's timestamp converter auto-detects the unit — count the digits first, convert second.

If you're integrating across languages regularly, it's worth keeping a quick reference of which unit each one defaults to — see the Epoch Time in Programming Languages guide for the specific snippets.

The habit that actually prevents it

Name your variables with the unit in them — createdAtMs or expiresAtSec, not just createdAt. It feels redundant right up until the moment it saves you from exactly this bug. It costs nothing and it turns an invisible unit mismatch into something a reviewer can actually catch by reading the code.

Related

Related tools & reading