Python
In brief: Pure Python handles dates far beyond 2038. Its integers have no size limit, and
datetimeruns 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
- At risk ConfirmedCPython 32-bit on glibc without _TIME_BITS=64time and datetime C conversions raise OverflowError after 2038-01-19. Pure-Python arithmetic is fine.Do: Use a 64-bit or time64 build of Python.
Not affected or different deadline (1), and why
- Not affectedPython on 64-bit systems, 32-bit Windows, or musl 1.2+time and datetime conversions use a 64-bit time_t on these builds.
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:
PyArg_ParseTuple(args, "i", &secs)orPyLong_AsLong()into anint/longthat is then passed as a time.longis 32-bit on Windows and on 32-bit Unix.- Cython code with
cdef int ts. - Extensions that build with their own
CFLAGSand so don't use_TIME_BITS=64on 32-bit glibc, even when the interpreter does. That creates an ABI mismatch between the interpreter and the extension. See Lang-C-and-CPP.
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
- The highest risk is on 32-bit Raspberry Pi OS and other armhf images, on embedded Linux appliances running Python, and on pinned old Docker base images (
i386/*,arm32v7/*). - Code paths that handle future dates (licence expiry, certificate
notAfter, cookieExpires, scheduling) fail before 2038. - Owners: application teams for code, and platform/OS image owners for interpreter build flags.
Detection & mitigation
- Run
python -c "import struct; print(struct.calcsize('P')*8)"to check the interpreter width. On 32-bit, runpython -c "import time; time.gmtime(2**31)". AnOverflowErrormeans the interpreter has a 32-bittime_t. - Grep for
pack('i',pack('<i','l',c_int,int32near time-related names. - Use
freezegunortime-machinein 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. - Prefer timezone-aware
datetimeobjects andtimedeltaarithmetic over epoch integers. - See Testing-and-Detection and Remediation-Guide.
See also
References
- 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. - datetime — Basic date and time types, Python 3 documentation — MINYEAR 1 / MAXYEAR 9999;
fromtimestamp()"commonly restricted to years in 1970 through 2038". - struct — Interpret bytes as packed binary data, Python 3 documentation —
'i'/'l'standard size 4;struct.errorwhen out of range. - 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.
- bpo message 213756 — Python bug tracker — range check before casting to
time_tin CPython's pytime conversion. - bpo message 76204 (issue 4379) — Python bug tracker — Windows
os.stat()post-2038 support via 64-bittime_t. - Django ticket #30264 — Django Project — OverflowError in tests on 32-bit platforms for a 2050 date.
- PYTHON-122 — MongoDB Jira —
utcfromtimestamplimited bytime_ton 32-bit. - 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.statinherits.