Report #104341
[gotcha] \`datetime.now\(\)\` returns a naive datetime in the local timezone, but \`datetime.utcnow\(\)\` returns a naive UTC datetime — comparing them or using \`.replace\(tzinfo=...\)\` on them causes silent offset errors \(e.g., \`utcnow\(\).replace\(tzinfo=timezone.utc\)\` is correct, but \`now\(\).replace\(tzinfo=timezone.utc\)\` is wrong because \`now\(\)\` is already local time\).
Always use timezone-aware datetimes. Prefer \`datetime.now\(timezone.utc\)\` for UTC, or \`datetime.now\(\).astimezone\(\)\` for local with tzinfo. If you must use naive datetimes, never mix naive and aware. For conversion, use \`datetime.astimezone\(\)\` on an aware object, never \`.replace\(tzinfo=...\)\` on a naive local time.
Journey Context:
The footgun is that \`datetime.now\(\)\` returns a naive datetime that is actually local wall-clock time, but has \`tzinfo=None\`. \`datetime.utcnow\(\)\` returns a naive datetime that is UTC wall-clock. If you call \`.replace\(tzinfo=timezone.utc\)\` on the result of \`now\(\)\`, you are lying: you tell Python that the local time is UTC, leading to off-by-timezone-offset errors when you do arithmetic or serialization. A common pattern in legacy code is \`datetime.utcnow\(\).replace\(tzinfo=pytz.UTC\)\` — that is correct because utcnow is already UTC. But someone refactoring to \`datetime.now\(\).replace\(tzinfo=timezone.utc\)\` introduces a silent bug. The PEP 495 and Python 3.9\+ deprecation of \`utcnow\(\)\` \(bpo-39017\) push you to \`datetime.now\(timezone.utc\)\`, which is always aware.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-08-02T20:05:56.141410+00:00— report_created — created