Q&A library Python

How do I work with time zones in Python using zoneinfo?

Python Advanced datetimetimezonezoneinfo

Short answer

Attach a real zone to a datetime with ZoneInfo, then convert with astimezone. zoneinfo is built into Python 3.9 and later and handles daylight saving, unlike a fixed offset.

The short version

Use zoneinfo.ZoneInfo to attach a real, named time zone to a datetime, then move between zones with astimezone. zoneinfo ships with Python 3.9 and later and understands daylight saving from the system time zone database, which a fixed timedelta offset does not. Store instants in UTC and convert to local zones only for display.

Attaching a zone and converting

from datetime import datetime
from zoneinfo import ZoneInfo

london = datetime(2021, 6, 1, 12, tzinfo=ZoneInfo("Europe/London"))
new_york = london.astimezone(ZoneInfo("America/New_York"))

print(london)     # 2021-06-01 12:00:00+01:00
print(new_york)   # 2021-06-01 07:00:00-04:00

Setting tzinfo labels the datetime with a zone; astimezone shifts it to the equivalent moment in another zone. The offsets shown (+01:00, -04:00) reflect summer time, which zoneinfo applies automatically.

Naive versus aware datetimes

A datetime with no tzinfo is naive: it has no zone, so it cannot be converted and comparisons across zones are meaningless. One with a zone is aware. Mixing the two raises, which is a common error.

naive = datetime(2021, 6, 1, 12)
naive.astimezone(ZoneInfo("America/New_York"))   # assumes local zone, better to be explicit

Make datetimes aware as early as possible, at the point data enters your program, so the rest of the code deals only with aware values.

Why not a fixed offset

Writing timezone(timedelta(hours=1)) for London is wrong for half the year, because it ignores daylight saving. A named zone like Europe/London knows when the clocks change, so it gives the correct offset for any date. Always use a named zone rather than a hard-coded offset for real locations.

The UTC discipline

The robust pattern is to store and compute in UTC, and convert to a local zone only when showing a time to a person.

now = datetime.now(ZoneInfo("UTC"))
now.astimezone(ZoneInfo("Europe/London"))   # for display

Keeping the internal representation in one zone avoids a whole family of off-by-an-hour bugs, because arithmetic and comparisons all happen in a single, unambiguous frame.

The daylight saving edge cases

When the clocks change, some local times do not exist and others happen twice. zoneinfo resolves these with the fold attribute for the repeated hour, but the safest defence is to do arithmetic in UTC and convert only at the edges, so you never do maths across a clock change in local time.

Availability and data

zoneinfo is standard from Python 3.9. On some minimal systems the time zone database may be missing, in which case installing the tzdata package provides it. On older Python versions, the third-party pytz library filled this role, but zoneinfo is now the recommended built-in choice.

Need this solved on your own data?

The library is free and always will be. When the problem is bigger than an answer, we take on the work.

Start a project