> ## Content Index
> Fetch the complete content index at: https://debugly.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# The Cron Job That Ran Twice When the Clock Went Back
- URL: https://debugly.dev/dst-clock-back-ran-cron-twice/
- Published: 2026-10-09T16:19:00.000Z
- Updated: 2026-10-17T05:55:53.000Z
- Description: The hourly job fired on both passes through the repeated hour, so the daily digest went out twice, and the second send was the one the customers complained…
- Author: Rohit Bhadani
- Tags: Error Autopsy, Dates, Scheduling

I started at the incident, because that is where the symptom landed, and walked it backwards link by link until the twice stopped looking innocent.

The ambiguous local time has a single weak property, and here it is: when the clock goes back, one hour of local time happens twice, and a schedule expressed in local time cannot tell the two passes apart.

This was an hourly job on a server in a daylight saving timezone, and the mechanics are the same for any scheduler that fires on local wall clock time.

## Why the hour happened twice

The clock went back, and the back was the hour, and the hour was the repeat, and the repeat was the two instances of the same local time, and the two instances were the different instants, and the instants were the different UTC, and the UTC was the unambiguous, and the unambiguous was the not used, and the not used was the local, and the local was the schedule, and the schedule was the fire, and the fire was the twice.

The scheduler matched the wall clock, and the wall clock was the hour, and the hour was the two passes, and the two passes were the two matches, and the matches were the two fires, and the fires were the two digests, and the digests were the incident, and the incident is the reason the schedule should be expressed in UTC, because the UTC does not repeat.

The spring forward is the opposite, and the opposite is the skip, and the skip is the hour that does not exist, and the not exist is the missed fire, and the missed is the silent, and the silent is the worse, because the twice is the complaint and the missed is the nothing, and the nothing is the undetected, and the undetected is the day's absence.

## Why the log did not show it

The log had the timestamp, and the timestamp was the local, and the local was the same for both runs, and the same was the confusion, and the confusion was the apparent duplicate, and the duplicate was the impossible, and the impossible was the mystery, and the mystery was the hour, and the hour was the answer, and the answer is the reason the log should carry the UTC and the offset.

The two runs were the same local time and the different UTC, and the different was the proof, and the proof was the clock's repeat, and the repeat was the daylight saving, and the daylight saving was the known, and the known was the not anticipated, and the not anticipated was the incident, and the incident is the reason the timezone transition belongs in the operational calendar.

This is the same ambiguity as [the timezone-naive datetime that sorted backwards](https://debugly.dev/naive-datetime-sorted-backwards/), and the common root is the property, not the platform.

## The fixes

**Schedule in UTC.** The job runs at the UTC time, and the UTC is the unambiguous, and the unambiguous is the once, and the once is the fix, and the fix is the crontab's timezone, and the timezone is the setting, and the setting is the one line, and the line is the prevention, because the UTC does not repeat and does not skip.

**Make the job idempotent per period.** The job records the period it processed, and the processed is the second run's skip, and the skip is the fix, and the fix is the key, and the key is the date and the hour in UTC, and the UTC is the unique, and the unique is the deduplication, and the deduplication is the discipline.

**Log in UTC with the local offset.** The timestamp is the ISO with the offset, and the offset is the disambiguation, and the disambiguation is the diagnosis, and the diagnosis is the fix, and the fix is the format, and the format is the one setting, and the setting is the discipline, because the ambiguous timestamp is the mystery's start.

**Handle the skipped hour explicitly.** The spring forward is the missed fire, and the missed is the catch-up, and the catch-up is the explicit run, and the run is the fix, and the fix is the scheduler's awareness, and the awareness is the timezone database, and the database is the updated, and the updated is the discipline, because the stale database is the wrong transition.

**Test the transition dates.** The test sets the clock across the transition and asserts the single fire, and the assert is the guard, and the guard is the prevention, and the prevention is the test, and the test is the fix, and the fix is the twice a year, and the twice is the cheap insurance.

## The takeaway

A schedule expressed in local time fires twice when the clock goes back and not at all when it springs forward, because one local hour repeats and another does not exist. Schedule in UTC, make the job idempotent per period, log with the offset, and test both transition dates each year.

The daily digest went out twice on the same morning because the clock went back and the hourly job matched the repeated local hour on both passes, and the two runs had identical local timestamps so the log looked like a duplicate rather than a repeat. The lesson I keep is the property, not the incident.

---

Published on [debugly.dev](https://debugly.dev/) by [Rohit Bhadani](https://debugly.dev/about/) | Founder of [Krova Cloud](https://krova.cloud/?ref=debugly.dev)