Skip to content

The Five Modes and Fields

A schedule is a single composite value with seven fields. Which fields you populate tells pgrelay.schedule which of five modes you mean — there is no separate "kind" flag that could contradict the fields you actually set.

Mode Fields used Example
1. Every matching weekday dow weekdays at 04:30, 17:00
2. Nth weekday of the month dow + nth 2nd Tuesday
3. Every N weeks dow + week_interval (+ anchor) fortnightly on Tuesday
4. Days of the month dom 3rd, 5th, 22nd
5. Independent per-day times day_times Mon 05:00; Tue 17:30 and 20:00; Wed 12:00

Field reference

Field Type Meaning
dow text 'mon,wed,fri' | 'mon-fri' | 'weekdays' | 'weekends' | 'everyday' | '1'..'7' (ISO: 1=Monday .. 7=Sunday). Case insensitive.
nth smallint[] 1..5 = the nth occurrence of that weekday in the month; -1..-5 = counting back from the end (-1 = last).
week_interval smallint 1..52. 1 means every week, 2 means fortnightly, 3 means every third week. Weeks run Monday to Sunday (ISO).
anchor date Which week counts as "week zero" for week_interval. Any date you give is truncated to its Monday. Leave it NULL and pg_relay uses a fixed epoch (1970-01-05) — every schedule that leaves anchor unset is automatically aligned with every other one. Set an explicit anchor to deliberately stagger a schedule onto the opposite week.
dom smallint[] 1..31 (clamped — see below), or -1..-31 counting back from the end of the month (-1 = last day).
times text Comma-separated, always UTC, to the minute: 330,4:30,05:30,645,1700. A bare 1–2 digit number means on the hour ('17' = 17:00). Seconds are rejected outright, never silently dropped.
day_times text Independent day→times groups, semicolon separated: 'mon=0500;tue=1730,2000;wed=1200'. Each group is dow=times, using exactly the same grammar as the dow and times fields above, on either side of the =. Groups are a union — mon=0500;mon=0600 means exactly the same thing as mon=0500,0600.

Rules that get enforced automatically

You'll get a clear error message — not a silent wrong answer — if you break one of these:

  • dom can't be combined with dow, nth, week_interval, or anchor.
  • day_times can't be combined with any other field — it's a complete mode on its own.
  • Otherwise, dow is required (unless you're using day_times).
  • nth and week_interval can't both be set. One counts occurrences within a month; the other counts weeks on a continuous cycle. Combined, they'd produce a pattern nobody could predict ("2nd Tuesday, every 3rd week" — which Tuesday, exactly?).
  • anchor only makes sense with week_interval set.
  • times is always required, except in day_times mode, where each group carries its own times instead.

Two things worth understanding before you rely on them

Everything is UTC — there is no timezone field

A schedule that says times = '0430' fires at 04:30 UTC, every day it's scheduled to run, all year round. There's no DST handling: if your users think in a timezone that observes daylight saving, a "4:30am" job will land an hour off from their local wall clock for half the year. This is deliberate — timezone-aware recurrence has to handle a schedule firing twice, or not at all, on the day of a DST transition, and sidestepping that entirely keeps the type simple and its behaviour completely predictable. If your application needs local-time scheduling, convert at your own boundary (e.g. store the user's timezone separately and compute the UTC times value yourself).

dom clamps to the last day of short months

dom = {31} fires on the last day of every month — in February, that's the 28th (or 29th in a leap year). This makes {31} behave identically to {-1} ("last day"): there's no way to say "the 31st, but only in months that actually have one."

A related consequence: dom = {30, 31} collapses to a single fire on 30 April (a 30-day month), because 31 April doesn't exist and clamps down to 30 — the same date 30 already produces. Fire times are de-duplicated, so this doesn't cause a double-fire; it's just not always what you'd expect at first glance.

Use the constructors, not a hand-written value

pgrelay.month_end(times) and pgrelay.month_start(times) exist specifically so you never have to think about which end of the dom range means what. See the API Reference.


Continue to the API Reference.