Skip to content

Preview a schedule's next runs, and keep the calendar jump from skipping one - #365

Merged
abhishekgahlot2 merged 2 commits into
mainfrom
routine-preview-and-guards
Oct 18, 2026
Merged

abhishekgahlot2 merged 2 commits into
mainfrom
routine-preview-and-guards

Conversation

@abhishekgahlot2

@abhishekgahlot2 abhishekgahlot2 commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Two follow-ups to #363, backend only.

Preview a schedule before it is saved. GET /api/automations/preview?cron=<five fields>&timezone=<IANA> (also under /api/schedules) answers { "next_runs": [three ISO UTC times] }. It uses the rule the scheduler fires by: repo.ts nextRunAt for the first run, then cron.ts nextOccurrence. An editor can call it, debounced, while a person types a schedule, and show what it will do without computing run times of its own.

  • The first run is this minute when this minute is an occurrence, as the tick would still fire a schedule enabled now.
  • The zone defaults to UTC, as a create's does.
  • A cron that names no date gets an empty list.
  • A cron or zone the scheduler cannot read, or a cron longer than 512 characters, answers 400 with a plain error: cron must be a valid 5-field expression or timezone must be a valid IANA name.
  • Org-scoped like the other automation routes.
  • The route and fields match the hosted product's.

Keep the calendar jump from skipping an occurrence. #363 made nextOccurrence pass the days a cron rules out using calendar arithmetic, resuming at noon on the day before the next date it names. Across a change of half a day or more, or a clock set back past midnight, that jump could skip a real occurrence:

  • 0 0 31 12 * in Pacific/Apia from 2011-12-01 answered 2012 instead of 2011-12-30T10:00Z (Apia crossed the date line).
  • * 23 4 3 * in Antarctica/Casey skipped the 23:00 that its 2010 change brought back.

It now jumps only where a day-by-day walk would see the same dates. It does not jump while one of the two dates before the current one matches, and it jumps only when it lands on the day before the named date. A comparison with the walk over every zone, twelve date-restricted crons and starts from 1970 to 2035 (58,740 searches) found no difference. A cron that names no date still costs a fraction of a millisecond.

The release-window trigger from 0114 stays. Dropping it in a later migration would break a still-serving older backend whenever an upgrade applies 0114 and that migration in one batch, and this repository cannot know when every deployment has passed 0114.

Known limit: like next_due_at, the preview names an occurrence the scheduler is due to fire. If a person enables a schedule at 09:00:10 after the minute's tick already ran at 09:00:05, the 09:00 run waits for the next day.

Tests: the preview route (aliases, UTC default, no-date crons, plain errors, the length cap), the current-minute start, and the Apia, Kiritimati, Kwajalein and Casey changes. The full backend suite passes (3544 + 119).


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

GET /api/automations/preview?cron=<five fields>&timezone=<IANA> answers
{next_runs: [three ISO UTC times]} by the rule the scheduler fires by
(repo.ts nextRunAt, then cron.ts nextOccurrence), so an editor can show
what a schedule will do while a person types it, without computing run
times of its own. The first run is this minute when it is an occurrence,
as the tick would still fire a schedule enabled now. The zone defaults to
UTC as a create's does; a cron that names no date gets no runs; a cron or
zone the scheduler cannot read, or a cron longer than any a person types
(512 characters), answers 400 with the service's plain error ("cron must be
a valid 5-field expression", "timezone must be a valid IANA name"). The
/api/schedules alias answers the same, and the route and fields match the
hosted product's.
nextOccurrence passes a run of days the cron rules out by calendar
arithmetic and resumes at noon on the day before the next date it names.
Across a change of half a day or more, or a clock set back past midnight,
that jump could skip a real occurrence: from 2011-12-01, "0 0 31 12 *" in
Pacific/Apia answered 2012 instead of 2011-12-30T10:00Z (Apia crossed the
date line), and "* 23 4 3 *" in Antarctica/Casey skipped the 23:00 its 2010
change brought back.

It now jumps only where a day-by-day walk would see the same dates: not
while one of the two dates before the current one matches, and only when it
lands on the day before the named date. Otherwise the walk takes the next
day as before. The Apia, Kiritimati, Kwajalein and Casey changes give the
walk's answers again, and a comparison with the walk over every zone,
twelve date-restricted crons and starts from 1970 to 2035 (58,740 searches)
found no difference; a cron that names no date still costs a fraction of a
millisecond.
@abhishekgahlot2
abhishekgahlot2 merged commit 9be3a7f into main Oct 18, 2026
22 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 18, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants