A named-zone local clock reading can identify one UTC instant, two instants during a backward transition, or no instant during a forward transition.
Python ZoneInfo: classify unique, repeated and missing local times
Operation contract
The classifier accepts a naive datetime and an existing ZoneInfo object. It builds both fold candidates, converts each to UTC and back, and retains only candidates whose round-trip reproduces the same local fields. UTC instants are deduplicated because a normal time gives the same instant for both folds. The New York fixtures show one ordinary time, the repeated 01:30 autumn reading, and the missing 02:30 spring reading. No candidate is selected automatically.
Failure and ownership boundary
Attaching tzinfo does not itself reject a nonexistent local time. Returning two candidates lets the application ask for an offset or a deliberate earlier/later choice. Returning none requires rejection or a separately declared adjustment policy. Zone rules depend on the installed IANA data. Python zoneinfo and fold: two instants can share one clock label and Python datetime: require an offset before comparing timestamps provide the next step after classification.
Working program
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
def local_candidates(local, zone):
if type(local) is not datetime or local.tzinfo is not None or not isinstance(zone, ZoneInfo):
raise ValueError("naive datetime and named zone required")
candidates = set()
for fold in (0, 1):
instant = local.replace(tzinfo=zone, fold=fold).astimezone(timezone.utc)
restored = instant.astimezone(zone).replace(tzinfo=None)
if restored == local:
candidates.add(instant)
return sorted(candidates)
zone = ZoneInfo("America/New_York")
for local in (datetime(2024, 2, 1, 12), datetime(2024, 11, 3, 1, 30),
datetime(2024, 3, 10, 2, 30)):
print("candidates:", len(local_candidates(local, zone)))
print("repeated UTC:", [value.strftime("%H:%M") for value in
local_candidates(datetime(2024, 11, 3, 1, 30), zone)])Output
candidates: 1
candidates: 2
candidates: 0
repeated UTC: ['05:30', '06:30']Costs and limits
Two conversion paths give bounded application work per reading, plus zone transition lookup. This is classification for the installed data, not a promise about future government timezone changes. Persist the chosen instant and, when relevant, the original zone and selection policy.
Common Mistakes
- Do not treat replace(tzinfo=zone) as validation of a local reading.
- Do not silently resolve a repeated reading by whichever fold happens to be the default.
Connected lessons
Python zoneinfo and fold: two instants can share one clock label, Python datetime: require an offset before comparing timestamps, Java daylight-saving transitions: reject gaps and choose overlaps.
