toolhq.io

All posts
4 min readby Jameel Haider

Milliseconds vs seconds in Unix timestamps

You paste a timestamp into a converter and get a date in 1970, or one 50,000 years in the future. Almost always the cause is the same: the value was in milliseconds and the tool expected seconds, or the other way around. The two units differ by exactly a factor of 1000, and nothing in the raw number tells you which one you have unless you know where it came from.

Where each unit comes from

Classic Unix time counts whole seconds since midnight UTC on 1 January 1970. This is what C's time(), the date +%s shell command, Python's int(time.time()), and most database and log formats give you.

JavaScript broke with that convention. Date.now() and new Date().getTime() return milliseconds since the same epoch, because the language wanted sub second precision in a single integer. Many web APIs, analytics events, and JSON payloads inherited the millisecond habit.

So the same instant has two common representations:

seconds:      1700000000
milliseconds: 1700000000000

Telling them apart by digit count

You rarely need to guess. For any date in the current era, the digit count is a reliable tell.

UnitExampleDigits (through year 2286)
Seconds170000000010
Milliseconds170000000000013

A ten digit number is seconds. A thirteen digit number is milliseconds. Microseconds (16 digits) and nanoseconds (19 digits) show up in high resolution tracing and some databases, each adding three more digits. If you see a trailing 000, you are probably looking at a coarser unit padded up.

The x1000 bug

The failure mode is mixing the two in one calculation. A common one:

// value is in seconds (from an API)
const d = new Date(value);        // wrong: Date() wants milliseconds
const d = new Date(value * 1000); // correct

Feeding seconds to new Date() lands you in January 1970, because 1.7 billion milliseconds is only about 20 days after the epoch. The reverse mistake, treating milliseconds as seconds, throws you tens of thousands of years into the future. Both are silent; no error is raised, you just get a nonsensical date.

The safe habit is to label the unit at every boundary. Name variables expiresAtSeconds or clickedAtMs rather than a bare timestamp, and convert once at the edge where data enters your code rather than sprinkling * 1000 through the logic.

Checking a value quickly

When you have a number and no idea which unit it is, paste it into the timestamp converter and read the resulting date. If it lands near today, the unit was right; if it lands in 1970 or the distant future, flip between seconds and milliseconds. The conversion runs entirely in your browser, so the timestamps you check, which can leak when something happened, never leave your device.

Count the digits first: ten means seconds, thirteen means milliseconds. Convert at the boundary, name the unit in the variable, and the off by 1000 bug stops happening. If you also need the reverse direction, see converting a Unix timestamp to a date.