datetime.astimezone
The converter. It moves the value to UTC using its own offset, then lets tz.fromutc() compute the new local fields — so the result compares equal to the input. On a naive value, or with no tz, the machine's local zone gets involved.
Demo
from datetime import datetime, timedelta, timezone dt = datetime.fromisoformat('2026-09-29T20:00Z') if dt.tzinfo is None: raise ValueError('give the timestamp an offset, e.g. Z or +02:00') local = dt.astimezone(timezone(timedelta(hours=9))) (local.isoformat(), local == dt)
Converting can change the date: 20:00 UTC is already 05:00 the next day at +09:00. The == check is True every time because aware datetimes compare by instant. Naive input is stopped by the template: CPython would treat it as the machine's local time, which a demo cannot know.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| tz | tzinfo | None | no (None) | Target zone: UTC, timezone(offset), ZoneInfo(...). None means the system local zone (a fixed-offset timezone for that moment). |
Return value
datetime — An aware datetime equal to dt (same instant) whose tzinfo is tz.
Common patterns
from datetime import datetime, UTC from zoneinfo import ZoneInfo stored = datetime.now(UTC) shown = stored.astimezone(ZoneInfo('Europe/Paris'))
from datetime import UTC normalized = [dt.astimezone(UTC) for dt in parsed]
from datetime import UTC local = naive_utc.replace(tzinfo=UTC).astimezone(target_tz)
Examples
Pitfalls
from datetime import datetime, timedelta, timezone, UTC datetime(2026, 9, 29, 12, tzinfo=UTC).astimezone(timezone(timedelta(hours=2))).hour
from datetime import datetime, timedelta, timezone, UTC datetime(2026, 9, 29, 12, tzinfo=UTC).replace(tzinfo=timezone(timedelta(hours=2))).hour
from datetime import datetime naive = datetime(2026, 9, 29, 12) naive.tzinfo is None
from datetime import datetime, timedelta, timezone, UTC datetime(2026, 9, 29, 12).replace(tzinfo=UTC).astimezone(timezone(timedelta(hours=2))).isoformat()
When to use
- Showing a stored UTC value in a user's zone
- Normalizing aware values from different offsets
- Labelling naive values → replace(tzinfo=…)
- Server code with astimezone() and no argument — the result depends on the machine's zone setting
Notes
FAQ
Make sure it is aware, then call dt.astimezone(target). For regions use zoneinfo: dt.astimezone(ZoneInfo("America/New_York")); for fixed offsets timezone(timedelta(hours=...)).