Timezones and DST

How a schedule's timezone and daylight saving time decide when it fires.

A schedule's timezone is an IANA zone name, entered in the Timezone (IANA) field on the Cadence step. The field starts with your browser's zone, or UTC if the browser does not report one. The server looks the name up with Python's zoneinfo and refuses a name it cannot find; the server's response carries unknown IANA timezone '…'. Legacy names in the zone database, such as CET and EST, are accepted. Write the name as the database spells it: europe/madrid was refused in a test run where Europe/Madrid was accepted.

How a fire time is computed

The expression is evaluated on the local clock of the schedule's zone: 0 9 * * * means 09:00 local time, whatever the zone's offset from UTC is that day. The server computes each next fire as a local time with croniter and zoneinfo, converts it to UTC, and stores the UTC instant. Pentr applies no offset arithmetic of its own. The ticker compares that stored instant with the current time.

At a daylight-saving change

The results below come from running the application's cron code for Europe/Madrid, where clocks go back from 03:00 to 02:00 on 25 October 2026 and forward from 02:00 to 03:00 on 28 March 2027.

A time outside the changed hour keeps its local time. 0 9 * * * fires at 09:00 Madrid time on both sides of the change. Its UTC time moves by an hour.

Date Local time UTC
27 March 2027 09:00 CET 08:00
28 March 2027 09:00 CEST 07:00
29 March 2027 09:00 CEST 07:00

A local time that does not exist fires at the end of the gap. On 28 March 2027 the Madrid clock has no 02:30. 30 2 * * * fires that day at 03:00 local time, then returns to 02:30 the next day.

Date Local time UTC
27 March 2027 02:30 CET 01:30
28 March 2027 03:00 CEST 01:00
29 March 2027 02:30 CEST 00:30

A local time that occurs twice fires twice. On 25 October 2026 the Madrid clock shows 02:30 twice, an hour apart. 30 2 * * * fires at both.

Date Local time UTC
24 October 2026 02:30 CEST 00:30
25 October 2026 02:30 CEST 00:30
25 October 2026 02:30 CET 01:30
26 October 2026 02:30 CET 01:30

The ticker computes each next run time from the moment of the tick that fired the previous one, and a step-by-step run of that computation from 24 October produced the same two fires on the 25th. They are two occurrences. Each creates its own engagement, and the second is subject to the plan cap like any other: if the first is still queued or running an hour later and the organization is at its cap, the second is skipped.

The presets all fire at 09:00, which in Europe/Madrid is outside both changes. In another zone, a custom time inside that zone's change behaves as in the tables above: runs for Australia/Lord_Howe, whose clocks move by 30 minutes, and America/Havana, which changes at midnight, gave the same results.

DST and the one-hour floor

The one-hour floor compares fire times on the local clock. Across a change that is not the same as elapsed time, and the check errs in both directions:

  • 30 1,2 * * * in Europe/Madrid is accepted. On 28 March 2027 it fires at 01:30 CET and 03:00 CEST — 00:30 and 01:00 UTC, 30 minutes apart. On the local clock the gap is an hour and a half.
  • 30 2 * * 0, Sundays at 02:30, in Europe/Madrid is refused with observed a 0:00:00 gap. 25 October 2026 is a Sunday inside the check's sample, and its two 02:30 fires are an hour apart in elapsed time and zero apart on the local clock.

30 2 * * *, the daily form of the same time, is accepted: its sample ends in July 2026 and does not reach October. It still fires twice on 25 October, as the table above shows.

How the console shows times

The console shows every schedule time in the schedule's own zone — not in UTC and not in your browser's zone. The Next 3 runs preview labels each time with the zone name. On the Schedules list, the next and last times are in the zone printed on the same row. The date and time format follows your browser's locale, and no UTC offset is shown, so the two 02:30 fires on a fall-back night display the same. If the browser does not recognise the zone name, the time is shown in the browser's own zone instead.

The HTTP API returns schedule times as UTC ISO 8601 strings; the conversion to the schedule's zone happens in the browser.

Next: Caps and skips.

Last updated 2026-09-22