Skip to content
Guides

What a 2:30 AM cron job does on daylight saving day, in three schedulers

The same schedule runs at three different times when the clocks go forward. What Debian cron, GitHub Actions and the croner library do, with dates for 2027.

Schedule a job for 30 2 * * * in New York, and on Sunday, March 14, 2027 it has a problem: at 2:00 AM the clocks jump to 3:00, so 2:30 never happens. What your job does that night depends entirely on the scheduler. Debian's cron runs it soon after the jump. GitHub Actions moves it to 3:00. The croner JavaScript library, in the version we tested, ran it at 3:30. All three are defensible. None of them is "skip it", which is what a lot of people assume.

If a scheduled agent sends a morning briefing, files a report, or pays a bill, those differences matter twice a year. Here is what each scheduler does, how to check your own, and how to write schedules that do not depend on it.

The two awkward nights

In a time zone with daylight saving time, local time breaks in two ways each year:

  • The gap. When clocks go forward, an hour of local time does not exist. Anything scheduled inside it has no real moment to run.
  • The overlap. When clocks go back, an hour of local time happens twice. Anything scheduled inside it has two candidate moments.

The dates differ by region, and the southern hemisphere runs the other way round. For 2027, computed from the IANA time zone database:

Time zoneGap (clocks forward)Overlap (clocks back)
America/New_YorkMarch 14, 2:00 to 3:00November 7, 1:00 to 2:00
Europe/BerlinMarch 28, 2:00 to 3:00October 31, 2:00 to 3:00
Australia/SydneyOctober 3, 2:00 to 3:00April 4, 2:00 to 3:00

Zones such as Asia/Seoul or Asia/Shanghai have no daylight saving time, so a schedule there never meets either case. UTC never does either.

Three schedulers, three answers

Debian cron

The cron(8) manual page in Debian describes the behaviour for clock changes of less than three hours. When time moves forward, jobs that would have run in the skipped time run soon after the change. When time moves back, jobs that fall into the repeated time are not run again.

There is a catch in the same paragraph. This only applies to jobs that run at a particular time. Jobs written with a wildcard in the hour or minute field, or as @hourly, run based on the new time immediately.

So in Debian cron, a 2:30 job runs once, shortly after 3:00 on the spring night, and once at the first 2:30 in the autumn.

GitHub Actions

GitHub Actions schedules run in UTC by default, and the workflow trigger documentation also accepts an IANA time zone next to the cron line. For time zones with daylight saving time, it says scheduled workflows in skipped hours advance to the next valid time, and gives the example of a 2:30 AM schedule advancing to 3:00 AM.

croner

croner is a popular cron library for JavaScript and TypeScript. Its README says jobs scheduled during DST gaps are skipped, and jobs in DST overlaps run once, at the first occurrence.

We checked that against version 10.0.1 by asking it for the next run times in America/New_York:

import { Cron } from "croner";

const job = new Cron("30 2 * * *", { timezone: "America/New_York", paused: true });
job.nextRun(new Date("2027-03-14T06:00:00Z"));
// 2027-03-14T07:30:00.000Z, which is 3:30 AM EDT

The overlap matched the README: a 30 1 * * * job on November 7, 2027 ran once, at 1:30 AM EDT (05:30 UTC), and not again an hour later. The gap did not: instead of skipping March 14, the library returned 3:30 AM, the same wall-clock minute one hour later.

We also asked for the next runs of an hourly schedule, 0 * * * *, across both nights. In the autumn, the repeated 1:00 hour was skipped, so the 25-hour day got 24 runs. In the spring, the library listed 3:00 AM twice in a row, for the same instant. A scheduler that runs whatever comes back without checking would run that job twice.

The documented behaviour and the behaviour of the version you ship can differ, and the only way to know is to test the exact dates.

Summary of the three

Scheduler2:30 AM job, clocks forward1:30 AM job, clocks back
Debian cronRuns soon after the changeRuns once
GitHub Actions with a time zoneRuns at 3:00 AMNot stated in the docs
croner 10.0.1, as testedRuns at 3:30 AMRuns once, first occurrence

How to test your own scheduler

Whatever you use, the test is cheap. Ask it for the next few run times starting shortly before each transition, and print them in both UTC and local time:

  1. Pick the zones you actually schedule in.
  2. Look up the next gap and overlap for each. Python's zoneinfo or any IANA-aware library can find them by stepping through the year and watching the UTC offset change.
  3. For each schedule pattern you use, compute the next three runs from one hour before the transition.
  4. Check three things: does the job run at all, does it run once, and does it run at a time you can live with.

Put that check in your test suite with fixed dates. Daylight saving rules change by law from time to time, and the IANA database tracks those changes in its regular releases, listed on the IANA time zones page. A test pinned to dates catches it when an update moves a transition under your schedule.

Write schedules that do not care

Better than knowing what your scheduler does on these nights is choosing schedules where it does not matter.

Keep daily jobs out of the early morning. In the zones above, every transition falls between 1:00 and 3:00 local time; a few other zones switch at midnight, so check yours. A daily job at 4:00 or 6:00 never touches the gap or the overlap in any of the zones above. If a job must run "overnight", 4:00 is as overnight as 2:30.

Use local time for human things, UTC for machine things. "Weekdays at 9:00" is about a person's morning, so it should follow their time zone and move with daylight saving time. "Every 15 minutes" or "sync at the top of each hour" is about elapsed time, and is cleaner in UTC, where every hour exists exactly once.

Store the zone name, never the offset. America/New_York knows about daylight saving time. -05:00 does not, and will be an hour off for half the year. croner's own documentation makes the same point about its utcOffset option, which does not take daylight saving time into account.

Make the job safe to run twice, and know if it ran zero times. A job that checks "has today's report already been sent" before sending is immune to double runs from any cause, not only DST. A job that records its last successful run can tell when a run was skipped.

Decide what happens after downtime. Daylight saving time is not the only way runs go missing. If your scheduler was down at the scheduled time, does it catch up or skip? Kubernetes documents its rule clearly in the CronJob documentation: if more than 100 schedules were missed, it does not start the job and logs an error, and startingDeadlineSeconds limits how late a run may start. Its concurrencyPolicy setting decides whether a new run may overlap one that is still going. Long-running agent tasks need the same decision.

Why this matters more for agents

A traditional cron job usually does one fixed thing. A scheduled AI agent might triage an inbox, post a briefing, or chase invoices, and it can take minutes or hours. That makes three of the cases above sharper:

  • A double run can mean two briefings in a channel, or two follow-up emails to the same customer. That is one more reason to put outbound messages behind an approval until a routine has proven itself, as the guide to agent actions that need a human describes.
  • A shifted run can land after the person it was meant for has already started their day.
  • An overlapping run, where the next scheduled run starts while the last one is still working, can have two copies of the agent acting on the same inbox.

For a morning briefing, the better fix is the boring one: schedule it at 7:00. Tenslo Bot stores every routine schedule with a time zone and computes the next run with croner, so a 2:30 AM routine in New York runs at 3:30 on the spring-forward night.

Scheduler behaviour was checked in October 2026 against the documentation above and, for croner, by running version 10.0.1.

Sources

  1. cron(8) manual page, Debian
  2. Events that trigger workflows: schedule, GitHub Docs
  3. README and options, croner
  4. CronJob, Kubernetes documentation
  5. Time zone database, IANA

Share

Scheduled agents that follow your clock.

Tenslo Bot routines keep each schedule with its time zone, so a morning briefing stays at your morning.