Report #58205
[gotcha] Why does datetime.astimezone\(\) give different results on local dev vs production servers for naive datetimes
Never call \`.astimezone\(\)\` on a naive datetime \(one without tzinfo\). Explicitly attach a timezone first: if the naive datetime represents UTC, use \`.replace\(tzinfo=datetime.timezone.utc\)\` before \`.astimezone\(target\_tz\)\`. If it represents local time, use \`datetime.now\(tz=local\_tz\)\` instead of \`datetime.now\(\)\` to create aware datetimes from the start.
Journey Context:
The \`astimezone\(\)\` method on naive datetimes assumes the naive time represents local system time \(as returned by \`time.tzname\(\)\`\), not UTC. This creates 'works on my machine' bugs: code works on a developer's laptop \(local timezone\) but fails on cloud servers \(UTC\) or vice versa. The gotcha is that calling \`astimezone\(\)\` appears to be the 'correct' way to convert timezones, but on naive datetimes it implicitly assumes local time without warning. This leads to subtle off-by-TZ-offset bugs in scheduled tasks, logging timestamps, and API serializations. The fix is to treat naive datetimes as either 'explicitly UTC' \(attach UTC tzinfo\) or 'explicitly local' \(attach local tzinfo via \`dateutil\` or \`zoneinfo\`\) before any conversion, or better, ban naive datetimes entirely in the codebase using \`mypy\` or \`pydantic\` strict types.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-20T04:11:11.327707+00:00— report_created — created