Standard library4 min read

Python zoneinfo Handles DST with Aware Datetimes and fold

How to use ZoneInfo for IANA time zones, distinguish repeated wall times, detect missing times, and ship time-zone data.

  • datetime
  • time zones
  • zoneinfo

Use zoneinfo.ZoneInfo with an aware datetime when a timestamp belongs to a real IANA time zone. The zone object applies the offset rules for the represented date. During a backward clock change, datetime.fold distinguishes the first and second occurrence of the same wall-clock reading.

zoneinfo entered the standard library in Python 3.9. This article targets Python 3.14.7 and follows the zoneinfo documentation and the datetime.fold contract.

A zone key carries historical rules

from datetime import datetime, timezone
from zoneinfo import ZoneInfo

stockholm = ZoneInfo("Europe/Stockholm")
instant = datetime(2026, 7, 1, 12, tzinfo=timezone.utc)
local = instant.astimezone(stockholm)

assert local.isoformat() == "2026-07-01T14:00:00+02:00"

ZoneInfo("Europe/Stockholm") is different from timezone(timedelta(hours=2)). The fixed offset knows only +02:00. The IANA zone contains date-dependent rules, so the same zone can use another offset in winter and can incorporate future rule updates when its data source is updated.

Store an instant in UTC when it represents an event that already happened or a deadline shared across regions. Store the zone key as well when users need the originating civil-time context or when a future recurring schedule should continue to follow local rules.

fold selects one of two repeated times

When clocks move backward, two instants can share one local wall time. PEP 495 calls that repeated interval a fold. fold=0 selects the earlier instant and fold=1 the later one:

from datetime import datetime
from zoneinfo import ZoneInfo

los_angeles = ZoneInfo("America/Los_Angeles")
first = datetime(2020, 11, 1, 1, 30, tzinfo=los_angeles, fold=0)
second = first.replace(fold=1)

assert first.isoformat() == "2020-11-01T01:30:00-07:00"
assert second.isoformat() == "2020-11-01T01:30:00-08:00"
assert second.timestamp() - first.timestamp() == 3600

The flag is 0 or 1, not a general daylight-saving indicator. Political offset changes can also create folds, and some transitions are not one hour. PEP 495 defines fold=0 as the earlier and fold=1 as the later of the two matching instants.

Converting a UTC instant with astimezone sets fold correctly because the instant is already unambiguous. Constructing a local time from user fields requires the application to ask which occurrence the user means when both exist.

Missing wall times are not rejected

A forward clock change creates a gap containing local readings that never occur. Attaching a ZoneInfo does not validate that a wall time exists:

from datetime import datetime, timezone
from zoneinfo import ZoneInfo

zone = ZoneInfo("America/Los_Angeles")
entered = datetime(2020, 3, 8, 2, 30, tzinfo=zone)
round_trip = entered.astimezone(timezone.utc).astimezone(zone)

assert round_trip.replace(tzinfo=None) != entered.replace(tzinfo=None)

On that date, Los Angeles jumps from 01:59:59 to 03:00:00, so 02:30 is missing. Python permits the invalid local construction. A form that accepts local date and time should round-trip through UTC and compare the wall fields when rejecting gaps matters. A changed result means the input was missing under the installed rules.

For a fold, both choices round-trip, so gap detection is separate from ambiguity handling. To detect ambiguity, compare the UTC offsets produced by fold=0 and fold=1, then confirm that both candidates round-trip to the same wall fields. This avoids treating an arbitrary fold=1 outside a transition as meaningful.

Deploy the time-zone database

The zoneinfo module contains the API but not the IANA database. It searches the system time-zone path and then the first-party tzdata package. If neither source contains a requested key, ZoneInfo raises ZoneInfoNotFoundError.

Many POSIX deployments provide system data. Windows generally has no well-known IANA database path. Applications that need predictable cross-platform availability should declare a dependency on tzdata, which CPython core developers maintain as the first-party PyPI data package. zoneinfo is unavailable on WASI.

Time-zone rules change. Pinning tzdata makes deployments agree, but it also means they will not receive later political updates until the pin moves. Choose an update process based on whether historical repeatability or prompt future-rule changes matter more, and record the data version alongside calculations that must be reproduced exactly.