~ / topics / Languages
$man lang-python

Python

In brief: Pure Python handles dates far beyond 2038. Its integers have no size limit, and datetime runs to year 9999. Python does use the operating system's C time functions underneath, though, so on a 32-bit system some calls fail with an "out of range" error for dates after January 2038. Code that packs timestamps into 4-byte binary fields also breaks.

Risk level Low on 64-bit builds. Medium on 32-bit builds and for binary formats
Status 64-bit CPython is unaffected. 32-bit builds inherit the C library's time_t. bpo-5537 (cookie jar on 32-bit) is still open
Last reviewed 2026-10-21

2038 status at a glance

Not affected or different deadline (1), and why

Overview

Python has several ways to represent time:

API Type Range
time.time() float seconds since the epoch No 2038 limit. Precision drops slowly with magnitude
time.time_ns() (3.7+) int nanoseconds Unlimited (arbitrary-precision int) [1]
datetime.datetime broken-down fields Years 1 (MINYEAR) to 9999 (MAXYEAR) [2]
time.localtime(), gmtime(), ctime(), mktime() call the platform C library Limited by the C time_t [1]

The time docs say it directly: "The cut-off point in the future is determined by the C library; for 32-bit systems, it is typically in 2038" [1].

Technical details

1. Platform time_t limits (32-bit builds)

The time module and the fromtimestamp()/timestamp() conversions in datetime go through the C library's localtime(), gmtime() and mktime(). According to the docs, they "may raise OverflowError, if the timestamp is out of the range of values supported by the platform C localtime() or gmtime() functions… It's common for this to be restricted to years in 1970 through 2038" [2]. From Python 3.3, out-of-range values raise OverflowError rather than ValueError.

import datetime
# On a 32-bit build with 32-bit time_t:
datetime.datetime.fromtimestamp(2**31)
# OverflowError: timestamp out of range for platform time_t

# On 64-bit Linux (tested 2026-10-21, CPython 3, x86-64):
datetime.datetime.fromtimestamp(2**31, datetime.timezone.utc)
# datetime.datetime(2038, 1, 19, 3, 14, 8, tzinfo=datetime.timezone.utc)

Workaround that avoids the C library entirely (UTC only):

from datetime import datetime, timedelta, timezone
EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)

def utc_from_ts(ts: int) -> datetime:
    return EPOCH + timedelta(seconds=ts)      # pure Python, works to 9999

def ts_from_utc(dt: datetime) -> int:
    return (dt - EPOCH) // timedelta(seconds=1)

Victor Stinner proposed the same technique for the standard library's cookie code (bpo-5537, below) [4]. Local-time conversion still needs the C library or zoneinfo, so on 32-bit systems keep stored values in UTC.

2. struct.pack('i') and fixed-width binary fields

struct's 'i', 'l' (standard size) and 'I' codes are 4 bytes. Packing a value out of range raises struct.error [3]. An error is better than silent corruption, but it's still an outage:

import struct, time
struct.pack('<i', 2**31)
# struct.error: 'i' format requires -2147483648 <= number <= 2147483647

struct.pack('<I', int(time.time()))   # unsigned: works until 2106-02-07
struct.pack('<q', int(time.time()))   # FIX: 8-byte signed

The same applies to array.array('i'), NumPy int32 arrays, ctypes.c_int and c_long on Windows or 32-bit, and the 4-byte fields of many file formats (for example, gzip's MTIME and the timestamp in .pyc headers). See File-Formats-and-Protocols.

import numpy as np
np.array([2**31], dtype=np.int32)   # OverflowError in NumPy 2.x; older NumPy wrapped silently
np.datetime64(2**31, 's')           # FIX: datetime64 is 64-bit

(The NumPy 1.x vs 2.x behaviour is from general knowledge and is unverified here.)

3. C extensions

C extension modules see the C time_t directly. Patterns to audit:

Use time_t with the PyLong_AsLongLong/PyLong_FromLongLong pair, or CPython's internal _PyTime_ObjectToTime_t helpers, which range-check before casting [5].

4. time.time() is a float

A double has 53 bits of mantissa, so time.time() keeps sub-microsecond resolution well past 2038. It is not a 2038 problem. Use time.time_ns() when you need exact integer nanoseconds [1].

5. Relevant bugs and history

Issue Summary Status
bpo-5537 (GH-49787) LWPCookieJar can't save cookies that expire in 2038 or later on 32-bit platforms ("timestamp out of range for platform time_t"). Opened 2009-03-22. Fixed in 2011, reopened in 2020 for 32-bit 3.7.6/2.7.16 Open. Affected versions listed as 3.8–3.10 [4]
bpo-4379 / bpo-8278 Windows os.stat()/os.utime() used int. Switched to 64-bit time_t so post-2038 file times work Fixed [6]
Django #30264 Test fails on 32-bit only: a 2050 date overflows time_t Resolved by adjusting the test [7]
PyMongo PYTHON-122 datetime.utcfromtimestamp() can't convert large dates on 32-bit; calendar.timegm works Driver worked around it [8]

On 32-bit Linux, os.stat() can only return correct post-2038 st_mtime values if CPython itself was compiled with 64-bit time_t. Red Hat bug 1419736 documents glibc's 32-bit stat returning wrong st_mtime for out-of-range values [9]. Distributions that finished a 64-bit time transition (such as Debian armhf from trixie) build their Python packages with the new flags. Self-built 32-bit Pythons need CFLAGS="-D_FILE_OFFSET_BITS=64 -D_TIME_BITS=64" on glibc ≥ 2.34.

Affected versions / products

Build time/datetime C conversions Pure-Python arithmetic
CPython 64-bit (Linux, macOS, Windows) OK past 2038 OK
CPython 32-bit on Windows (VS2005+ CRT) 64-bit time_t, OK OK
CPython 32-bit on glibc without _TIME_BITS=64 OverflowError after 2038-01-19 OK
CPython on musl ≥ 1.2 (e.g. Alpine 32-bit) 64-bit time_t, OK OK
MicroPython / CircuitPython Port-specific epochs and int sizes; check per board (see OS-Embedded-and-RTOS) varies

Risk & compliance notes

Detection & mitigation

  1. Run python -c "import struct; print(struct.calcsize('P')*8)" to check the interpreter width. On 32-bit, run python -c "import time; time.gmtime(2**31)". An OverflowError means the interpreter has a 32-bit time_t.
  2. Grep for pack('i', pack('<i', 'l', c_int, int32 near time-related names.
  3. Use freezegun or time-machine in tests to move the clock to 2038-01-19T03:14:08Z. Note that these patch Python's view of the clock, not the C library's, so test on a real 32-bit build too.
  4. Prefer timezone-aware datetime objects and timedelta arithmetic over epoch integers.
  5. See Testing-and-Detection and Remediation-Guide.

See also

References

  1. time — Time access and conversions, Python 3 documentation — cut-off "for 32-bit systems, it is typically in 2038"; time() float; time_ns() int (3.7+); localtime() OverflowError.
  2. datetime — Basic date and time types, Python 3 documentation — MINYEAR 1 / MAXYEAR 9999; fromtimestamp() "commonly restricted to years in 1970 through 2038".
  3. struct — Interpret bytes as packed binary data, Python 3 documentation — 'i'/'l' standard size 4; struct.error when out of range.
  4. bpo-5537: LWPCookieJar cannot handle cookies with expirations of 2038 or greater on 32-bit platforms — Python bug tracker — opened 2009, reopened 2020, still open; timedelta workaround.
  5. bpo message 213756 — Python bug tracker — range check before casting to time_t in CPython's pytime conversion.
  6. bpo message 76204 (issue 4379) — Python bug tracker — Windows os.stat() post-2038 support via 64-bit time_t.
  7. Django ticket #30264 — Django Project — OverflowError in tests on 32-bit platforms for a 2050 date.
  8. PYTHON-122 — MongoDB Jira — utcfromtimestamp limited by time_t on 32-bit.
  9. Bug 1419736: 32-bit stat returns wrong st_mtime if file timestamp does not fit in 32 bits — Red Hat Bugzilla — the C-level behaviour os.stat inherits.