tzdata
tzdata is the IANA timezone database compiled with zic and repackaged as a pip-installable wheel. There is almost no code in it: just the binary TZif files for every zone plus two constants (tzdata.IANA_VERSION and __version__). It exists because of PEP 615: the stdlib zoneinfo module reads system timezone data first, and on platforms without /usr/share/zoneinfo (Windows, slim and distroless containers) it falls back to this package. Installing it is the standard fix for ZoneInfoNotFoundError, and it is how you pin timezone data independent of the host OS.
A boring dependency in the best sense: one job, done under the python GitHub org, tracking IANA releases promptly. Install it whenever zoneinfo might run without system data, keep it unpinned, and skip it on well-maintained Linux hosts.
Use it if
- You use zoneinfo on Windows or in slim containers (python:slim, alpine, distroless) where system timezone data is missing and ZoneInfo raises ZoneInfoNotFoundError
- You want reproducible timezone data across dev, CI, and production instead of whatever version each host OS happens to have
- You publish a library that uses zoneinfo and needs to work everywhere: the conditional dependency tzdata; sys_platform == 'win32' (or unconditional) is the accepted pattern
- You want timezone updates on pip's schedule: a pip install -U tzdata picks up new IANA releases without waiting for OS package updates
- You are on a maintained Linux or macOS host with system tzdata installed: zoneinfo checks TZPATH first, so this package is dead weight there and installing it changes nothing
- Your code still uses pytz: pytz bundles its own copy of the database and never reads this package, so installing both just gives you two possibly disagreeing datasets
- You are on Python 3.8 or older without backports.zoneinfo: the package is pure data with no API, so nothing in the stdlib will consume it
- You expect a pip upgrade to fix timestamps in a running process: ZoneInfo instances are cached per process, so new data only applies after a restart or an explicit no_cache reload
Setup reality
pip install tzdata is as painless as packaging gets: a universal pure-data wheel, no compilation, no platform variants, no dependencies. The subtle parts are behavioral. It only takes effect when zoneinfo finds nothing on TZPATH, so verifying a Windows fix from a Linux laptop proves nothing; set PYTHONTZPATH to empty to simulate. Versions are calendar-based (2026.3 wraps IANA release 2026c), and you should let it float rather than pin an exact version, because stale timezone data is a silent correctness bug, not a stability feature.
Patterns
Fix ZoneInfoNotFoundErrorfix-zoneinfo-not-found
# pip install tzdata
from zoneinfo import ZoneInfo
tz = ZoneInfo("America/New_York") # works even with no system tz dataNo import of tzdata is needed; zoneinfo discovers the package automatically when nothing is found on TZPATH. This is the whole fix for Windows and slim Docker images.
Create an aware datetime with zoneinfocreate-aware-datetime
from datetime import datetime
from zoneinfo import ZoneInfo
dt = datetime(2026, 8, 5, 12, 0, tzinfo=ZoneInfo("Asia/Kolkata"))
print(dt.isoformat()) # 2026-08-05T12:00:00+05:30Unlike pytz, passing tzinfo directly to the constructor is correct with zoneinfo; there is no localize step.
Convert a datetime between zonesconvert-between-zones
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
now_utc = datetime.now(timezone.utc)
local = now_utc.astimezone(ZoneInfo("Europe/Paris"))
back = local.astimezone(timezone.utc)Arithmetic on zoneinfo-aware datetimes re-resolves the UTC offset automatically across DST changes; no normalize call exists or is needed.
Check which IANA release you are runningcheck-data-version
import tzdata
from importlib.metadata import version
print(tzdata.IANA_VERSION) # '2026c'
print(version("tzdata")) # '2026.3'IANA versions like 2026c are not valid PEP 440 versions, so the pip version is YYYY.n with n counting releases that year; IANA_VERSION gives the real upstream identifier.
List all available zone nameslist-available-zones
import zoneinfo
zones = zoneinfo.available_timezones()
print(len(zones))
print(sorted(zones)[:3])available_timezones() returns an unsorted set that merges system data and the tzdata package, and it does filesystem walks on every call; cache the result instead of calling it per request.
Force the tzdata package instead of system dataforce-tzdata-over-system
# option 1: environment, before interpreter start
# PYTHONTZPATH="" python app.py
# option 2: at runtime, before creating ZoneInfo objects
import zoneinfo
zoneinfo.reset_tzpath(to=[])
tz = zoneinfo.ZoneInfo("Europe/Paris") # now read from tzdata wheelWith an empty TZPATH, zoneinfo can only use the tzdata package, which makes every environment read identical data; do this in tests when host OS data versions differ.
Declare tzdata in pyproject.tomldeclare-dependency
[project]
dependencies = [
"tzdata; sys_platform == 'win32'",
]
# or unconditionally, which also covers slim Linux containers:
# dependencies = ["tzdata"]The win32 marker misses slim and distroless Linux images that lack /usr/share/zoneinfo; depending on it unconditionally costs little and removes a whole class of deploy-time surprises.
Disambiguate repeated wall-clock times with foldambiguous-time-fold
from datetime import datetime
from zoneinfo import ZoneInfo
paris = ZoneInfo("Europe/Paris")
# 02:30 on 2026-10-25 occurs twice (clocks fall back)
first = datetime(2026, 10, 25, 2, 30, tzinfo=paris) # fold=0
second = datetime(2026, 10, 25, 2, 30, fold=1, tzinfo=paris)
print(first.utcoffset(), second.utcoffset()) # 2:00:00 1:00:00fold replaces pytz's is_dst: 0 picks the first occurrence, 1 the second. Nothing raises on ambiguous input, so user-entered local times near transitions need explicit handling.
Pick up new data in a long-running processreload-after-upgrade
from zoneinfo import ZoneInfo
# after pip install -U tzdata in-place:
fresh = ZoneInfo.no_cache("America/Santiago")The normal constructor caches instances for the life of the process, so already-created zones keep old rules after an upgrade. no_cache reads fresh data but does not update datetimes holding the old instance; a restart is the honest fix.
Read the raw TZif files and zone listread-raw-tzif
from importlib.resources import files
zone_names = (files("tzdata") / "zones").read_text().splitlines()
raw = (files("tzdata.zoneinfo") / "Europe" / "Paris").read_bytes()
print(raw[:4]) # b'TZif'The zones file lists every key the package ships; the binary files live under tzdata/zoneinfo/. Useful for tools that parse TZif themselves instead of going through zoneinfo.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| pytz | PyPI | Legacy codebases that predate zoneinfo and already depend on its localize/normalize API |
| backports.zoneinfo | PyPI | You need the zoneinfo API itself on Python 3.6 to 3.8; pair it with tzdata for the data |
| python-dateutil | PyPI | You also need parsing and recurrence rules; dateutil.tz reads system data and carries its own fallback |