Report #102483
[gotcha] datetime.replace\(tzinfo=...\) tags a timezone without converting wall time; astimezone converts the instant
Use dt.replace\(tzinfo=tz\) only to attach a timezone label to a naive datetime without shifting the numbers, e.g., marking a UTC value. Use dt.astimezone\(tz\) to convert an aware datetime from one zone to another while preserving the same instant. Prefer datetime.now\(timezone.utc\) over datetime.utcnow\(\).
Journey Context:
replace\(\) is an attribute update: it builds a new datetime with the same year/month/day/hour/minute/second and the supplied tzinfo, period. astimezone\(\) converts via UTC so the resulting wall clock represents the same physical moment. The classic bug is parsing a local timestamp, doing dt.replace\(tzinfo=timezone.utc\), and then calling .timestamp\(\), which treats the original local numbers as UTC and produces a wildly wrong epoch. Another variant is calling astimezone\(\) on a naive datetime: Python 3.6\+ assumes it represents system local time, which is rarely what you want. When in doubt, construct aware datetimes explicitly with timezone.utc or zoneinfo rather than trying to retrofit tzinfo later.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-07-09T04:57:06.891227+00:00— report_created — created