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:
domcan't be combined withdow,nth,week_interval, oranchor.day_timescan't be combined with any other field — it's a complete mode on its own.- Otherwise,
dowis required (unless you're usingday_times). nthandweek_intervalcan'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?).anchoronly makes sense withweek_intervalset.timesis always required, except inday_timesmode, 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.