Work with Time Zones in Python

Learn how to handle time zones in Python using the zoneinfo module, create timezone-aware datetimes, and convert between time zones.

7 min read

When you work with dates and times in Python, you eventually need to handle Python time zones. A meeting scheduled for 10:00 AM in New York happens at a different UTC moment than a meeting at 10:00 AM in Tokyo. Python's zoneinfo module, available since Python 3.9, provides access to the IANA time zone database so you can create timezone-aware datetimes and convert between zones.

pythonpython
from datetime import datetime
from zoneinfo import ZoneInfo
 
ny_time = datetime(2026, 7, 12, 10, 0, tzinfo=ZoneInfo("America/New_York"))
tokyo_time = datetime(2026, 7, 12, 10, 0, tzinfo=ZoneInfo("Asia/Tokyo"))
 
print(ny_time)               # 2026-07-12 10:00:00-04:00
print(tokyo_time)             # 2026-07-12 10:00:00+09:00
print(ny_time < tokyo_time)  # True

Both datetimes say 10:00 AM, but New York is at UTC-4 while Tokyo is at UTC+9. Tokyo's 10:00 AM happens 13 hours earlier in real time, so the comparison correctly shows New York's meeting comes first.

Naive vs aware datetimes

A datetime is naive when it has no time zone information. It is aware when it carries a tzinfo attribute.

pythonpython
from datetime import datetime, timezone
 
naive = datetime(2026, 7, 12, 10, 0)
aware = datetime(2026, 7, 12, 10, 0, tzinfo=timezone.utc)
 
print(naive.tzinfo)  # None
print(aware.tzinfo)  # UTC

A naive datetime has tzinfo=None. You cannot reliably compare a naive datetime with an aware one, and you cannot convert a naive datetime to another time zone without first attaching a zone to it.

Naive datetimeAware datetime
Has tzinfoNo (None)Yes
Safe to compare across zonesNoYes
Can call astimezone()NoYes
Typical sourcetime.strptime, plain datetime()datetime.now(timezone.utc), ZoneInfo attached

Python raises an error if you try to subtract an aware datetime from a naive one:

pythonpython
from datetime import datetime, timezone
 
naive = datetime(2026, 7, 12, 10, 0)
aware = datetime(2026, 7, 12, 10, 0, tzinfo=timezone.utc)
print(aware - naive)

Running this code raises an error, because Python refuses to guess how a naive datetime relates to a zone. The traceback tells you exactly what went wrong:

texttext
TypeError: can't subtract offset-naive and offset-aware datetimes

Always know whether your datetimes are naive or aware before doing arithmetic or comparisons.

Getting the current time in UTC

Use datetime.now(timezone.utc) to get the current UTC time as an aware datetime.

pythonpython
from datetime import datetime, timezone
 
now_utc = datetime.now(timezone.utc)
print(now_utc)          # 2026-07-12 18:30:05.123456+00:00
print(now_utc.tzinfo)  # UTC

The +00:00 in the output confirms this is a UTC datetime. Always prefer this over datetime.utcnow(), which returns a naive datetime and has been deprecated in newer Python versions.

Working with IANA time zones

The zoneinfo module reads time zone data from your system's IANA database. You specify a time zone with its IANA key, like "America/New_York" or "Asia/Tokyo".

pythonpython
from datetime import datetime
from zoneinfo import ZoneInfo
 
meeting = datetime(2026, 11, 15, 9, 0, tzinfo=ZoneInfo("America/Chicago"))
print(meeting)  # 2026-11-15 09:00:00-06:00

In November, Chicago is at UTC-6 (Central Standard Time). The offset is automatically correct, accounting for whether daylight saving time is in effect on that date.

If the same code ran in July, ZoneInfo would report UTC-5 instead, because Chicago observes Central Daylight Time in summer. You never have to track these transitions yourself, since the IANA database that zoneinfo reads from is updated whenever a country changes its daylight saving rules.

You can list available time zones with zoneinfo.available_timezones(), though the list is large. A few commonly used keys:

IANA keyRegion
"UTC"Coordinated Universal Time
"America/New_York"US Eastern
"America/Chicago"US Central
"America/Denver"US Mountain
"America/Los_Angeles"US Pacific
"Europe/London"United Kingdom
"Europe/Berlin"Central European
"Asia/Tokyo"Japan
"Asia/Kolkata"India
"Australia/Sydney"Australian Eastern

Avoid three-letter abbreviations like "EST" or "PST". They are ambiguous and do not handle daylight saving transitions correctly.

Converting between time zones

Use the astimezone() method to convert an aware datetime from one time zone to another.

pythonpython
from datetime import datetime
from zoneinfo import ZoneInfo
 
utc_time = datetime(2026, 7, 12, 15, 0, tzinfo=ZoneInfo("UTC"))
ny_time = utc_time.astimezone(ZoneInfo("America/New_York"))
tokyo_time = utc_time.astimezone(ZoneInfo("Asia/Tokyo"))
 
print(ny_time)      # 2026-07-12 11:00:00-04:00
print(tokyo_time)  # 2026-07-13 00:00:00+09:00

3:00 PM UTC is 11:00 AM in New York and midnight the next day in Tokyo. astimezone handles the math and adjusts the date when the conversion crosses midnight.

Attaching a time zone to a naive datetime

When you receive a naive datetime from user input or a database, attach a time zone with replace().

pythonpython
from datetime import datetime
from zoneinfo import ZoneInfo
 
user_input = datetime(2026, 7, 12, 14, 30)
aware = user_input.replace(tzinfo=ZoneInfo("America/Chicago"))
print(aware)  # 2026-07-12 14:30:00-05:00

replace(tzinfo=...) tells Python "this naive datetime should be interpreted as Chicago time." It does not convert the hour value. Use this only when you know the datetime already represents the intended local time.

Making a naive datetime safe with UTC

When you do not know the original time zone of a naive datetime, the safest approach is to assume UTC.

pythonpython
from datetime import datetime, timezone
 
naive = datetime(2026, 7, 12, 14, 30)
aware_utc = naive.replace(tzinfo=timezone.utc)
print(aware_utc)  # 2026-07-12 14:30:00+00:00

This treats the original 14:30 as UTC. If the datetime actually came from local time, you need to attach the correct local zone instead and then convert to UTC if needed.

Practical example: displaying event times

A common pattern is to store events in UTC and display them in the viewer's local time.

pythonpython
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
 
event_time_utc = datetime(2026, 12, 25, 0, 0, tzinfo=timezone.utc)
 
for zone_name in ["America/New_York", "Europe/London", "Asia/Kolkata"]:
    local = event_time_utc.astimezone(ZoneInfo(zone_name))
    print(f"{zone_name}: {local.strftime('%Y-%m-%d %H:%M %Z')}")

The loop converts the same UTC moment into three different local times, one for each viewer, and prints them side by side so you can compare the results directly:

texttext
America/New_York: 2026-12-24 19:00 EST
Europe/London: 2026-12-25 00:00 GMT
Asia/Kolkata: 2026-12-25 05:30 IST

Midnight UTC on Christmas arrives at 7:00 PM on December 24 in New York, midnight in London, and 5:30 AM on December 25 in India. Each user sees the correct local time for the same UTC moment.

For more on formatting datetimes, see Format Dates and Times in Python.

Common mistakes

Mixing naive and aware datetimes. Comparing or subtracting a naive datetime from an aware one raises a TypeError. Always make both datetimes aware before operating on them.

Using pytz instead of zoneinfo. The pytz library was the standard before Python 3.9, but it has confusing behavior with localize() vs astimezone(). For new code, use the built-in zoneinfo module. It is simpler and follows the standard library conventions.

Using fixed offsets instead of IANA zones. A fixed offset like timezone(timedelta(hours=-5)) does not handle daylight saving time. A place that is normally UTC-5 might be UTC-4 in summer. Use ZoneInfo("America/Chicago") instead of a hardcoded offset.

Forgetting that datetime.now() returns a naive datetime. datetime.now() gives local time but without a tzinfo. This datetime cannot be safely converted to other zones. Always pass a time zone: datetime.now(timezone.utc) or datetime.now(ZoneInfo("America/New_York")).

Rune AI

Rune AI

Key Insights

  • Use datetime.timezone.utc for UTC and ZoneInfo(\"zone name\") for IANA time zones.
  • A timezone-aware datetime carries zone information; a naive datetime does not.
  • Store timestamps in UTC and convert to local time zones only when displaying to users.
  • Use .astimezone() to convert a datetime from one time zone to another.
  • Prefer the built-in zoneinfo module over the third-party pytz library for new code.
  • Always attach a time zone when creating datetimes from user input.
RunePowered by Rune AI

Frequently Asked Questions

What does 'timezone-aware' mean in Python?

A timezone-aware datetime knows which time zone it belongs to. A naive datetime has no time zone information. Operations like comparing datetimes from different zones or converting to UTC require aware datetimes.

Should I store dates in UTC or local time?

Store dates in UTC. Convert to the user's local time zone only when displaying. UTC avoids ambiguity from daylight saving time transitions and makes it straightforward to compare timestamps from different locations.

How do I get the user's local time zone?

Python cannot reliably detect the user's time zone from code alone. In a web application, use JavaScript to send the browser's time zone (via `Intl.DateTimeFormat().resolvedOptions().timeZone`). In a desktop or mobile app, use the operating system's time zone setting.

Conclusion

Time zones are a common source of bugs, but Python's zoneinfo module (available since Python 3.9) makes them manageable. Always store UTC, attach time zones to datetimes with ZoneInfo, and convert to local time only at display time. Avoid pytz for new code unless you are maintaining a legacy project.