Time Zones with zoneinfo
zoneinfo is the standard-library module that attaches real IANA time zones — like America/New_York — to a datetime, turning an ambiguous wall-clock reading into an exact, daylight-saving-aware moment in time.
Learn Time Zones with zoneinfo in our free Python course — an interactive lesson with runnable examples, a practice exercise and a quick reference.
Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Dates and times look simple until two cities, a daylight-saving change, and a midnight deadline collide. Once you understand aware datetimes and store everything in UTC, time-zone bugs simply stop happening.
A naive datetime has no tzinfo — it's "12:00 on Jan 15" with no clue which 12:00. An aware datetime carries a zone, pinning it to one exact instant on the globe:
That trailing -05:00 is the zone's offset from UTC. Its presence is how you know a datetime is aware and can be safely compared or converted.
ZoneInfo("Area/City") builds a real IANA time zone. Pass it as tzinfo when constructing a datetime to make that wall-clock reading aware:
Note 9:00 in June shows -04:00 (EDT), because New York is on daylight saving time then. zoneinfo applies the correct offset for the date automatically.
.astimezone(other_zone) returns the same instant expressed in a different zone — the clock reading changes, but the actual moment does not:
The professional pattern: keep all stored and computed times in UTC (which never shifts), and convert to a local zone only at the moment you show it to a user:
The same zone can have different offsets depending on the date, because of daylight saving time. zoneinfo knows the rules, so the offset and abbreviation update automatically:
Complete the code to make a UTC time and convert it to Tokyo. Replace each ___ , then run it.
✅ Make both aware (attach a zone) before comparing or subtracting them.
✅ Use .astimezone(...) , which recalculates the clock time for the same moment.
✅ Use exact IANA names with underscores: America/New_York , Asia/Kolkata , Europe/Paris .
A meeting is set for 15:00 UTC. Print its local start time for three offices and show that DST is handled (use a July date).
Lesson complete — time zones are no longer scary!
You can tell naive from aware datetimes, build real zones with ZoneInfo , attach and convert them with astimezone , store everything in UTC, and let zoneinfo handle daylight saving for you. That's the foundation of correct date-and-time code.
🚀 Up next: Serialization with pickle — save and load Python objects.
Practice quiz
What distinguishes a naive datetime from an aware one?
- A naive datetime stores the year as a string
- An aware datetime cannot be compared
- A naive datetime has no tzinfo (it's None); an aware one carries a time zone
- There is no difference
Answer: A naive datetime has no tzinfo (it's None); an aware one carries a time zone. A naive datetime's tzinfo is None — just a wall-clock reading; an aware datetime carries a tzinfo, pinning it to an exact instant.
Which standard-library class builds a real IANA time zone?
- ZoneInfo('America/New_York')
- datetime.timezone('America/New_York')
- pytz.timezone('America/New_York')
- TimeZone('America/New_York')
Answer: ZoneInfo('America/New_York'). zoneinfo.ZoneInfo('Area/City') builds a real IANA zone that knows about DST and historical offset changes.
In which Python version was the zoneinfo module added?
- Python 3.6
- Python 3.7
- Python 3.11
- Python 3.9
Answer: Python 3.9. zoneinfo was added to the standard library in Python 3.9, providing IANA time zones without a third-party package.
What does .astimezone(ZoneInfo('Europe/London')) do to an aware datetime?
- Keeps the clock digits and relabels the zone
- Returns the same instant expressed in the new zone (the clock reading changes)
- Raises an error on naive datetimes only
- Deletes the original tzinfo
Answer: Returns the same instant expressed in the new zone (the clock reading changes). astimezone returns the same moment in a different zone — the wall-clock time changes, the actual instant does not.
Why is swapping tzinfo with .replace() to 'convert' a time almost always a bug?
- It keeps the same clock digits but changes the actual instant in time
- It is too slow
- replace() does not exist on datetime
- It always raises ZoneInfoNotFoundError
Answer: It keeps the same clock digits but changes the actual instant in time. replace(tzinfo=...) keeps 9:00 but relabels the zone, changing the real moment; astimezone recalculates the clock for the same instant.
It is 09:00 in New York (EDT) in June. What is the equivalent time in London?
- 09:00
- 13:00
- 14:00
- 22:00
Answer: 14:00. 9am New York in June (-04:00) is 14:00 in London (+01:00) — the same instant shown on two clocks.
Why is storing times in UTC the recommended best practice?
- UTC is always one hour ahead of local time
- UTC has no daylight saving and never shifts, giving an unambiguous baseline
- UTC automatically converts to the user's zone
- Only UTC supports microseconds
Answer: UTC has no daylight saving and never shifts, giving an unambiguous baseline. UTC never observes DST and never changes its rules, so storing/computing in UTC and converting only for display avoids a class of bugs.
How should you get the current time in UTC as an AWARE datetime?
- datetime.utcnow()
- datetime.now()
- ZoneInfo.now()
- datetime.now(timezone.utc)
Answer: datetime.now(timezone.utc). datetime.now(timezone.utc) returns an aware UTC datetime; the deprecated datetime.utcnow() returns a naive one, which is error-prone.
For America/New_York, why does noon in January show -05:00 but noon in July shows -04:00?
- The zone name is misspelled
- Daylight saving time changes the offset; zoneinfo applies the correct one per date
- January has fewer hours
- The datetime is naive in summer
Answer: Daylight saving time changes the offset; zoneinfo applies the correct one per date. zoneinfo knows the DST rules: New York is EST (-05:00) in winter and EDT (-04:00) in summer, applied automatically by date.
What happens with ZoneInfo('America/New York') — a space instead of an underscore?
- It works the same as the underscore form
- It silently defaults to UTC
- It raises ZoneInfoNotFoundError because the IANA name is wrong
- It returns a naive datetime
Answer: It raises ZoneInfoNotFoundError because the IANA name is wrong. IANA names use underscores; 'America/New York' isn't a valid key, so it raises ZoneInfoNotFoundError. Use 'America/New_York'.