One API hands you 1792920600. Another hands you 1792920600000. Both mean the same instant, 25 October 2026 at 09:30:00 UTC. The only difference is the unit: seconds in the first, milliseconds in the second. Get the unit wrong and your date lands in January 1970, or tens of thousands of years in the future.
For a quick answer, paste the value into the timestamp converter to see it as a readable date. It reads any value above 10¹² (one trillion) as milliseconds and anything else as seconds, tells you which one it picked, and shows the result in UTC, your local zone and one extra zone you choose. The rest of this guide shows how to make the same call yourself, and where that rule breaks.
Count the digits
For any date between September 2001 and November 2286, the number of digits gives the unit away:
| Digits | Unit | 2026-10-25 09:30:00 UTC |
|---|---|---|
| 10 | seconds | 1792920600 |
| 13 | milliseconds | 1792920600000 |
| 16 | microseconds | 1792920600000000 |
| 19 | nanoseconds | 1792920600000000000 |
The window is exact. One billion seconds (1000000000) is 2001-09-09T01:46:40Z, and the largest 10-digit value, 9999999999, is 2286-11-20T17:46:39Z. Milliseconds cover the same window with 13 digits. Timestamps from the 1990s have only 9 digits in seconds and 12 in milliseconds, and that is where simple length checks start to fail.
Which systems use which unit
There is no universal default, so check the source. These are the ones you will meet most often:
- Seconds: the shell (
date +%s), MySQLUNIX_TIMESTAMP(), PostgreSQLto_timestamp()andextract(epoch from ...), Gotime.Now().Unix(), and theexp,iatandnbfclaims of a JWT. - Seconds as a float: Python
time.time()anddatetime.timestamp(), for example1792920600.123456. PostgreSQL'sextract(epoch ...)also keeps the fractional part. - Milliseconds: JavaScript
Date.now()and thenew Date(number)constructor, JavaSystem.currentTimeMillis(), .NETDateTimeOffset.ToUnixTimeMilliseconds(), GoUnixMilli(). - Nanoseconds: Python
time.time_ns(), GoUnixNano(), GNUdate +%s%N.
When you design a field, put the unit in its name. expires_at_ms or created_unix_s saves the next reader a guess.
The classic bugs
Seconds read as milliseconds: everything is January 1970
JavaScript's Date constructor takes milliseconds. Give it seconds and you get a date about three weeks after the epoch:
new Date(1792920600).toISOString()
// "1970-01-21T18:02:00.600Z" seconds passed as milliseconds
new Date(1792920600 * 1000).toISOString()
// "2026-10-25T09:30:00.000Z" correct
Dates scattered through January 1970 in a UI almost always mean this. A date of exactly 1970-01-01T00:00:00Z is usually a different bug: a null or zero that was converted anyway.
Milliseconds read as seconds: the year 58785
The opposite mistake multiplies the time since 1970 by a thousand:
new Date(1792920600000 * 1000).toISOString()
// "+058785-05-23T20:00:00.000Z"
# Python
datetime.fromtimestamp(1792920600000, tz=timezone.utc)
# fails: the year 58785 is beyond datetime's maximum of 9999
JavaScript happily returns a six-digit year; Python refuses. Either way, the value was in milliseconds and should have been divided by 1,000 first.
The mixed comparison
The quiet version compares two values in different units. A JWT's exp is in seconds and Date.now() is in milliseconds, so this check rejects every token:
// Bug: exp is seconds, Date.now() is milliseconds
if (payload.exp < Date.now()) throw new Error("token expired");
// Fix: bring both sides to the same unit
if (payload.exp * 1000 < Date.now()) throw new Error("token expired");
The guide to JWT exp and iat claims covers the rest of that check, including clock skew.
Microseconds, nanoseconds and JavaScript numbers
16- and 19-digit values come from tracing systems, Go and Python services, and databases with sub-millisecond precision. Two things go wrong with them.
First, most converters only understand seconds and milliseconds, and the devprix converter is no exception. Anything above 10¹² is read as milliseconds, so a 16-digit microsecond value shows up as a date in the year 58785, and a 19-digit nanosecond value is outside the range a JavaScript date can hold, so it is reported as invalid. Divide by 1,000 (microseconds) or 1,000,000 (nanoseconds) before you paste.
Second, JavaScript numbers are 64-bit floats, so integers are exact only up to Number.MAX_SAFE_INTEGER, which is 9007199254740991. Nanosecond timestamps are larger than that, and JSON.parse rounds them without any warning:
JSON.parse("1792920600123456789")
// 1792920600123456800 the last digits are gone
If a browser or Node.js service needs nanoseconds, send them as strings and parse them with BigInt, or split them into whole seconds plus a separate nanosecond field.
Detecting the unit in code
When you don't control the input, a threshold check is the usual answer. This is the same rule the converter uses:
function toDate(ts) {
// 1e12 ms is 2001-09-09; 1e12 s would be the year 33658
return new Date(Math.abs(ts) > 1e12 ? ts : ts * 1000);
}
It works for current data because no realistic seconds value is above a trillion. It fails for milliseconds before September 2001. A millisecond timestamp from June 1990, 644198400000, is below the threshold, gets read as seconds and becomes the year 22383. If your data includes historical dates, don't guess: fix the unit in the API contract, or send an ISO 8601 string, which has no unit to confuse. The comparison of Unix timestamps and ISO 8601 covers when each format makes sense.
The year 2038 problem
A signed 32-bit integer tops out at 2147483647. Read as seconds since the epoch, that is 2038-01-19T03:14:07Z. One second later the counter wraps to its most negative value, which reads as a date in 1901:
2147483647 → 2038-01-19T03:14:07Z max signed 32-bit
-2147483648 → 1901-12-13T20:45:52Z what it wraps to
4294967295 → 2106-02-07T06:28:15Z max unsigned 32-bit
Current operating systems and languages use 64-bit time, so the risk sits in things that were sized years ago: a 32-bit INT column holding epoch seconds, a file format or protocol with a 4-byte time field, firmware on embedded devices. MySQL's TIMESTAMP type has the same ceiling; its documented range ends at 2038-01-19 03:14:07 UTC. Far-future values are the early warning, because a long-lived expiry or a "valid until" date can already be past 2038 today.
Millisecond time in JavaScript doesn't have this limit. A Date holds up to 8.64 × 10¹⁵ milliseconds either side of 1970, which reaches the year 275760.
Next steps
Paste the next ambiguous number you find into the epoch converter; it says whether it read seconds or milliseconds and shows the date in UTC and your local time. If the unit is right but the hours are off, the problem is time zones, and debugging timezone errors in APIs is the next read.