The Number Behind the Problem
2147483647 is 2^31 − 1, the maximum value of a signed 32-bit integer. In binary:
01111111 11111111 11111111 11111111
The leading 0 is the sign bit. When Unix timestamps were designed in the late 1960s, storing time as a 32-bit integer was a pragmatic choice that balanced precision, storage cost, and the available range. A signed 32-bit integer gives you roughly 68 years of positive timestamps starting from January 1, 1970. That was enough headroom for the foreseeable future in 1969.
That future is now January 19, 2038 at 03:14:07 UTC.
What Happens at the Overflow
One second after 2147483647, a signed 32-bit counter rolls over:
01111111 11111111 11111111 11111111 (2147483647)
+1
10000000 00000000 00000000 00000000 (-2147483648 in two's complement)
The bit pattern 0x80000000, interpreted as a signed integer, is -2147483648. As a Unix timestamp, that corresponds to December 13, 1901 at 20:45:52 UTC. Any system that stores, compares, or displays timestamps using a signed 32-bit integer will experience one of:
- A crash if the value is used in a context that rejects negative timestamps
- Silent corruption if the negative value is stored and later retrieved
- Incorrect date display or logic (e.g., expiration checks that pass when they should fail, or vice versa)
Comparison With Y2K
Y2K (Year 2000 Problem) was caused by two digit year representations (99 → 00). It was widespread because almost every piece of software written before the late 1990s used this format, and the failure mode was silent data corruption (the year 2000 might be interpreted as 1900).
Y2K38 is structurally similar but more contained:
- Y2K affected software regardless of architecture. Y2K38 only affects systems using 32-bit time_t.
- Y2K was fixed by a massive global effort in 1998 to 1999. Y2K38 mitigation has been ongoing since the 1990s and is largely complete for mainstream software.
- Y2K’s deadline was universal. Y2K38 affects different systems at different times depending on whether they use signed or unsigned 32-bit integers, and what epoch they use.
One difference worth noting: some embedded systems use unsigned 32-bit integers for timestamps, which extends the range to 2^32 − 1 = 4294967295, corresponding to February 7, 2106. That buys significantly more time, but those systems will eventually face the same problem.
Systems Still at Risk
Embedded and IoT devices
Many microcontrollers (ARM Cortex-M, MIPS, older AVR derivatives) run on 32-bit architectures with fixed firmware. Industrial PLCs, medical devices, telecommunications equipment, and smart meters may have no viable firmware update path.
Legacy databases
MySQL’s TIMESTAMP data type is stored internally as a 32-bit Unix timestamp and has a maximum value of 2038-01-19 03:14:07. The DATETIME type does not have this limitation. Any application using TIMESTAMP columns needs to migrate to DATETIME or BIGINT before 2038.
-- MySQL TIMESTAMP max value
SELECT FROM_UNIXTIME(2147483647);
-- Returns: 2038-01-19 03:14:07
-- DATETIME has no such limit (supports up to 9999)
ALTER TABLE events MODIFY created_at DATETIME(3);
32-bit Linux installations
Older 32-bit Linux systems using glibc before 2.32 use a 32-bit time_t. Distributions like Debian have patched this, but unpatched systems or custom builds may still be affected.
Compiled binaries
Executables compiled for 32-bit targets and never recompiled carry the 32-bit time_t assumption even if they run on 64-bit hardware. This includes some PHP extensions, older Python C extensions, and legacy C applications.
The Fix: 64-bit time_t
A 64-bit signed integer can represent timestamps up to approximately ±9.2 × 10^18 seconds from the epoch, roughly 292 billion years in either direction. In practice, the fix is to change time_t from int32_t to int64_t:
// Old (32-bit, overflows 2038)
typedef int32_t time_t;
// New (64-bit, safe for 292 billion years)
typedef int64_t time_t;
Most modern languages and runtimes already use 64-bit timestamps:
- Python:
time.time()returns a float;datetimeuses 64-bit internally. No Y2K38 issue. - JavaScript:
Date.now()returns milliseconds as a float64, good until the year 275760. - Java:
System.currentTimeMillis()is along(64-bit).java.time.Instantis effectively unbounded. - Go:
time.Timeuses a 64-bit int internally. No issue. - Rust:
std::time::SystemTimeuses 64-bit. No issue. - C/C++: Depends on the platform. On 64-bit Linux and macOS,
time_tis 64-bit. On 32-bit targets, it depends on the libc version.
Practical Implications for Developers
Check your database schema
If you have TIMESTAMP columns in MySQL, audit whether the values need to represent dates past 2038. Migrate to DATETIME or store as BIGINT (Unix milliseconds) if so.
Audit your integer types
If you store timestamps as integers in code, make sure they are int64 / long / bigint, not int32 / int.
Test with future timestamps
Add a test that creates a timestamp for January 20, 2038 and verifies your system handles it correctly. This is cheap to do now and catches the problem before it matters.
import datetime
# This should work on any modern system
future = datetime.datetime(2038, 1, 20, tzinfo=datetime.timezone.utc)
print(future.timestamp()) # Should print ~2147569200, not raise an error
Document your embedded systems
If your organization runs embedded devices with fixed 32-bit firmware, document which ones are affected and their expected end of life. You have until 2038 to plan replacements, but planning late means choosing between a rushed migration and a production outage.