1. Guides
  2. Recurrence and time zones

A schedule says when it runs using exactly one of four fields. Sending none, or more than one, is refused with invalid_request.

FieldRunsTime zone
atoncenone, always UTC
delaySecondsoncenone
everySecondsrepeatingnone
cronrepeatingtimeZone

at is an instant in RFC 3339 UTC with milliseconds, such as 2026-07-28T15:00:00.000Z. delaySeconds is converted into an instant when the request is accepted, so the schedule reads back the same as if you had sent at.

This guide covers the two repeating kinds.

​
A fixed interval: everySeconds

everySeconds is a duration: a whole number of seconds from 60 (one minute) to 31,536,000 (one year).

  • The first occurrence is one interval after the schedule is accepted.
  • It takes no time zone, because a duration does not depend on one.
  • It ignores daylight saving time. 86400 is always exactly 24 hours. If clocks change, a schedule that ran at 09:00 local time runs at 08:00 or 10:00 local time afterwards.
{ "everySeconds": 3600, "endpoint": "ep_..." }

Use everySeconds when what matters is the gap between runs, such as polling or heartbeats.

​
A wall clock: cron and timeZone

cron names local times, such as “09:00 on weekdays”. It is read against the timeZone you send with it, or UTC if you send none.

{ "cron": "0 9 * * 1-5", "timeZone": "Europe/London", "endpoint": "ep_..." }
  • Use five fields (minute, hour, day of month, month, day of week), or an @ nickname. Six-field expressions are accepted only when the seconds field is zero.
  • The finest schedule is once a minute. An expression that asks for a particular second is refused.
  • At most 120 characters.
  • An expression that never matches a real time, such as 0 0 30 2 * (30 February), is refused.
  • The first occurrence is the first time the expression matches after the schedule is accepted.

Use cron when what matters is the local time, such as a report due at 09:00 in your office.

​
Choosing a time zone

timeZone must be an IANA time zone name with a region, such as Europe/London, America/New_York, or Asia/Kolkata, or exactly UTC.

These are refused:

RefusedWhy
Abbreviations: BST, CET, GMTThey are ambiguous. BST resolves to Bangladesh, so a schedule meant for British Summer Time would run six hours off, and nothing would look wrong.
Fixed offsets: +01:00, Etc/GMT+5They have no daylight saving rules, so the schedule drifts an hour away from local time the day clocks change. (Etc/GMT+5 is actually UTC−5.)
Names with no region: Eire, JapanTreated like abbreviations.

Old and new spellings of a renamed zone are both accepted, for example Asia/Kolkata and Asia/Calcutta. Greatwait stores one spelling for each zone, so a schedule may read back with a different spelling than the one you sent.

​
When clocks change

A cron schedule follows local time through daylight saving changes. Two days a year, a local time is either missing or happens twice:

  • Clocks go forward and your time does not exist that day: the occurrence runs at the first moment after the gap, which is the moment the clocks jump.
  • Clocks go back and your time happens twice: the occurrence runs once, at the earlier of the two.

For example, with timeZone Europe/London and a daily time of 01:30:

DayWhat happens
Clocks go forwardLocal time jumps from 01:00 to 02:00, so 01:30 never happens. The occurrence runs at 02:00 local time.
Clocks go backLocal time passes 01:30 twice. The occurrence runs once, at the first 01:30.
Every other dayThe occurrence runs at 01:30 local time.

everySeconds is unaffected by any of this, as described above.

​
How occurrences are planned

A repeating schedule plans one occurrence at a time. The next one is planned once the current one comes due, so reading the schedule always shows one occurrence still to come.

schedules.replace can switch a schedule between any of the four kinds. An occurrence planned under the old definition that has not started settles as skipped_stale, and a new one is planned under the new definition.

​
When a run is still going

This applies only to repeating schedules that allow long deliveries, meaning delivery.maxDurationSeconds is above 30 seconds. If the previous occurrence has not finished when the next comes due, delivery.onOverlap decides what happens:

delivery.onOverlapWhat happens
skip (default)The new occurrence is recorded as skipped and not delivered.
queueThe new occurrence waits and runs when the previous one finishes.

With shorter deliveries, every occurrence runs.

​
When a run starts late

lateness.policy decides what happens when an occurrence’s first attempt would start after its planned instant:

  • skipAhead, the default for repeating schedules, never runs a late occurrence once the next planned time has passed.
  • catchUp still runs one late occurrence within lateness.maxStalenessSeconds.

A one-shot defaults to catchUp with a 24-hour window. An occurrence too late to run settles as skipped_stale. Retries of an attempt that has already started are not affected; see Failures and retries.