Unix timestamps: seconds vs. milliseconds

Why 1700000000 is a date and 1700000000000 is also a date, which unit your system uses, and how to avoid the classic off-by-1000 bug.

What a Unix timestamp actually is

A Unix timestamp is a single number: the count of seconds since 1970-01-01 00:00:00 UTC, known as the epoch. Because it is just a number, it has no timezone, no format, and no ambiguity — the same value means the same instant everywhere on Earth. That is why databases, APIs, and log systems prefer timestamps over strings like '03/04/2026', which mean different dates in the US and Europe.

The catch is that 'a Unix timestamp' is not one thing. Different ecosystems multiplied the base unit, and you must know which one you are holding before converting.

The four units you will meet

A quick digit-count heuristic covers almost every case: around 10 digits is seconds, 13 is milliseconds, 16 is microseconds, 19 is nanoseconds. If converting a timestamp gives you a date in the year 56000 or in 1970, you guessed the unit wrong by a factor of one thousand.

  • Seconds — the classic. 10 digits until 2286. Used by Unix itself, most server logs, JWT exp/iat claims, and PostgreSQL's epoch functions.
  • Milliseconds — 13 digits. The JavaScript standard (Date.now()), Java, and most JSON APIs.
  • Microseconds — 16 digits. Python's time.time_ns() aside, some databases (Cassandra, parts of MySQL) use micros.
  • Nanoseconds — 19 digits. Go's time.Time internals and precision logging systems.

The off-by-1000 bug

The most common timestamp bug in production is mixing seconds and milliseconds between systems. A JavaScript frontend sends Date.now() (milliseconds) to a Python backend that divides nothing and stores it as seconds: every record is suddenly dated 55,000 years in the future. Or the reverse: the backend's seconds get rendered by JavaScript as a moment in January 1970.

Neither language warns you, because both values are perfectly valid numbers. The defense is to name units explicitly in your schemas and variable names — created_at_ms beats created_at — and to assert the unit at system boundaries.

Timezone is a display concern

A timestamp has no timezone; timezones only appear when you render one for a human. Store and transmit timestamps (or ISO 8601 strings with an explicit offset), and convert to the user's local zone at the last possible moment. Storing 'local wall-clock time' without a zone is how daylight-saving transitions create duplicate or vanished hours in your data.

One more edge: Unix time ignores leap seconds, so a timestamp during a leap second is simply the same value as the second after it. It almost never matters, but it explains why strict UTC math and Unix time math can disagree by a few dozen seconds over decades.

References

Related tools