Python Atlas
Standard library & async

Datetime and timezone correctness

Model instants, local times, durations, and daylight-saving transitions correctly.

Time bugs usually come from confusing an instant on the global timeline with a local wall time. Python calls a datetime without tzinfo naive and one with tzinfo aware.

Use aware values for instants

from datetime import datetime, timezone

created_at = datetime.now(timezone.utc)
serialized = created_at.isoformat()
restored = datetime.fromisoformat(serialized)

assert restored == created_at
assert restored.tzinfo is not None

datetime.utcnow() returns a naive value and is deprecated in modern Python. Use datetime.now(timezone.utc). Store instants in UTC, but retain a user's timezone identifier when future local scheduling matters.

Convert with zoneinfo

zoneinfo.ZoneInfo uses IANA timezone data and applies historical daylight-saving rules.

from datetime import datetime, timezone
from zoneinfo import ZoneInfo

instant = datetime(2026, 11, 1, 5, 30, tzinfo=timezone.utc)
new_york = instant.astimezone(ZoneInfo("America/New_York"))
print(new_york.isoformat())

Use geographic names such as America/New_York, not abbreviations such as EST: abbreviations are ambiguous and fixed offsets do not model daylight-saving changes.

On systems without timezone data, applications may need the separately installed tzdata package. That package is not part of the standard library.

Ambiguous and nonexistent wall times

When clocks move backward, one local time occurs twice. PEP 495's fold distinguishes the two:

from datetime import datetime
from zoneinfo import ZoneInfo

zone = ZoneInfo("America/New_York")
first = datetime(2026, 11, 1, 1, 30, tzinfo=zone, fold=0)
second = datetime(2026, 11, 1, 1, 30, tzinfo=zone, fold=1)

assert first.timestamp() != second.timestamp()

When clocks move forward, some local times never occur. Constructing a datetime does not guarantee that a wall time exists. User-facing schedulers need an explicit policy: reject, shift forward, or ask the user. Convert through UTC and round-trip to detect problematic values.

Parse and format

Prefer ISO 8601 for machine exchange:

from datetime import datetime

value = datetime.fromisoformat("2026-07-19T17:30:00+00:00")
text = value.isoformat(timespec="seconds")

Use strptime() only when an external format requires it. Parsing %Z timezone abbreviations is not portable. A trailing Z denotes UTC and is accepted by current fromisoformat() versions.

Durations and calendar arithmetic

timedelta represents a fixed elapsed duration:

from datetime import datetime, timedelta, timezone

expires_at = datetime.now(timezone.utc) + timedelta(minutes=15)

It does not represent "one calendar month." Month arithmetic needs a domain rule because months have different lengths. Also distinguish:

  • elapsed-time deadlines, best measured with time.monotonic();
  • civil timestamps, represented with aware datetime;
  • CPU time, available from time.process_time().

System clock adjustments can make time.time() jump. Use monotonic clocks for timeout math:

import time

deadline = time.monotonic() + 2.0
remaining = max(0.0, deadline - time.monotonic())

Timestamps and precision

datetime.timestamp() returns POSIX seconds as a float and may lose sub-microsecond precision. datetime.fromtimestamp(value, tz=timezone.utc) creates an aware value. Avoid converting naive local values to timestamps because the host's timezone silently affects the result.

Common mistakes

  • Comparing naive and aware values (Python rejects ordering).
  • Replacing tzinfo to convert zones. dt.replace(tzinfo=...) labels wall fields; dt.astimezone(...) converts an instant.
  • Storing only a UTC offset for a recurring local event.
  • Using local wall-clock time to measure elapsed work.
  • Assuming every day is exactly 24 local hours.

Practice

Implement a meeting formatter that accepts an aware UTC instant and an IANA timezone name. Add tests around both daylight-saving transitions. Then design validation for a user-entered future local time and document your policy for ambiguous and nonexistent values.

LEARNING RECORD

Finish this lesson

Mark it complete when you can explain the main decision without looking.

KNOWLEDGE CHECK

Datetime and timezone correctness

1What should be stored for a weekly 09:00 meeting that must continue following the organizer's daylight-saving rules?
2A retry loop must stop after two elapsed seconds even if NTP adjusts the system clock. Which clock should calculate its deadline?