Sets, dict keys and sorting of aware datetimes behave surprisingly around DST unless you know the rules.
datetime defines two kinds of operations between aware values:
- Intra-zone: both operands share the same tzinfo object. Comparison and subtraction then use the naive wall-clock fields and ignore
utcoffset()andfold. So the two occurrences of 1:30 on a fall-back night compare equal, and subtracting across a DST change gives wall-clock differences. - Inter-zone: different tzinfo objects. Both sides are converted to UTC first, so the result reflects real elapsed time.
Same zone name isn't enough; it has to be the same object. ZoneInfo caches instances, so ZoneInfo("Europe/Berlin") usually yields the same object, but a ZoneInfo.no_cache(...) instance or a different tzinfo implementation flips the behaviour.
PEP 495 adds an exception for inter-zone comparison: if either operand is in a gap or fold where fold would change its offset, == returns False (a "different instant" guarantee), which makes equality non-transitive in rare cases.
Hashing uses the UTC value for aware datetimes, with fold ignored, so datetimes that are equal hash equally. Naive and aware values never compare equal, so a dict can hold both datetime(2025,1,1) and its aware twin as separate keys.
The practical rule: convert to UTC before comparing, sorting or deduplicating instants.
Example
from datetime import UTC, datetime
from zoneinfo import ZoneInfo
ny = ZoneInfo("America/New_York")
first = datetime(2025, 11, 2, 1, 30, tzinfo=ny)
second = first.replace(fold=1) # one hour later in reality
print(first == second) # True: intra-zone ignores fold
print(second - first) # 0:00:00
u1, u2 = first.astimezone(UTC), second.astimezone(UTC)
print(u1 == u2, u2 - u1) # False 1:00:00
other = ZoneInfo.no_cache("America/New_York") # same rules, new object
print(first == second.replace(tzinfo=other)) # False: inter-zone, PEP 495
print(len({first, second})) # 1
naive = datetime(2025, 1, 1)
print(len({naive, naive.replace(tzinfo=UTC)})) # 2Remember
- Same tzinfo object: wall-clock comparison and subtraction (fold ignored)
- Different tzinfo objects: both sides converted to UTC first
- Convert to UTC before comparing, sorting or deduplicating instants
Watch out for
- Deduplicating events by aware local datetimes, which merges the two different 1:30s on a fall-back night.