Toolminex
Developer & Security

Unix Time and the Year 2038 Problem, with the Numbers Worked Out

ToolminexUpdated 2026-10-306 min read

Unix time is the number of seconds since 00:00:00 UTC on 1 January 1970. It shows up in log files, databases, JWT expiry claims, API responses and file systems. It's simple, which is why it's everywhere. But a few of its quirks cause real bugs: confusing seconds with milliseconds, assuming it counts leap seconds, and the fact that 32-bit signed timestamps run out in January 2038. This guide works through each one with actual numbers.

How the count works

The moment 1970-01-01T00:00:00Z is timestamp 0. Every second after it adds one. Times before 1970 are negative. Because the count is based on UTC, a Unix timestamp is the same everywhere on Earth at a given instant. The time zone only comes in when you turn it into a human-readable date.

Some landmarks help you sanity-check a value at a glance:

Computed from the definition of Unix time.
TimestampUTC date and timeNote
01970-01-01 00:00:00The epoch
1,000,000,0002001-09-09 01:46:40First 10-digit timestamp
1,700,000,0002023-11-14 22:13:20
1,793,318,4002026-10-30 00:00:00Start of the day this guide was written
2,000,000,0002033-05-18 03:33:20
2,147,483,6472038-01-19 03:14:07Largest 32-bit signed value

A timestamp for any date between 2001 and 2286 has 10 digits. That's a handy check. If you see a 13-digit number like 1793318400000, it's almost certainly milliseconds.

Seconds or milliseconds?

Unix tools, PHP's time(), Python's time.time() (as a float), and most databases use seconds. JavaScript's Date.now() and Java's System.currentTimeMillis() use milliseconds. Mixing them up produces dates that are wildly wrong in a predictable way:

  • Read 1793318400 as milliseconds by mistake and it's about 20.8 days after the epoch, so you get a date in January 1970. If your app shows "21 January 1970", this is why.
  • Read 1793318400000 as seconds by mistake and it lands tens of thousands of years in the future. Many date libraries will simply reject it as out of range.

The Unix Timestamp Converter lets you choose seconds or milliseconds explicitly. If a converted date looks absurd, switch the unit before assuming the data is broken.

Unix time ignores leap seconds

Every Unix day is exactly 86,400 seconds. Real UTC days have occasionally been 86,401 seconds long, because leap seconds were added to keep clocks in step with the Earth's slightly irregular rotation. There have been 27 of them since 1972. Unix time doesn't count them. When a leap second happens, systems either repeat a timestamp, freeze for a second, or "smear" the extra second across several hours, depending on how they're configured.

The consequence is that subtracting two Unix timestamps gives you the elapsed time minus any leap seconds in between. For almost every application that's irrelevant. It matters for precise scientific timing, and it's caused real outages when software didn't expect a repeated second. Leap seconds are on their way out: in 2022 the General Conference on Weights and Measures voted to stop adding them by 2035.

The Year 2038 problem

Many older systems store Unix time in a signed 32-bit integer. The largest value it can hold is 2³¹ − 1 = 2,147,483,647. That's 03:14:07 UTC on Tuesday, 19 January 2038. One second later, the value overflows. In two's-complement arithmetic, it wraps around to the most negative value, −2,147,483,648, which is 20:45:52 UTC on 13 December 1901.

2,147,483,647 → 2038-01-19 03:14:07 UTC
+1 second → −2,147,483,648 → 1901-12-13 20:45:52 UTC

A system hit by this doesn't crash with a clear error. It suddenly believes it's 1901. Certificates appear not yet valid, scheduled jobs think they're 136 years overdue or never due, and "time since" calculations go negative.

From the start of 30 October 2026, there are 354,165,247 seconds left until the overflow. That's about 4,099 days, or roughly 11.2 years. It sounds like a lot, but software that calculates dates in the future hits the limit early. A 15-year loan or a 20-year certificate computed today already reaches past 2038.

Where 32-bit time still lives

  • The Linux kernel gained 64-bit time support for 32-bit platforms in version 5.6 (2020), but devices running older kernels, and user-space software built against 32-bit time, are still out there.
  • Embedded devices such as routers, industrial controllers, cars and medical equipment often run for decades and rarely get firmware updates.
  • MySQL's TIMESTAMP type has historically topped out at 2038-01-19 03:14:07 UTC. DATETIME doesn't have that limit. A schema designed years ago with TIMESTAMP columns needs checking.
  • Some file formats and protocols define a 32-bit seconds field. Changing those means changing the format, not just recompiling.

Why 64-bit fixes it

A signed 64-bit count of seconds reaches about 292 billion years in each direction, roughly 20 times the current age of the universe. Modern 64-bit operating systems, languages and databases use 64-bit time by default. JavaScript is a special case: it stores time as milliseconds in a 64-bit float, but the Date object limits itself to ±8.64 × 10¹⁵ ms, which is about ±100 million days, or until the year 275,760.

An unsigned 32-bit field, sometimes used as a stopgap, buys time but can't represent dates before 1970. It overflows at 2³² − 1 = 4,294,967,295, which is 2106-02-07 06:28:15 UTC.

Reading a timestamp in code

Every language has a one-liner for this, and the most common bug is forgetting which unit the function expects. Here's the same timestamp, 1793318400, read in a few places:

// JavaScript: Date wants milliseconds, so multiply by 1000
new Date(1793318400 * 1000).toISOString()   // "2026-10-30T00:00:00.000Z"

# Python: fromtimestamp wants seconds; pass tz to avoid local-time surprises
datetime.fromtimestamp(1793318400, tz=timezone.utc)

# Shell (GNU date on Linux)
date -u -d @1793318400

# Shell (BSD date on macOS)
date -u -r 1793318400

Note the Python call. Without the tz argument, fromtimestamp returns a naive datetime in the machine's local time zone. Code that works on a laptop set to IST then gives different results on a server set to UTC, five and a half hours apart. Passing a time zone explicitly makes the result the same everywhere.

Practical habits

  1. Store UTC, display local. Keep timestamps in UTC and convert to the user's time zone only at display time.
  2. Name the unit. Call fields created_at_ms or expires_at_s. It costs nothing and prevents the most common bug in this whole guide.
  3. Use 64-bit types. In databases, prefer BIGINT or a native date-time type without a 2038 limit for new columns.
  4. Test with a date after 2038. Set a test clock to 2040 and run your date logic. Bugs show up immediately.
  5. Prefer ISO 8601 for humans. In logs and APIs that people read, 2026-10-30T00:00:00Z is unambiguous and readable. Use Unix time where compactness and easy arithmetic matter.

Converting timestamps

The Unix Timestamp Converter turns a timestamp into a readable date in your local time and in UTC, and turns a date back into a timestamp in seconds or milliseconds. For a quick check of a JWT's exp claim, paste the number from the payload; JWT times are always in seconds. The JWT Decoder does that conversion for you.

Limitations

  • Browser limits apply. The converter uses JavaScript's Date, so it accepts values within ±8.64 × 10¹⁵ milliseconds. Larger values are rejected as out of range.
  • It doesn't model leap seconds. Like Unix time itself, it treats every day as 86,400 seconds.
  • Local time comes from your device. The local display uses your browser's time zone settings. If your system clock or time zone is wrong, the local reading will be too, though the UTC value is still right.
  • It can't tell what a system will do. The converter shows what a timestamp means. Whether a particular device will survive 2038 depends on its software, which you'd need to test directly.

Convert a timestamp

Turn seconds or milliseconds into a readable date, or go the other way, in UTC and your local time.