runtime
102 / 250
lesson 102 Dates Numbers & Regex · intermediate

Time zones and UTC

`18:35` means nothing until you know where. An aware datetime carries its time zone, and `astimezone` shows the same moment on another zone's clock, daylight saving included.

You'll be able to
  • Tell a naive datetime (no zone) from an aware one, and why servers store UTC
  • Convert a UTC time to a local zone with astimezone(ZoneInfo("America/New_York"))
  • Attach the right zone to a local clock time with replace(tzinfo=...), and see daylight saving change the offset
The idea

A shop in New York and a warehouse in Tokyo look at the same order. "Placed at 19:02" is true on one clock and false on the other. A datetime with no zone is naive: it's a clock reading with no place attached. An aware datetime carries a time zone, so it pins down one moment.

Servers and logs record moments in UTC, which has no daylight saving and no ambiguity: timezone.utc from datetime. To show a moment to people, convert it to their zone with moment.astimezone(zone). Real zones come from zoneinfo: ZoneInfo("America/New_York") knows the zone's whole history of offsets, including when daylight saving starts and ends.

Run each example to see what it prints. Running examples doesn't count toward progress.

example-1.py · One moment on three clocks
from datetime import datetime, timezone
from zoneinfo import ZoneInfo

created = datetime(2025, 3, 3, 0, 2, 37, tzinfo=timezone.utc)
print(created)
print(created.astimezone(ZoneInfo("America/New_York")))
print(created.astimezone(ZoneInfo("Asia/Tokyo")))

Daylight saving is why you never hard-code an offset. New York is 5 hours behind UTC in winter and 4 in summer; ZoneInfo picks the right one for each date. This function shows a UTC log time on a local clock. datetime.fromisoformat reads ISO text, and the Z at the end of 2025-01-15T14:00:00Z means UTC.

example-2.py · The same UTC hour in winter and summer
from datetime import datetime
from zoneinfo import ZoneInfo

def local_clock(utc_text, zone_name):
    moment = datetime.fromisoformat(utc_text)
    return moment.astimezone(ZoneInfo(zone_name)).strftime("%Y-%m-%d %H:%M %Z")

print(local_clock("2025-01-15T14:00:00Z", "America/New_York"))
print(local_clock("2025-07-15T14:00:00Z", "America/New_York"))
print(local_clock("2025-07-15T14:00:00Z", "Europe/London"))
example-3.py · Comparing a naive time with an aware one
from datetime import datetime, timezone

cutoff = datetime(2025, 3, 3, 17, 0)
placed = datetime(2025, 3, 3, 21, 30, tzinfo=timezone.utc)
print(placed > cutoff)

To go the other way (a local clock time someone typed, to UTC), attach the zone with replace(tzinfo=ZoneInfo(...)), which keeps the clock reading and says where it is, then convert with astimezone(timezone.utc). replace is only right for a naive time you know is in that zone; on an aware time it would change the moment.

When to use it: store and compare in UTC, convert to local only to display or to group by a local calendar day. Mistakes to avoid: mixing naive and aware values, hard-coding offsets like -5, and calling astimezone on a naive time, which silently assumes the computer's own zone.

Review
  • A naive datetime has no zone; an aware one carries timezone.utc or a ZoneInfo(...) zone.
  • astimezone(zone) shows the same moment on another clock; replace(tzinfo=zone) attaches a zone to a naive clock reading.
  • Store and compare in UTC, convert to local to display; ZoneInfo handles daylight saving.
  • Next: how far apart two moments are, with timedelta.
Practise this

Loading your progress…

To master this lesson, solve both challenges without hints or the answer, and pass the concept check.

Time zones and UTC — Dates Numbers & Regex · Runtime