- Guides
- Recurrence and time zones
Guides
Recurrence and time zones
Choose between a fixed interval and a wall-clock cron schedule, pick a valid time zone, and know what happens when clocks change.
A schedule says when it runs using exactly one of four fields. Sending none, or more than one, is refused with invalid_request.
| Field | Runs | Time zone |
|---|---|---|
at | once | none, always UTC |
delaySeconds | once | none |
everySeconds | repeating | none |
cron | repeating | timeZone |
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.
86400is 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:
| Refused | Why |
|---|---|
Abbreviations: BST, CET, GMT | They 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+5 | They 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, Japan | Treated 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:
| Day | What happens |
|---|---|
| Clocks go forward | Local time jumps from 01:00 to 02:00, so 01:30 never happens. The occurrence runs at 02:00 local time. |
| Clocks go back | Local time passes 01:30 twice. The occurrence runs once, at the first 01:30. |
| Every other day | The 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.onOverlap | What happens |
|---|---|
skip (default) | The new occurrence is recorded as skipped and not delivered. |
queue | The 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.catchUpstill runs one late occurrence withinlateness.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.