skip to content

Where does zoneinfo.ZoneInfo load its IANA time-zone data from at runtime?

level: middleimportance: should knowfreq 36%

answer

  1. The library ships code, not data
  2. A search path, then a package fallback
  3. Same key, resolved as a relative path
  4. Windows and slim images have no system copy
  5. TZPATH, PYTHONTZPATH, and the tzdata distribution

basics

~10 s

It searches the directories in zoneinfo.TZPATH, which default to the system tz database, and falls back to the first-party tzdata distribution from PyPI if it is installed. With neither present, a lookup raises zoneinfo.ZoneInfoNotFoundError.

solid answer

~40 s

The standard library ships the **code**, not the **data**. On import, `zoneinfo` builds a search path — exposed as `zoneinfo.TZPATH` — from the `PYTHONTZPATH` environment variable or a compile-time default listing the usual system locations such as `/usr/share/zoneinfo`. A key like `Europe/Berlin` is resolved as a relative path under each of those directories in turn. If none of them has it, `zoneinfo` falls back to the `tzdata` distribution on PyPI, which packages the same database as importable resources; if that is not installed either, the lookup raises `zoneinfo.ZoneInfoNotFoundError`, a subclass of `KeyError`. This is why the same code works on a typical Linux host and fails on Windows or a stripped container image — those have no system database — and why services usually declare `tzdata` as an ordinary dependency.

code

python · 9 lines
python
import zoneinfo

print(zoneinfo.TZPATH)
print("Europe/Berlin" in zoneinfo.available_timezones())

try:
    zoneinfo.ZoneInfo("Mars/Olympus_Mons")
except zoneinfo.ZoneInfoNotFoundError as exc:
    print("unknown key:", exc)

go deeper

for a junior

Know that the zone rules are data read from the machine, not something built into Python, and that installing the tzdata package is the usual fix when a key will not resolve.

for a middle

Explain the order: the TZPATH directories built from PYTHONTZPATH or a compile-time default, then the tzdata distribution, then ZoneInfoNotFoundError. Say why Windows and slim images differ.

for a senior

Show the deployment judgement: declare tzdata as a dependency so rendering is reproducible across the fleet, resolve required keys at startup rather than mid-request, and know whether the platform or your lockfile controls the revision in use.

for a principal

Own the policy: whether rule updates arrive by base-image patching or by a deliberate dependency bump, what that means for reproducing a historical report, and how the choice is enforced consistently across services.

## Code in the stdlib, data on the system The IANA time-zone database is revised several times a year as governments change their rules, so pinning a snapshot of it inside CPython's own release cycle would be the wrong design. Instead `zoneinfo` reads whatever compiled database it can find at runtime, in a defined order. **1. The search path.** At import time the module computes `zoneinfo.TZPATH`, a tuple of absolute directories. It comes from the `PYTHONTZPATH` environment variable if set, otherwise from a default baked in when Python was built — typically `/usr/share/zoneinfo`, `/usr/lib/zoneinfo`, `/usr/share/lib/zoneinfo`, `/etc/zoneinfo`. `zoneinfo.reset_tzpath()` recomputes it, and accepts an explicit sequence of directories, which is occasionally useful in tests. Entries that are not absolute paths are ignored with a warning. **2. Key resolution.** `ZoneInfo("Europe/Berlin")` treats the key as a relative path and looks for `Europe/Berlin` under each search directory in order, opening the first hit as a TZif file. That is why keys use forward slashes on every platform and why they are case-sensitive. **3. The fallback.** If no directory yields the key, `zoneinfo` tries the `tzdata` distribution — a first-party package published on PyPI by the same maintainers, containing the compiled database as package resources. If it is not installed, or does not have the key either, you get `zoneinfo.ZoneInfoNotFoundError`. It subclasses `KeyError`, so a broad `except KeyError` will swallow it; catch the specific class. ## Why this is a deployment question, not a trivia question The three environments where the system database is absent are exactly the three where this bites: * **Windows** has no `/usr/share/zoneinfo` at all. Nothing resolves without `tzdata`. * **Slim container images** frequently drop the tz database to save space, so code that worked on the developer's machine raises on the first zone lookup in production. * **Frozen or bundled applications** ship their own filesystem and may not include system data directories. The fix in all three is the same: declare `tzdata` as a normal runtime dependency of the project, alongside everything else, so the data travels with the application. Many teams do this unconditionally rather than conditionally on the platform, precisely so that behaviour does not depend on the base image. ## Staleness is the other half Having *a* database is not the same as having a *current* one. If you rely on the system copy, the rules your service applies are controlled by whoever maintains the image, and a long-lived host can drift years behind. If you depend on `tzdata`, the version is pinned by your lockfile and updates when you deliberately bump it — which also makes rendering reproducible across a fleet, because every process is reading the same revision. The tradeoff is real either way: the system copy gets patched by the platform without a redeploy; the packaged copy is reproducible but only updates when you ship. Pick deliberately, and know which one your processes are actually using — printing `zoneinfo.TZPATH` at startup is a cheap way to find out. ## Useful entry points * `zoneinfo.available_timezones()` returns the set of keys visible through the current search path and the fallback. It walks the whole database, so it is a startup or diagnostic call, not something for a hot path. * `zoneinfo.ZoneInfo.from_file(fobj, key=None)` builds a zone from an open TZif file directly, bypassing key lookup entirely — the escape hatch when you ship a specific file yourself. * `zoneinfo.ZoneInfo.no_cache(key)` builds an instance that is not shared through the cache, and `zoneinfo.ZoneInfo.clear_cache()` empties the cache so that subsequent lookups re-read the files, which is what you call after the underlying data has been updated in a long-running process. * A constructed instance exposes `key`, the identifier it was built from — handy when you want to persist the identifier next to a value. ## The failure mode in practice Because the lookup happens when a `ZoneInfo` is constructed, a missing database usually surfaces as an exception deep inside request handling, on the first record that needs a zone, rather than at startup. A cheap defence is to resolve the zones your application knows it needs during startup, so a missing or unexpected key fails fast and loudly instead of at 3 a.m. on an unusual code path.

  • Your service raises ZoneInfoNotFoundError only in the container, never locally. What happened?
    The image has no system tz database — slim base images routinely omit `/usr/share/zoneinfo` — and `tzdata` is not among the installed dependencies, so both the search path and the fallback come up empty. Add `tzdata` to the project's runtime dependencies so the data ships with the code, and resolve the zones you need at startup so the failure is loud and immediate.
  • Should a long-running process do anything when the underlying time-zone data is updated?
    Constructed zones are cached by key, so a running process keeps using the rules it parsed. `zoneinfo.ZoneInfo.clear_cache()` drops that cache so later lookups re-read the files; zones still referenced by live datetime objects keep their old rules until those objects are gone. Most services simply restart on redeploy, which is the simpler and more predictable answer.
  • What does zoneinfo.available_timezones() cost, and where should you call it?
    It enumerates the whole database across the search path and the fallback, so it touches the filesystem broadly. Call it at startup, in a diagnostic endpoint, or to validate configuration — not per request. To check a single key, just construct the `ZoneInfo` and catch `ZoneInfoNotFoundError`.

saying these in an interview costs you the question

  • Believes CPython bundles the IANA database itself
  • Thinks tzdata is only needed on Windows
  • Expects a missing key to return None instead of raising
  • Assumes the system database is always current
  • Calls available_timezones() on a per-request path

context