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'.