2026, Jan 07 23:00

Fixing Python datetime with ancient dates on Linux: strftime drops leading zeros; use isoformat for ISO-8601-safe round trips

Learn why Python datetime strftime trims leading zeros for pre-1000 AD years on Linux, breaking strptime, and how isoformat ensures stable ISO-8601 round-trips.

Handling very old dates in Python’s datetime can be surprisingly tricky. A seemingly harmless round-trip through strftime/strptime breaks for years before 1000 AD: the formatted string drops leading zeros, and parsing it back raises a ValueError. This guide walks through the issue, why it occurs on Linux, and how to steer clear of it when you need consistent ISO-style output.

Reproducing the inconsistency

The example below formats a date from year 33 AD. Direct ISO output looks good, but strftime trims the year, and the subsequent strptime fails.

import datetime

ancient_date_obj = datetime.date(year=33, month=3, day=28)

# ISO-style serialization preserves leading zeros
ancient_date_obj.isoformat()
# "0033-03-28"

# strftime drops them for %Y on this platform
ancient_date_obj.strftime("%Y-%m-%d")
# "33-03-28"

# The truncated year no longer matches the expected %Y-%m-%d format
datetime.datetime.strptime(ancient_date_obj.strftime("%Y-%m-%d"), "%Y-%m-%d")
# ValueError: time data '33-03-28' does not match format '%Y-%m-%d'

What’s actually going on

The behavior stems from the platform C library behind strftime on Linux. For pre-1000 AD years, its implementation omits leading zeros for %Y and %G, so you end up with a shortened year string like "33" instead of "0033". Python’s datetime delegates to the platform for strftime, and the documentation explicitly notes that the set and semantics of format codes can vary by platform.

“The full set of format codes supported varies across platforms, because Python calls the platform C library’s strftime() function, and platform variations are common.”

A related discussion is tracked in CPython’s issue tracker: https://github.com/python/cpython/issues/120713.

The practical fix

If you need a stable, zero-padded, ISO-8601-like representation for ancient dates, avoid strftime for this case and rely on date.isoformat() instead. It produces the expected four-digit year with leading zeros, which sidesteps the platform-dependent trimming.

import datetime

ancient_date_obj = datetime.date(33, 3, 28)
iso_string = ancient_date_obj.isoformat()
# iso_string == "0033-03-28"

This keeps your serialization consistent across platforms and prevents round-trip failures caused by truncated years.

Why this matters

Applications that serialize dates to strings and later parse them back can break in subtle ways when crossing platform boundaries or when handling historical data. On Ubuntu 24.04 with Python 3.12, the shortened year from strftime("%Y") leads directly to parsing exceptions during strptime("%Y-%m-%d"). If your code assumes a fixed-width year, this is a correctness and reliability risk, not just a formatting quirk.

Takeaways

Be mindful that strftime behavior is platform-dependent, and for pre-1000 AD years on Linux, %Y and %G may omit leading zeros. For ISO-style, zero-padded output, use isoformat() to avoid discrepancies. And if you do accept user-entered dates, don’t rely on a strftime-produced string to round-trip back into a date for ancient years—prefer a representation that you know is stable on your target environments.