The job moved but the worker was on time
A report expected at 9 a.m. appears at 10 a.m. after the spring clock change. That symptom can come from a perfectly punctual worker. If the schedule advances by 24 elapsed hours, its next occurrence may move relative to the customer's wall clock.
The first check belongs before the queue. Compare the intended local occurrence with the scheduled instant in UTC, then compare that instant with actual execution. A wrong instant points toward schedule semantics or time-zone conversion. A correct instant with a late execution points toward delivery or worker delay. Increasing worker capacity will not repair the first case.
This article reconstructs those clocks with a local Python fixture. It is not an account of a customer incident. The dates and time zone are explicit so the result can be inspected: America/New_York around the 2026 spring and fall transitions, using the time-zone data installed for the recorded run.
Reconstruct the two clocks
At 9 a.m. on March 7, the local offset is five hours behind UTC. At 9 a.m. on March 8, it is four hours behind. The same local appointment is separated by 23 elapsed hours across that boundary. A schedule that instead adds 24 hours to the earlier UTC instant reaches 10 a.m. locally on March 8.
| Local date | Daily local 9 a.m. in UTC | Fixed 24-hour cadence, local time |
|---|---|---|
| March 7, 2026 | 14:00 UTC | 09:00, offset -05:00 |
| March 8, 2026 | 13:00 UTC | 10:00, offset -04:00 |
| March 9, 2026 | 13:00 UTC | 10:00, offset -04:00 |
Run the clock reconstruction with Python 3.9 or later and an available IANA time-zone database. The saved result records the Python version and the hash of the time-zone file used in this run. The schedule CSV contains the complete three-day comparison.
The elapsed interval is calculated after both local appointments are converted to UTC. That detail matters when checking the example: local calendar arithmetic and elapsed-time arithmetic answer different questions. Start with the customer's intended rule, then select the arithmetic that expresses it.
Python's zoneinfo documentation explains the use of IANA zones and the fold attribute for ambiguous local times. The fixture uses those facilities to inspect candidate instants. It does not implement queue delivery, distributed locking or a general cron parser.
An occurrence can be missing or ambiguous
The spring problem is sharper at 2:30 a.m. On March 8 in this zone, that local clock reading does not occur. The fixture tries both fold values, converts each candidate to UTC and back, and keeps only candidates that reproduce the requested local reading. It finds none.
November 1 has the opposite problem. Local 1:30 a.m. maps to two instants: 05:30 UTC and 06:30 UTC. The same round-trip check retains both. Neither a date string nor a numeric UTC offset chosen months earlier is enough to express the intended recurring rule across all future transitions.
Those results identify a policy decision. A nonexistent appointment could be skipped, moved to the next valid local time or handled by a business-specific exception. An ambiguous appointment could mean the first occurrence, the second or both. The product must choose. A library's ability to construct a datetime object is not evidence that the requested appointment exists exactly once.
EventBridge Scheduler's documented behavior provides one concrete example: a cron occurrence in the spring gap is skipped, and a repeated fall time invokes once. Its rate-based day represents a 24-hour duration. These are that service's rules, not a portable promise about every scheduler. Verify the semantics of the scheduler actually configured for the application.
Separate schedule intent from delivery
Capture four values for a disputed run: the requested business rule, the time zone, the intended occurrence and the actual execution time. Add the schedule revision so an edited rule is distinguishable from a repeated delivery of the old one. These fields provide a causal sequence that an ordinary worker timestamp cannot reconstruct alone.
The diagnostic worksheet begins with three questions. Did the local appointment exist under the selected policy? Did the scheduler choose the corresponding UTC instant? Did delivery and execution happen within the allowed delay? Each answer leads to a different owner and a different test.
If two executions refer to the same intended occurrence, investigate delivery identity before blaming the fall transition. A repeated wall-clock reading can create two distinct intended occurrences only when the schedule policy allows both. Conversely, two different intended instants are not duplicate delivery merely because their formatted local labels match.
Use a durable occurrence identifier whose meaning includes the schedule revision and the chosen instant, with any business-date constraint defined separately. A policy requiring one customer report per business date may intentionally reject a second occurrence even if the scheduler considers it distinct. That is an application rule and should be visible in the acceptance test.
The background-job fencing example addresses a later boundary: whether an expired worker may still commit. Fencing does not decide whether a spring-gap appointment should have existed. Keep those checks separate so one passing test does not conceal the other problem.
Make the policy visible to the customer
Show the selected time zone and the next scheduled occurrences when a customer saves a recurring rule. For a business-local appointment, a preview around the next transition makes a surprising skip or shift reviewable before it affects delivery. For an elapsed cadence, describe the interval in elapsed units and avoid promising a fixed local hour.
An existing schedule also needs a rule for time-zone database changes. Record which future occurrences are recalculated and how customers learn of a material change. The fixture hash supports reproducibility of this article's run; it does not freeze future civil-time rules or recommend withholding time-zone updates.
To verify a fix, repeat the same boundary dates with the intended policy and inspect the generated occurrence identities. Then inject a delivery delay and a repeated delivery separately. The schedule test should still select the right instant, while the execution tests should expose their own timing and identity outcomes. Keep the requested local appointment in the evidence beside the UTC timestamp.
Sources
Documentation checked .
