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.
What You'll Learn in This Lesson
1 Naive vs Aware Datetimes
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.
🌐 Creating Zones & Attaching Them
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.
2 Converting Between Zones with astimezone
.astimezone(other_zone) returns the same instant expressed in a different zone — the clock reading changes, but the actual moment does not:
3 Store in UTC, Display Locally
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:
☀️ Daylight Saving Time, Handled for You
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:
🎯 Your Turn: Fill in the Blank
Complete the code to make a UTC time and convert it to Tokyo. Replace each ___ , then run it.
Common Errors
✅ 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 .
🎯 Mini Challenge: A Meeting Scheduler
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).
📚 Keep Learning
- zoneinfo — IANA time zone support — the ZoneInfo class, available zones, and data sources.
- datetime — basic date and time types — aware vs naive objects, astimezone, and timezone.utc.
- strftime / strptime formatting — turning datetimes into strings and back for display.
Frequently Asked Questions
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'.
Continue this course
- Previous: Custom Exceptions & Chaining
- Next: Serialization with pickle