Unix Timestamps: Seconds vs Milliseconds, and Why Your Date Is Wrong

If your date came out as 1970 or as some year in the distant future, you have a units mismatch. Here is how to tell which you have by counting digits.

Unix Timestamps: Seconds vs Milliseconds, and Why Your Date Is Wrong
Share this article:

Two symptoms, one cause.

Your date shows 1970. You passed a value in seconds to something expecting milliseconds. `1757376000` seconds became 1,757,376 milliseconds — about 20 days after the epoch.

Your date shows the year 57,672. The reverse: milliseconds passed to something expecting seconds, multiplying the elapsed time by a thousand.

Both are units mismatches, and both are trivial to spot once you know what to look for.

Count the digits

A Unix timestamp is the number of units elapsed since 1 January 1970 UTC. The digit count tells you the units immediately:

DigitsUnitsExampleRepresents
10Seconds`1757376000`9 Sep 2026
13Milliseconds`1757376000000`9 Sep 2026
16Microseconds`1757376000000000`9 Sep 2026
19Nanoseconds`1757376000000000000`9 Sep 2026

Ten digits is seconds. Thirteen is milliseconds. That rule holds from 2001 until 2286, which covers anything you will meet in practice.

Paste any of them into the timestamp converter and it will tell you the date; it accepts both seconds and milliseconds.

Which does your language use?

This is the actual source of the bug — different ecosystems chose differently.

Language / systemDefault unit
JavaScript `Date.now()`Milliseconds
Python `time.time()`Seconds (float)
Java `System.currentTimeMillis()`Milliseconds
Go `time.Now().Unix()`Seconds
PHP `time()`Seconds
Ruby `Time.now.to_i`Seconds
MySQL `UNIX_TIMESTAMP()`Seconds
PostgreSQL `EXTRACT(EPOCH ...)`Seconds (float)
`.NET DateTimeOffset.ToUnixTimeSeconds()`Seconds
JWT `exp` and `iat` claimsSeconds

JavaScript is the outlier that causes the most trouble, because it sits between everything else. A Python backend sends seconds, JavaScript reads them as milliseconds, and every date renders as January 1970.

// Python sent 1757376000 (seconds)
new Date(1757376000)          // 1970-01-21 — wrong
new Date(1757376000 * 1000)   // 2026-09-09 — right

Going the other way:

# JavaScript sent 1757376000000 (milliseconds)
datetime.fromtimestamp(1757376000000)        # ValueError or year 57672
datetime.fromtimestamp(1757376000000 / 1000) # correct

JWT expiry is in seconds

Worth calling out separately, because it produces a confusing failure. The `exp` and `iat` claims in a JWT are seconds since the epoch, per RFC 7519.

Set `exp` from JavaScript's `Date.now()` without dividing, and you create a token that expires roughly 55,000 years from now. It will not be rejected — it will simply never expire, which is a security problem rather than a visible bug.

// Wrong: token effectively never expires
{ exp: Date.now() + 3600000 }

// Right: seconds
{ exp: Math.floor(Date.now() / 1000) + 3600 }

If you are debugging a token, the JWT decoder shows the claims so you can check whether `exp` has ten digits or thirteen.

Timestamps are always UTC

A Unix timestamp has no timezone. It is a count of seconds since a fixed instant, and that instant is the same everywhere.

This makes timestamps the right thing to store and log, and it means any timezone you see has been applied at display time, by whatever library rendered it. If two systems disagree about what a timestamp means, they agree on the instant and differ on presentation — which is a much easier bug to fix.

It also means comparing timestamps is safe across regions and unaffected by daylight saving. That is precisely why scheduling in UTC avoids DST problems.

What happens in 2038

A signed 32-bit integer maxes out at 2,147,483,647. As a Unix timestamp in seconds, that is 03:14:07 UTC on 19 January 2038. One second later it overflows to negative and reads as December 1901.

Most modern systems use 64-bit time and are unaffected — that pushes the limit about 292 billion years out. The remaining risk is in embedded systems, older databases, and file formats with fixed 32-bit fields. If you work with any of those, it is worth checking rather than assuming.

The millisecond equivalent is not a concern: 64-bit milliseconds runs out in the year 292,278,994.

Negative timestamps

Dates before 1970 are negative. `-86400` is 31 December 1969. Most languages handle this correctly, but a few libraries and quite a lot of hand-rolled parsing code assume non-negative input and produce nonsense for historical dates. If you handle birthdates or archival records, test with a negative value.

Frequently asked questions

How do I convert milliseconds to seconds?

Divide by 1000 and floor it. Do not round — rounding can push a timestamp into the next second and break equality comparisons.

Does a Unix timestamp include leap seconds?

No. Unix time deliberately ignores them, so a day is always exactly 86,400 seconds. This means it is not a true count of elapsed SI seconds, which matters only for scientific timekeeping.

Why does my database show a different time than my app?

Almost always a display timezone difference, not a data difference. Check the session timezone on the database connection.

What is ISO 8601 and should I use it instead?

`2026-09-09T12:00:00Z` is human-readable and carries an explicit offset. It is better for APIs and logs; timestamps are better for arithmetic and storage. The [timestamp converter](/timestamp-converter) goes between them.

Ten digits today — will that stay true?

Until November 2286, when seconds-based timestamps reach eleven digits. The digit rule is safe for any code you write now.

Related reading

For token debugging, the JWT decoder reads `exp` and `iat` directly. If your team spans regions, the world clock and timezone meeting planner convert one instant across zones.

Convert a timestamp →