LocalFirstTools.com — data tools that never leave your device
No upload — everything runs on your device

Unix timestamps: seconds vs milliseconds

Last updated

Unix time counts from 1970-01-01T00:00:00 UTC — but some systems count seconds and others milliseconds, and every mismatch is a factor-of-1000 bug with an unmistakable signature: dates in January 1970, or dates fifty millennia away. One moment, two spellings — 2024-08-31T12:00:00Z is 1725105600 in seconds and 1725105600000 in milliseconds. Paste either into the Unix Timestamp Converter — it detects the unit by size and shows the ISO date.

Who uses which unit

  • Seconds: Unix time(), most REST APIs, JWT claims (exp, iat, nbf — see decoding JWTs), cron-adjacent tooling, databases' extract(epoch …), Stripe and most payment APIs.
  • Milliseconds: JavaScript — Date.now() and the Date constructor are ms end to end — plus Java's System.currentTimeMillis(), Kafka, MongoDB dates and most logging pipelines.

The classic crash site is the boundary between a backend speaking seconds and a JavaScript frontend speaking milliseconds.

Read the unit off the digit count

digits  unit          example              example decoded
10      seconds       1725105600           2024-08-31T12:00:00Z
13      milliseconds  1725105600000        2024-08-31T12:00:00Z
16      microseconds  1725105600000000     (databases, tracing)
19      nanoseconds   1725105600000000000  (Go, Kafka log times)

The 10/13 split is stable for the entire era you will ever debug: seconds crossed from 9 to 10 digits in 2001 and stay 10-digit until 2286. Micro- and nanoseconds show up further down the stack — PostgreSQL stores timestamps in microseconds, Go's time.Time and Kafka speak nanoseconds — and produce the same class of bug at a factor of a million or a billion, with the same digit-count cure.

The two bug signatures

new Date(1725105600)      // 1970-01-20T23:11:45.600Z  ← seconds fed as ms
new Date(1725105600 * 1000) // 2024-08-31T12:00:00.000Z  ← correct

// And the mirror image, ms treated as seconds:
// 1725105600000 s  ≈ year 56637

January 1970 means multiply by 1000; a five-digit year means divide. Memorize the pair and you will diagnose these bugs from the symptom alone, before reading any code.

Converting safely

In JavaScript: Math.floor(Date.now() / 1000) for current Unix seconds, and new Date(seconds * 1000) to go back. In Python, time.time() returns float seconds; JSON-encode integers, not floats, to avoid surprising consumers. At API boundaries, document the unit explicitly — a field named created_at_ms has prevented more incidents than any validator. For one-off checks — a JWT exp, a database row, a log line — the Unix → ISO preset and its ISO → Unix counterpart run in your browser.

A real debugging walkthrough

An API starts rejecting requests with 401s. You decode the JWT and find "exp": 1516242622. Ten digits → seconds. Convert: 1516242622 is 2018-01-18T02:30:22Z — long past, token expired, the 401 is correct and the bug is in token refresh, not the API. Total diagnosis time: under a minute, and the only skill involved was recognizing the unit and converting. The same two moves solve “why is this row's created_at in 1970” (something divided by 1000 twice) and “why does the chart show one point in the year 56637” (something multiplied twice).

Negative numbers and the year 2038

Unix time is signed, and both edges matter occasionally. Negative values are simply dates before 1970: -86400 is 1969-12-31T00:00:00Z, and birthdates stored as Unix timestamps go negative for anyone born before the epoch — code that treats negative timestamps as invalid quietly rejects everyone over a certain age. In the other direction, systems that still hold seconds in a signed 32-bit integer overflow at 2147483647 — 2038-01-19T03:14:07 UTC — wrapping to December 1901. Modern platforms moved to 64 bits long ago, but the bug survives in embedded devices, old file formats and databases with 32-bit integer columns; if you are choosing a column type today, that is the argument for bigint settled in one sentence.

Two footnotes worth knowing

Unix time is timezone-less — the same number everywhere on Earth; only its rendering as a wall-clock date involves a timezone, which is why the converter outputs ISO 8601 in UTC. And Unix time ignores leap seconds by definition (every day counts exactly 86,400), which is a feature for arithmetic and a rounding error you will never notice in application code. For recurring schedules rather than single moments, that is the realm of cron expressions.

Do it now — locally

Convert Unix time to ISO dates and back, locally. Nothing is uploaded.

Open the Timestamp

Frequently asked questions

How do I tell if a timestamp is seconds or milliseconds?

Count digits: dates near the present are 10 digits in seconds (1725105600) and 13 in milliseconds (1725105600000). That heuristic holds for every date between 2001 and 2286.

Why did my date show up as January 1970?

A seconds value was fed to something expecting milliseconds — new Date(1725105600) in JavaScript is 1725105600 ms after the epoch, about 20 days into January 1970. Multiply by 1000.

Why did my date show up thousands of years in the future?

The mirror-image bug: a milliseconds value was interpreted as seconds. 1725105600000 seconds is the year 56637. Divide by 1000.

What about the year 2038 problem?

Signed 32-bit seconds counters overflow on 2038-01-19 at 03:14:07 UTC. Modern systems store Unix time in 64 bits, where the limit is astronomically far away; the issue survives mainly in old embedded systems and legacy file formats.

Are there microsecond and nanosecond timestamps too?

Yes — 16 digits is microseconds (common in databases and tracing), 19 digits is nanoseconds (Go's time package, Kafka log timestamps). The digit-count trick extends naturally.

Can a Unix timestamp be negative?

Yes — it means a moment before 1970-01-01T00:00:00 UTC. -86400 is the last day of 1969. Validation that rejects negative timestamps rejects real historical dates, like the birthdates of anyone born before 1970.