Developer Tools

2038 Problem Explained: What Breaks and How to Test It

The 2038 problem in detail: the exact overflow moment, why signed 32-bit time wraps to 1901, which systems are genuinely still at risk in 2026, and commands to test your own stack.

By Inventive HQ Team

A signed 32-bit Unix timestamp runs out at 03:14:07 UTC on 19 January 2038. One second later the counter overflows from 2,147,483,647 to -2,147,483,648, and any system still using that representation reads the date as 20:45:52 UTC on 13 December 1901. That is the 2038 problem — also written Y2K38, the 2038 bug, or the Unix Millennium Bug.

The important part is not the date. It is that 64-bit hardware does not fix this. The overflow lives wherever a timestamp is stored or transmitted as 32 bits: a MySQL TIMESTAMP column, a 4-byte field in a binary file format, an INT column in a schema nobody has revisited, firmware on a controller with a 25-year service life. A fully modern 64-bit server writing to any of those still fails in 2038.

As of August 2026 there are 4,178 days — about 11 years and 5 months — until the overflow. Systems being specified today will still be running.

This article covers the arithmetic, what actually breaks (mostly not crashes), what is genuinely fixed versus genuinely still exposed in 2026, and — the part almost nobody writes down — the commands to test your own stack. If you just want to convert or check a specific value, our Unix timestamp converter will decode 2147483647 and any date either side of the boundary.

Unix Timestamp ConverterRuns in your browser — nothing is uploaded.

The Arithmetic, Exactly

A signed 32-bit integer holds values from -2,147,483,648 to 2,147,483,647. Interpreted as seconds since the Unix epoch, those two bounds are:

ValueDecodes toMeaning
21474836472038-01-19 03:14:07 UTCThe last representable second
-21474836481901-12-13 20:45:52 UTCWhere it lands one second later
42949672952106-02-07 06:28:15 UTCUnsigned 32-bit maximum (the 2106 problem)
01970-01-01 00:00:00 UTCThe epoch itself

The wrap looks like this at the bit level. 2147483647 is 0111 1111 1111 1111 1111 1111 1111 1111. Adding one carries into the sign bit, giving 1000 0000 0000 0000 0000 0000 0000 0000, which in two's complement is the most negative value available, not the largest. The clock does not stop at the ceiling; it jumps to the floor.

Why 1901 specifically? Because -2,147,483,648 seconds is 68 years, 18 days, 3 hours, 14 minutes and 8 seconds before 1 January 1970 — landing in December 1901. The 1901 date is not a special value anyone chose. It is simply what the negative half of the range points at.

The reason the range is only ~136 years wide, rather than the 272 you might expect from 2^32 seconds, is the sign bit: half the range is spent on dates before 1970. That choice was deliberate and still useful — it is what lets Unix time express historical dates at all, as covered in our piece on negative Unix timestamps.

What Actually Breaks

The mental image of every machine crashing at once is wrong, and it is why the problem gets underestimated. Overflow does not usually raise an error. It produces a number that is valid, plausible to the code handling it, and catastrophically wrong. The failures are quiet:

  • Durations go negative. end - start across the boundary yields roughly -4.29 billion seconds. Code that assumes elapsed time is positive will divide by it, sleep on it, or use it as an array index.
  • Everything looks expired. Certificates, licences, sessions, tokens, and cache entries compare their expiry against a clock reading 1901. Depending on which side of the comparison overflowed, everything is either permanently expired or permanently valid — and the second is worse.
  • Schedulers misfire. A job due "in 30 days" computed past the boundary is due in 1901, so it fires immediately, every time it is evaluated. Or the next-run time is unreachable and it never fires again.
  • Retention deletes your data. Anything computing "older than N days" against a 1901 timestamp concludes every record is 136 years old. Log rotation, backup pruning, and GDPR retention jobs are all shaped exactly like this.
  • Sorting inverts. Records written after the boundary sort before records from the 1970s, so "most recent" queries return the oldest rows.
  • Rejections, not crashes, at the storage layer. A database with a 2038-capped column refuses the write, and whether that surfaces as an error or a silently truncated value depends on the driver and mode.

Note that several of these bite before 2038, not on the day. Any system that stores or computes a future date past the boundary hits it the moment it does the arithmetic. A 30-year mortgage schedule ran into 2038 in 2008. A 20-year equipment maintenance plan hit it in 2018. A 10-year certificate hits it now. This is why 2038 exposure surfaces gradually, in far-future date handling, rather than all at once.

What Is Already Fixed (and on What Basis)

"Modern systems are fine" is close to true at the platform layer and worth stating precisely, because the precision is what tells you where to look next. Each of these is a documented vendor or project statement, not an assumption:

LayerStatusBasis
MSVC / Windows CRTtime_t is __time64_t by default — a 64-bit signed integer supporting dates through 23:59:59, 31 December 3000 UTCMicrosoft's time documentation, which also warns that the _USE_32BIT_TIME_T opt-out "may fail after January 18, 2038" and is not allowed on 64-bit platforms
glibc on 32-bit Linux64-bit time_t is available but opt-in: define _TIME_BITS=64 (with _FILE_OFFSET_BITS=64)The _TIME_BITS macro is documented as available "as of glibc 2.34"
64-bit Linux/macOS buildstime_t is 64-bit because the platform's long isVerify per-build with the sizeof check below rather than assuming
ext4Timestamps extend to about May 2446 when inodes are larger than 128 bytes; 128-byte inodes remain capped at 2038Kernel ext4 inode documentation — the extra bits live in the space a 256-byte inode has and a 128-byte inode does not
XFSWith the bigtime feature: December 1901 to July 2486. Without it: December 1901 to January 2038mkfs.xfs documentation; bigtime is a filesystem-creation feature, so older filesystems do not have it

Read the two filesystem rows carefully, because they are the pattern for this whole problem: the fix exists, it is in your kernel, and it does not apply to media that was formatted before the fix landed. An ext4 filesystem created years ago with 128-byte inodes is still a 2038 filesystem on a fully patched 2026 kernel.

The same caveat applies to glibc. Shipping glibc 2.34 or later does not make a 32-bit binary safe; the binary had to be compiled with _TIME_BITS=64. Distributions have been working through this, but a binary built before the switch keeps its 32-bit time_t until someone rebuilds it.

What Is Genuinely Still at Risk in 2026

MySQL TIMESTAMP columns. This is the most common exposure in ordinary web applications, and it is not historical: the MySQL 8.4 manual still documents the TIMESTAMP range as '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' UTC. DATETIME is unaffected, with a documented range to '9999-12-31 23:59:59'. If you store contract end dates, subscription expiries, or retention deadlines in TIMESTAMP, you have a 2038 bug in a current database on current hardware.

Timestamps stored as INT. A column typed INT rather than BIGINT is a 32-bit field regardless of the database engine, the OS, or the CPU. This is the single easiest thing to audit and the single easiest thing to miss.

Long-lived 32-bit embedded devices. Industrial and building automation controllers, metering and telemetry equipment, medical and automotive systems. The risk here is not that the CPU is 32-bit — it is that the firmware is frozen, the vendor may not exist in 2038, and the device's service life crosses the boundary. Equipment specified today with a 20-year life expires after it.

Binary and wire formats with a fixed 4-byte time field. A format's field width is part of its specification; it cannot be widened by upgrading anything. This covers older on-disk formats, custom binary protocols, and embedded telemetry framing. If a format definition says "4-byte timestamp", the format has a hard 2038 ceiling until the format itself is versioned.

32-bit builds that were never revisited. Container images pinned to old bases, vendored binaries, cross-compiled firmware toolchains, and anything built against a 32-bit time_t before the _TIME_BITS=64 switch.

Filesystems formatted before the extended-timestamp features. ext4 with 128-byte inodes; XFS without bigtime.

Advertisement

How to Check Your Own System

This is the part worth doing rather than reading. Start at the layer most likely to be broken — storage — not the OS.

1. Check the width of time_t in your build. Eight bytes is 64-bit; four is a 2038 bug.

printf '#include <stdio.h>\n#include <time.h>\nint main(){printf("time_t = %%zu bytes\\n", sizeof(time_t));}\n' > t.c
cc t.c -o t && ./t

Run this with the same compiler and flags your project uses, including the cross-compiler for any embedded target. On 32-bit glibc, compare the result with and without -D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64.

2. Test whether the date survives a round trip through the shell and libc.

date -u -d @2147483647    # GNU/Linux
date -u -r 2147483647     # macOS/BSD
# expect: Tue Jan 19 03:14:07 UTC 2038

date -u -d @2147483648    # one second later — 2038 or 1901?

3. Test every database column type you actually use. This is where most real exposure lives.

-- MySQL: the first fails, the second succeeds
CREATE TABLE t2038 (ts TIMESTAMP, dt DATETIME, epoch BIGINT);
INSERT INTO t2038 (ts) VALUES ('2038-01-19 03:14:08');
INSERT INTO t2038 (dt, epoch) VALUES ('2040-01-01 00:00:00', 2208988800);

-- PostgreSQL: check what your column actually accepts
SELECT '2040-01-01'::timestamptz;

Do this against your real schema, not a sample one. The question is not what the engine supports — it is what your columns were declared as.

4. Test your file and wire formats. Write a record with a post-2038 timestamp through your actual serialization path, read it back, and compare. A format with a 4-byte time field will round-trip to 1901 without raising anything.

5. Run your test suite past the boundary. Do not change your workstation clock — use throwaway isolation:

# Docker: fake the clock with libfaketime
docker run --rm -it -e FAKETIME='2038-01-19 03:14:08' \
  -v "$PWD:/app" -w /app your-image \
  sh -c 'LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1 ./run-tests'

Better still, add permanent test fixtures with post-2038 dates so the boundary is covered on every run rather than once. Paste 2147483647 and neighbouring values into the Unix timestamp converter to generate the fixture dates and confirm what each value should decode to.

6. Check your filesystems.

# XFS: look for "bigtime=1"
xfs_info /path/to/mount | grep -o 'bigtime=[01]'

# ext4: inode size of 128 means 2038-capped
sudo tune2fs -l /dev/sdXN | grep -i 'inode size'

7. Ask vendors in writing, specifically. For embedded and commercial equipment, "is it Y2038 compliant?" invites a reflexive yes. Ask instead: what is the width and signedness of the timestamp field in your on-disk and wire formats, and what does the device do when the clock is set to 19 January 2038? Request the test result, not the assurance.

Three Things People Get Wrong

"We're on 64-bit, so we're fine." The CPU and OS are the layer that is most likely to be fixed already, and the least likely to be where your bug is. A 64-bit process happily writes a 32-bit value into a TIMESTAMP column, a 4-byte protocol field, or an INT. Audit storage and wire formats first; check the CPU last.

"We made it unsigned, so it's solved." Switching a 32-bit timestamp from signed to unsigned moves the ceiling from 2038 to 06:28:15 UTC on 7 February 2106 — the 2106 problem. That is 68 more years, not a fix, and it costs you the ability to represent any date before 1970, which quietly breaks historical records. Unsigned 32-bit time fields are common in binary formats and network protocols, and each has its own rollover date determined by its own epoch — so check the epoch as well as the width. If you are touching the code anyway, widen it to 64-bit rather than buying time.

"It's an OS problem, so patching covers it." Patching does not reformat a filesystem, rewrite a schema, rebuild a vendored binary, or version a file format. Those are the four places the fix does not arrive automatically.

Fixing It

In rough order of return:

  1. Widen storage. INT epoch columns become BIGINT. MySQL TIMESTAMP columns holding future dates become DATETIME (or BIGINT epoch, if you want to keep arithmetic in seconds and handle UTC yourself). Migrating a large table is the expensive part — plan for it, do not discover it in 2037.
  2. Widen types in code. Use explicit int64_t rather than a bare int or long for timestamps. On 32-bit glibc, build with _TIME_BITS=64 and _FILE_OFFSET_BITS=64; on MSVC, do not define _USE_32BIT_TIME_T.
  3. Version your formats. A file or wire format with a 4-byte time field needs a format revision, which means a compatibility plan — the longest-lead item on this list.
  4. Inventory embedded estate now. Not because remediation is urgent, but because procurement is: anything you buy today with a service life past 2038 should have its timestamp handling in the specification.
  5. Add post-2038 dates to your test fixtures permanently. This is what stops the bug coming back after you fix it.

Modern languages mostly default to safe representations — Java's long milliseconds, JavaScript's 64-bit float milliseconds, Python's float seconds, Go's time.Time — but a default is not a guarantee. Any language can serialize into a 32-bit field, and that is the failure that matters.

Y2K Is the Right Comparison, for the Wrong Reason

Y2K is usually invoked to argue that the panic was overblown. The more useful parallel is about shape. Y2K was largely an application-layer problem in code that people owned and could edit. The 2038 problem is a data-representation problem in places nobody edits: schemas, formats, and firmware.

That cuts both ways. The platform layer has genuinely, verifiably been fixed already — which is why this will not be a single dramatic day. But the remaining exposure sits in the least visible, longest-lead places, where the fix requires migration rather than a patch. The systems that fail in 2038 will not be the ones nobody looked at. They will be the ones where someone looked at the OS, found 64 bits, and stopped.

Start with a schema audit and a sizeof(time_t) check. Both take an afternoon, and between them they will find most of what you have.

Frequently Asked Questions

What is the 2038 problem?

The 2038 problem (also called Y2K38, the 2038 bug, or the Unix Millennium Bug) is an integer overflow in systems that store time as a signed 32-bit count of seconds since 1 January 1970. That counter reaches its maximum value of 2,147,483,647 at 03:14:07 UTC on 19 January 2038. One second later it wraps to -2,147,483,648, which decodes to 20:45:52 UTC on 13 December 1901.

What exactly happens on 19 January 2038?

At 03:14:08 UTC any still-affected system reads the clock as 13 December 1901 instead. Outright crashes are the rare case; the common failures are silent and wrong — negative durations, certificates and licences that appear expired, schedulers that fire immediately or never, records that sort backwards, and retention jobs that treat everything as 136 years old and delete it.

Does 64-bit hardware fix the 2038 problem?

No. A 64-bit CPU and OS remove the platform-level limit, but the bug lives wherever a 32-bit value is stored or transmitted. A modern 64-bit server still fails if it writes timestamps to a MySQL TIMESTAMP column, a binary file format with a 32-bit time field, or a network protocol with a fixed 4-byte time field. Storage and wire formats matter as much as the CPU.

Is MySQL affected by the 2038 problem?

Yes, for the TIMESTAMP type. The MySQL 8.4 manual still documents TIMESTAMP's range as '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' UTC. DATETIME is unaffected, with a documented range to '9999-12-31 23:59:59'. Storing far-future dates in TIMESTAMP columns is one of the most common remaining exposures.

How do I test my system for the 2038 problem?

Check the width of time_t with a one-line C program (printf("%zu", sizeof(time_t)) — 8 means 64-bit, 4 means vulnerable), try to store a post-2038 date in every database column and file format you use, and run your test suite against a clock set past 19 January 2038 in a container or VM. Testing storage and wire formats matters more than testing the OS.

What is the 2106 problem?

The 2106 problem is the same overflow in an unsigned 32-bit counter. Because unsigned values have no negative half, the maximum is 4,294,967,295, which is 06:28:15 UTC on 7 February 2106. Code that switched from signed to unsigned to dodge 2038 has bought 68 years rather than fixed the bug, and has lost the ability to represent dates before 1970.

Which systems are still at risk in 2026?

Long-lived 32-bit embedded devices (industrial and building controllers, metering, medical and automotive equipment), software still built against 32-bit time_t without opting into 64-bit time, MySQL TIMESTAMP columns, timestamps stored as 32-bit INT, binary and network formats with fixed 4-byte time fields, and filesystems without extended timestamps — ext4 with 128-byte inodes and XFS without the bigtime feature.

20382038 problemyear 2038Y20382038 bugunix timestamp overflow32-bit integerlegacy systems