datetime, zoneinfo & time
Store instants in UTC with timezone-aware datetime. Convert for display with zoneinfo (tzdata on Windows via tzdata package). Use timedelta for durations, not integer seconds alone.
Search across all documentation pages
Store instants in UTC with timezone-aware datetime. Convert for display with zoneinfo (tzdata on Windows via tzdata package). Use timedelta for durations, not integer seconds alone.
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
utc_now = datetime.now(timezone.utc)
local = utc_now.astimezone(ZoneInfo("America/New_York"))
print(utc_now.isoformat(), local.isoformat())When to reach for this:
monotonic for perf)from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo
def next_billing(from_utc: datetime, days: int) -> datetime:
if from_utc.tzinfo is None:
raise ValueError("aware datetime required")
return from_utc + timedelta(days=days)
def format_for_user(instant_utc: datetime, tz_name: str) -> str:
tz = ZoneInfo(tz_name)
return instant_utc.astimezone(tz).strftime("%Y-%m-%d %H:%M %Z")
start = datetime(2026, 7, 9, 12, 0, tzinfo=timezone.utc)
due = next_billing(start, 30)
print(format_for_user(due, "Europe/Berlin"))What this demonstrates:
timedelta preserves awarenessZoneInfo IANA names for correct DST| Type | Safe for |
|---|---|
| Aware (tz set) | Storage, APIs |
| Naive | Local wall clock only with care |
time.perf_counter() for benchmarksdatetime.now for durationsdatetime.now(timezone.utc).tzdata PyPI. Fix: document in deployment.%z in APIs.| Alternative | Use When | Don't Use When |
|---|---|---|
| dateutil | Complex recurrence | Simple timedelta enough |
| pendulum | Friendly API preference | Stdlib-only policy |
| epoch int in DB | Legacy schema | Greenfield APIs |
Yes for distributed systems - convert at UI edge.
Parses many ISO-8601 forms in 3.11+ - prefer for JSON timestamps.
Use fixed aware datetimes in tests - not now().
perf_counter monotonic for durations; time.time wall clock subject to NTP jumps.
Store timezone-aware or UTC normalized - match DB column type.
Python datetime ignores leap seconds - same as most systems; use UTC.
zoneinfo is preferred in 3.9+ new code.
email.utils.parsedate_to_datetime for RFC 7231 headers.
Use aware next-run calculator or APScheduler - watch DST for local time triggers.
Coerces ISO strings; enforce aware with validators in v2.
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.
Reviewed by Chris St. John·Last updated Jul 19, 2026