The UK Clock Went Back. Python's 01:30 Can Now Mean Two Different Instants.
Handle repeated and nonexistent local times with Python zoneinfo. Use the October 2026 UK clock change to distinguish wall-clock labels from UTC instants.
A log contains two events labeled 01:30 in London on 25 October 2026. They need not be duplicates. When the clock moves backward, the same local hour occurs twice, and the wall-clock label alone cannot identify which instant an event represents.
The UK government's clock-change table gives 25 October as the 2026 autumn change and 29 March as the spring change. The clocks move back at 02:00 in October. A London 01:30 therefore occurs once with the summer offset and once with the winter offset.
For data pipelines, scheduling and reconciliation, the important distinction is between a local label and an instant. Python's established zoneinfo module supports the IANA database and the fold attribute for repeated times. Assigning a zone does not, by itself, validate every local input or choose the user's intended occurrence.
Keep the zone name with the local input
“01:30” does not identify a time zone, date or occurrence. “London time” needs an actual zone policy such as Europe/London, rather than a fixed offset copied from last month's data.
A fixed offset can identify an instant when paired with a date and time, but it does not represent a location's changing civil-time rules. A recurring appointment at a local hour and an event recorded at a UTC instant are different data contracts.
For historical events, retain a UTC instant when known, plus the original local representation and relevant source context. For a future local schedule, retain the named zone and the scheduling policy as well.
Recognize three possible input states
A local clock reading may correspond to one instant, two instants during a backward transition, or no instant during a forward transition.
| Local-input state | Meaning | Application decision |
|---|---|---|
| Ordinary | One UTC candidate | Continue under the stated zone policy |
| Repeated | Two UTC candidates | Ask for or apply a documented occurrence choice |
| Missing | No matching UTC candidate | Reject or use an explicitly defined adjustment policy |
Do not silently treat the default fold=0 as evidence that the person intended the earlier occurrence. It is a representation choice, not a record of user intent.
Compare the two autumn instants in UTC
In the London example, 01:30 with the earlier offset corresponds to 00:30 UTC. The later occurrence corresponds to 01:30 UTC. The instants differ by one hour.
Convert to a common instant representation before calculating elapsed time between events. Subtracting two wall-clock values that look the same can obscure the interval the application actually needs.
The local label still matters for explanation. A support record should be able to show why two events have matching clock text while their UTC timestamps differ. Removing the original label can make an accurate reconciliation difficult to explain later.
Test round-trip candidates explicitly
The following original helper tries both fold values, converts each candidate to UTC, and checks whether conversion back recreates the requested local reading. It returns distinct UTC instants rather than choosing one silently.
It expects an already parsed naive datetime and a trusted IANA zone key. It is a teaching helper for ordinary modern civil timestamps, not a complete scheduler, date parser or leap-second system. Python 3.9 or later and an available IANA database are required.
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
def utc_candidates(local_time, zone_key):
if not isinstance(local_time, datetime) or local_time.tzinfo is not None:
raise ValueError("Supply a naive datetime representing a local label")
zone = ZoneInfo(zone_key)
candidates = set()
for fold in (0, 1):
instant = local_time.replace(tzinfo=zone, fold=fold).astimezone(timezone.utc)
returned = instant.astimezone(zone).replace(tzinfo=None)
if returned == local_time:
candidates.add(instant)
return sorted(candidates)
repeated = utc_candidates(datetime(2026, 10, 25, 1, 30), "Europe/London")
assert repeated == [
datetime(2026, 10, 25, 0, 30, tzinfo=timezone.utc),
datetime(2026, 10, 25, 1, 30, tzinfo=timezone.utc),
]
assert (repeated[1] - repeated[0]).total_seconds() == 3600
assert utc_candidates(datetime(2026, 3, 29, 1, 30), "Europe/London") == []
assert len(utc_candidates(datetime(2026, 10, 26, 12), "Europe/London")) == 1
print([instant.isoformat() for instant in repeated])
The round trip is an explicit validation step. Simply constructing a zoned datetime does not provide the same application-level classification.
Choose a policy before accepting ambiguity
For a transaction log, use the source's recorded offset or UTC timestamp when available. If the source supplied only an ambiguous local label, preserve that uncertainty instead of inventing precision.
For a recurring business task, the policy might select the first occurrence, the second, both or neither, depending on the task. Those options have different consequences. A duplicated report and a duplicated financial action are not equivalent operational risks.
Write the choice into the schedule definition and test it. Do not make a hidden library default the only specification of how a consequential task behaves once a year.
Keep a missing hour distinct from an empty measurement
A spring-forward local reading that never occurred is different from a valid instant at which a sensor failed to report. They require different repairs.
Changing a missing local timestamp to the next available hour is an application policy that changes the label. It may be appropriate for some reminders and inappropriate for a historical event record. Preserve the original input and the reason for any adjustment.
Likewise, do not fill a nonexistent local hour with a zero observation merely to make a chart's rows look complete. The time-axis decision should be documented separately from missing-value handling.
Record the rules used by the pipeline
Time-zone rules can change, and systems may use different database versions. Record enough environment information to reproduce material conversions, especially when a pipeline exchanges future local schedules between systems.
The Python documentation explains the use of system time-zone data and the first-party tzdata package as a fallback. A deployment without either can fail to load the named zone. Test the actual runtime rather than assuming that a successful developer-machine conversion proves production readiness.
Test transitions and ordinary dates together
Include the repeated London hour, the missing spring hour, an ordinary London date and a zone without the same seasonal transition. Also reject an already-aware input when the helper's contract requires a naive local label.
Test the intended scheduling behavior in addition to the conversion function. Producing two candidates correctly does not establish that downstream code avoids executing the same business effect twice.
For related foundations, see Python time zones and idempotent payment effects. The common lesson is to keep identity explicit: a familiar label is not always a unique event.
Sources and limits
- GOV.UK: clock-change dates, checked 26 October 2026; the 2026 table identifies 29 March and 25 October.
- Python zoneinfo reference, checked 26 October 2026.
The helper classifies UTC candidates under the installed rules. It does not infer a person's intended occurrence or implement a complete recurrence policy.