How to stop an automation that still has work to do
Retire intake, accepted jobs and external authority in a deliberate order, so a late event cannot revive an obsolete process.
Four London clock examples turn a vague daily schedule into an explicit policy for ordinary, repeated and missing local times.
“At nine” is often a perfectly adequate agreement between colleagues who share an office clock. The server has not necessarily joined that agreement. A fixed nine UTC job follows London’s nine on some dates and misses it on others. Before configuring the scheduler, decide which promise the report makes: follow local civil time in a named place, or run after a fixed elapsed interval. Both make sense, but the people waiting for the report will notice if the implementation chooses the other one.
Take a daily report intended for 09:00 in Europe/London. In the worked historical examples, 15 January 2024 maps to 09:00 UTC and 15 July maps to 08:00 UTC. A fixed 09:00 UTC job would therefore arrive at 10:00 London time on that July date. Both schedules are coherent; they fulfil different promises. Writing the named zone beside the local time preserves the intention that a single stored offset would lose across the year.
The four mappings below use Python’s zoneinfo with timezone rules version 2026a. For this report, London belongs to the schedule, rather than to whoever opens it. A colleague travelling elsewhere might display delivery in another zone; that need not move the office’s daily job. Keep generating the intended London occurrence and present its resolved instant as needed. Otherwise a personal display preference can quietly become a change to the agreement about when the report should arrive.
The more revealing example is 01:30 on 27 October 2024 in London. The fixture finds two UTC instants: 00:30 and 01:30 on that date. They are an hour apart, despite sharing the same local date and time. A report scheduled for that wall-clock reading could be intended once, at the earlier occurrence, once at the later occurrence, or at both. The word daily alone does not settle which interpretation is appropriate.
Python represents an ambiguous local time using fold: zero identifies the earlier reading and one the later reading.[1] Zoneinfo applies timezone rules to supply their corresponding offsets.[2] These mechanisms expose the alternatives; the application must choose which occurrence its schedule intends. A constructor's default may select the desired one, but relying on it leaves the intended behaviour undocumented and makes an apparently harmless implementation change harder to assess.
For an overnight summary, a team might choose once per local date and the earlier occurrence of a repeated wall time. Another workflow may need both physical intervals. The person examining an incident log needs to know which rule applies and which UTC instant was selected. Two entries both saying 01:30 are not much help on the night when both are correct. Including the offset or resolved instant preserves the sequence without asking support to supply the missing interpretation from memory.
The report's business period is a separate identity. A run at the later occurrence could still refer to the same local reporting date as a run at the earlier one. If the policy allows both, their occurrence identities must distinguish them; if it allows one, an accidental second trigger must not become another promised daily report merely because it has a different UTC timestamp. Resolving civil time answers when an occurrence is possible. It does not by itself define what business work that occurrence represents.
The fixture also examines 01:30 on 31 March 2024 in London and finds no matching instant. Assigning zone information to that naive datetime does not reliably reject it. PEP 495 explicitly describes constructors and replacement operations that permit missing local times.[1] A programme can therefore hold an object that prints like a plausible zoned time without having established that the civil clock ever displayed that reading. The presence of an offset should not be mistaken for successful validation.
To find the alternatives, try both fold values, convert each candidate to UTC and back, retain matching local fields and deduplicate the resulting instants. Against the recorded timezone data, the checks yield one ordinary instant, two autumn instants and no spring-gap instant. A scheduler must still choose what to do with each outcome. If it silently shifts a missing time while another component rejects it, sharing a plausible datetime object will not make their schedules agree. The round trip exposes the choice so the schedule can name it.
A missing occurrence requires a different policy from an ambiguous one. Skip that date, move to the first valid time afterwards, or preserve the displacement by moving the requested time forward through the gap are distinct choices. For this London example, first valid afterwards and preserving the half-hour displacement would not name the same local reading. A vague setting called shift forward leaves that difference unresolved. Describe the resulting clock time in the requirement so a non-specialist can judge whether the report would still be useful.
A job meant for 09:00 avoids these particular London 01:30 transitions, but that is a property of the chosen zone and dates, not a universal guarantee about morning schedules. The named timezone database supplies rules; the application supplies policy for the outcomes those rules permit. Choosing a convenient time can reduce operational awkwardness, while explicit handling prevents a future configuration change from inheriting an accidental rule that nobody remembers approving.
Executed calculations for Europe/London using installed timezone data version 2026a.
Scroll sideways to see all columns.
| London local reading | UTC instant(s) | Count | Decision required |
|---|---|---|---|
| 15 Jan 2024, 09:00 | 15 Jan, 09:00Z | 1 | Ordinary occurrence |
| 15 Jul 2024, 09:00 | 15 Jul, 08:00Z | 1 | Civil nine differs from UTC nine |
| 27 Oct 2024, 01:30 | 27 Oct, 00:30Z and 01:30Z | 2 | Choose earlier, later or both |
| 31 Mar 2024, 01:30 | None | 0 | Choose explicit gap policy |
tzinfo alone does not validate a missing local time. Adjacent 09:00 runs span 23 hours in spring and 25 in autumn.The second calculation compares successive London 09:00 occurrences around the same 2024 transitions. From 30 to 31 March, the UTC interval is twenty-three hours. From 26 to 27 October, it is twenty-five. The local appointment remains nine on each date. This is the clearest way to separate a civil schedule from running again after 86,400 elapsed seconds. Neither is a more accurate clock; each is accurate to a different intention.
The report’s contents inherit this distinction. A day defined by local boundaries can have a different elapsed length during a clock change. Derive those boundaries in the relevant zone, then compare the resulting instants consistently, rather than add twenty-four hours to yesterday’s UTC boundary and assume the result is local midnight. Endpoints matter as well: an inclusive end can put the same boundary record in two adjacent reports. A half-open period convention gives that record one home, but remains an application decision to record beside the schedule.
Elapsed intervals remain appropriate for work such as polling a technical dependency every few minutes or delaying a retry. That work need not realign itself to an office clock. An appointment described as every Tuesday at nine usually requires a civil recurrence instead. These are different scheduling objects, so establishing the intended recurrence is necessary before choosing how to store and resolve it. A universal storage rule cannot settle that requirement.
Europe/London transition examples calculated with the fixture’s timezone data.
Scroll sideways to see all columns.
| Local recurrence | Elapsed UTC interval | Meaning |
|---|---|---|
| 09:00, 30 to 31 March 2024 | 23 hours | Both occurrences remain at local nine. |
| 09:00, 26 to 27 October 2024 | 25 hours | Both occurrences remain at local nine. |
| Repeat after 86,400 seconds | 24 hours | A fixed elapsed interval expresses another schedule. |
A useful schedule record keeps the local time, named zone, recurrence and fold/gap policy. A useful occurrence record keeps the intended local date, resolved instant and the policy decision used. Those records serve different purposes: one describes what to generate; the other explains a particular result. The worker should not have to reconstruct last month's intention from the server's current local timezone setting or a log line that says only 01:30.
Timezone data is another dependency. Python documents that zoneinfo obtains rules from system data or the first-party tzdata package, and that environments can differ.[2] Keep deployments supplied with the needed zone data and make its version identifiable when exact reproduction matters. A rule update affecting future schedules needs a decision about which pending occurrences to recalculate. Completed business records should retain their actual resolved instants rather than being silently rewritten to match a new interpretation of the schedule.
Support should be able to explain a report using its intended local date and actual resolved instant. That takes a named zone, a choice for repeated times, a rule for missing times and a definition of the business period. Catching up a missed run is another decision again; converting a clock reading cannot settle it. Once those choices are recorded, a delayed or repeated report becomes something the team can account for, instead of a debate about what everyone thought nine meant when the schedule was first agreed.
Working on a workflow like this? Hamsa helps finance and operations teams connect their tools and handle the exceptions, starting with one workflow.
See the Payment Operations PilotPrimary sources behind this article. Living documentation may change.

Practical engineering. A human perspective.
More about Hamsa Software and the person behind it. Editorial notes.