Unix timestamps explained: seconds, ms and time zones
· 6 min read
A log line says 1700000000. A JWT says exp: 1735689600. A database column holds 1727654400123. They’re all Unix timestamps, and the last one has three extra digits because it’s in milliseconds. Mixing those up puts a date in January 1970 or tens of thousands of years in the future, and it’s one of the most common date bugs there is.
This article explains what a Unix timestamp is, how to read one quickly, and the traps around units, time zones and JavaScript’s Date. The Unix timestamp converter turns any timestamp into readable dates in UTC and your time zone, and detects the unit automatically.
What the number means
A Unix timestamp is the number of seconds since 1970-01-01 00:00:00 UTC, the Unix epoch, not counting leap seconds.
0 1970-01-01T00:00:00Z
1000000000 2001-09-09T01:46:40Z
1700000000 2023-11-14T22:13:20Z
2147483647 2038-01-19T03:14:07Z
Negative values are dates before 1970. The number has no time zone: 1700000000 is the same instant in Tokyo and in London. Time zones only appear when you turn the instant into a calendar date and a clock time for display.
That’s the main strength of Unix time. Two timestamps can be compared and subtracted directly, and there are no daylight-saving gaps or ambiguous hours inside the number.
Seconds, milliseconds, microseconds, nanoseconds
Different systems count in different units:
| Unit | Digits today | Example | Typical source |
|---|---|---|---|
| Seconds | 10 | 1700000000 |
Unix tools, JWT exp/iat, most APIs |
| Milliseconds | 13 | 1700000000000 |
JavaScript Date.now(), Java, many databases |
| Microseconds | 16 | 1700000000000000 |
Python time.time_ns() // 1000, some log formats |
| Nanoseconds | 19 | 1700000000000000000 |
Go time.UnixNano(), some tracing systems |
The digit count is the fastest way to tell them apart, and it stays valid for a long time: 10 digits in seconds covers 2001 to 2286. The timestamp converter uses the same idea to detect the unit when you paste a value.
The classic bug is passing seconds where milliseconds are expected, or the reverse:
new Date(1700000000); // 1970-01-20T16:13:20.000Z (seconds read as ms)
new Date(1700000000 * 1000); // 2023-11-14T22:13:20.000Z (correct)
If a date comes out as January 1970, you gave milliseconds code a seconds value. If it comes out tens of thousands of years in the future, you did the opposite. JWTs are a common place for this mix-up, because their exp claim is always in seconds while JavaScript works in milliseconds (the JWT debugger shows exp as a date so you can spot it).
Converting on the command line and in code
date +%s # now, in seconds
date -u -d @1700000000 # GNU/Linux: Tue Nov 14 22:13:20 UTC 2023
date -u -r 1700000000 # macOS/BSD
Math.floor(Date.now() / 1000); // now, in seconds
new Date(1700000000 * 1000).toISOString(); // "2023-11-14T22:13:20.000Z"
Date.parse('2023-11-14T22:13:20Z') / 1000; // 1700000000
import time, datetime
int(time.time())
datetime.datetime.fromtimestamp(1700000000, datetime.timezone.utc).isoformat()
# '2023-11-14T22:13:20+00:00'
In Python, always pass a time zone to fromtimestamp. Without one it returns a naive local time, and utcfromtimestamp is deprecated since Python 3.12 because its naive result is so easy to misuse.
The year 2038 problem
Many older systems store Unix time as a signed 32-bit integer. Its maximum, 2,147,483,647, is reached on 2038-01-19 at 03:14:07 UTC. One second later the value wraps to −2,147,483,648, which is December 1901.
Modern 64-bit operating systems, languages and databases use 64-bit time and are fine. The risks are in places that still use 32-bit fields: embedded devices, old file formats, binary protocols, and some database types. MySQL’s TIMESTAMP column, for example, is limited to 2038-01-19 (use DATETIME or a 64-bit integer for dates after that). Anything that computes dates 15 years ahead, such as loans, certificates or subscriptions, meets 2038 today, not in 2038.
Leap seconds
UTC occasionally inserts a leap second to keep in step with the Earth’s rotation. Unix time pretends they don’t exist: every day is exactly 86,400 seconds. During a leap second, the Unix timestamp repeats or stalls for a second, depending on the system. Large providers “smear” the extra second over several hours instead.
For almost all application code this doesn’t matter. It matters if you measure durations across a leap second with wall-clock time. That’s why monotonic clocks (performance.now(), time.monotonic()) exist, and why you should use them for timing. In 2022 the General Conference on Weights and Measures decided to stop adding leap seconds by 2035.
JavaScript Date traps
The timestamp itself is simple. Turning it into or out of a calendar date is where bugs happen, and JavaScript’s Date has several well-known surprises:
Date-only strings are UTC, date-time strings are local
new Date('2026-09-30').toISOString(); // "2026-09-30T00:00:00.000Z" (UTC)
new Date('2026-09-30T10:00').toISOString(); // "2026-09-30T08:00:00.000Z" (local time; run in Berlin)
Same format family, different time zones. Show the first one with toLocaleDateString() in New York and it prints September 29. Always include an explicit offset (Z or +02:00) in strings you store or send.
Invalid dates roll over
new Date(2024, 1, 30); // March 1, 2024 (months are 0-based; February 30 rolls over)
new Date('2024-02-30'); // also March 1 in V8, instead of an error
A form that accepts 2024-02-30 silently stores March 1. Validate the calendar date yourself before converting. The timestamp converter rejects impossible dates with a message (“February 2024 has 29 days”) instead of rolling them over.
Daylight saving time
Local times can be skipped or repeated when clocks change. On the night clocks go forward in Europe, 02:30 doesn’t exist, and JavaScript quietly moves it to 03:30. On the night they go back, 02:30 happens twice. Store instants as UTC or as a timestamp, and convert to local time only for display.
The newer Temporal API fixes most of this with explicit types for plain dates, zoned date-times and instants. Until it’s available everywhere you need it, a library such as date-fns or Luxon, plus UTC everywhere in storage, is the practical choice.
Timestamps inside other formats
Unix time turns up inside other identifiers too:
- JWT claims
exp,iatandnbfare seconds. - UUID v7 starts with a 48-bit timestamp in milliseconds, so you can read a record’s creation time from its id. The UUID inspector extracts it.
- Snowflake-style ids (Twitter/X, Discord) contain a millisecond timestamp relative to a custom epoch, not 1970.
Checklist
- Know the unit. Count the digits: 10 is seconds, 13 is milliseconds.
- Store instants as UTC timestamps or ISO 8601 strings with an offset.
- Convert to local time at the edge, for display only.
- Validate calendar dates before converting; don’t trust rollover.
- Use 64-bit storage for anything that can reach 2038.
When a date looks wrong, paste the raw number into the Unix timestamp converter. It shows the value as UTC, local time, ISO 8601, RFC 2822 and “3 hours ago”, which is usually enough to see which of these rules was broken.