Rule reference
The complete recurrence rule syntax. If you're new to the rule language, repeating events teaches it from the beginning and building repeats covers the picker that writes rules for you.
This page is generated from the rule engine's own specification, which is verified against the engine: every example was run through it to confirm the dates it produces. The full argument lists for
MOON(...)andEASTER(...)have their own pages, Moon rules and Easter rules.
Contents
- The three things to know first
- Every day
- Every N days / weeks / months / years
- A weekday every week
- A day of the month
- A day of the year
- "2nd Saturday of the month" style
- Weekdays and weekends
- A one-off date
- Excluding dates with
! - Categories, tags, events and feeds
- Move to a qualifying day with
~ - Moving dates by a fixed amount with
+and- - Powerful patterns with
__OF__ - A specific ISO week of the year
- Limiting to certain months
- Moon phases
- Easter and the days that move with it
- Intersections
- Notes & limitations
- Cheat sheet
- Recipes
1. The three things to know first
1. Combine rules with commas. A schedule can be several rules separated by commas. The engine gathers the dates from each one:
Mon, Thu
→ every Monday and every Thursday.
2. A ! in front means "exclude".
A rule that starts with ! removes dates instead of adding them. It's perfect
for "…except":
WD, !BankHolidays
→ every weekday except bank holidays.
3. Order matters — put inclusions first, exclusions last.
The engine reads your rules left to right, adding dates for normal rules and
removing them for ! rules. So write what you want first, then subtract:
WD, !Fri
every weekday except Friday
!Fri, WD
removes Friday, then adds all weekdays back — Friday returns
If a schedule has no normal (non-!) rule at all, the event simply falls on
its own start date.
Everything below is just different ways to fill in those rules.
The building-block codes
Days of the week (exactly these, capitalised):
| Mon | Tue | Wed | Thu | Fri | Sat | Sun |
|---|---|---|---|---|---|---|
Months (exactly these, capitalised):
| Jan | Feb | Mar | Apr | May | Jun | Jul | Aug | Sep | Oct | Nov | Dec |
|---|---|---|---|---|---|---|---|---|---|---|---|
2. Every day
| Rule | Meaning |
|---|---|
Day |
Every single day |
That's it — Day on its own fires every day.
3. Every N days / weeks / months / years
These repeat relative to the event's start date. Add a number for the interval; leave it off for "every one".
| Rule | Meaning |
|---|---|
Day2 |
Every 2nd day (Day5 = every 5 days) |
Weekly |
Every week, on the start date's weekday |
Weekly2 |
Every 2 weeks |
Monthly |
Every month, on the start date's day-of-month |
Monthly2 |
Every 2 months |
Yearly |
Every year, on the start date's month and day |
Yearly2 |
Every 2 years |
Short months: if the start date is the 31st,
Monthlyautomatically falls on the last day of shorter months (the 30th, or 28th/29th in February).
Example — Day3 from 1 Jan 2025 → 2025-01-01, 2025-01-04, 2025-01-07, …
4. A weekday every week
Just write the day code. Add a trailing number to skip weeks.
| Rule | Meaning |
|---|---|
Thu |
Every Thursday |
Thu2 |
Every 2nd Thursday (fortnightly) |
Sat3 |
Every 3rd Saturday |
Example — Thu2 from 1 Jan 2025 → 2025-01-02, 2025-01-16, 2025-01-30, 2025-02-13, …
5. A day of the month
Write the day number followed by Month.
| Rule | Meaning |
|---|---|
5Month |
The 5th of every month |
5Month2 |
The 5th of every 2nd month |
-1Month |
The last day of every month |
-2Month |
The second-to-last day of the month |
A negative number counts back from the end of the month.
Example — -1Month → 2025-01-31, 2025-02-28, 2025-03-31, …
Example — 5Month2 → 2025-01-05, 2025-03-05, 2025-05-05, …
Months that are too short
Not every month has a 31st, and February has no 30th. By default the rule simply doesn't happen that month:
Example — 31Month → 2025-01-31, 2025-03-31, 2025-05-31, 2025-07-31,
2025-08-31, 2025-10-31, 2025-12-31 — seven dates a year, and nothing in the
five short months.
That is deliberate. It is what the iCalendar standard requires, so it is what every calendar Calendite exchanges events with does, and an event imported from elsewhere can never mean anything else.
It is often not what someone means by "the 31st of every month", though, so you can say what you want instead:
| Rule | February 2025 | April 2025 |
|---|---|---|
31Month |
(nothing) | (nothing) |
31Month(CLAMP) |
the 28th | the 30th |
31Month(NEXT) |
3 March | 1 May |
CLAMP uses the last day the month does have.
NEXT keeps counting past the end of the month. February 2025 runs out after
the 28th, so its 29th, 30th and 31st are the 1st, 2nd and 3rd of March — the day
number still means something once it has left the month, rather than everything
past the end piling onto the 1st. A leap February is a day longer, so the 31st
lands on 2 March instead. This is also how English law treats a 29 February
birthday: age is attained on 1 March.
Two things worth knowing:
- The rhythm doesn't change.
31Month2(CLAMP)stays on the odd months and clamps the short ones (2025-01-31, 2025-03-31, 2025-05-31, 2025-07-31, 2025-09-30, 2025-11-30). A date that runs over is still counted against the month it came from, soNEXTnever nudges the schedule along a month at a time. -1Monthis usually simpler. If you mean "the end of the month", say that —31Month(CLAMP)produces exactly the same dates the long way round.
The same suffix works on a day of the year (§6), so 29Feb happens only in leap
years, 29Feb(CLAMP) falls back to the 28th, and 29Feb(NEXT) counts on to
1 March.
6. A day of the year
Write the day and month together. Add a trailing number for a multi-year interval.
| Rule | Meaning |
|---|---|
25Dec |
Every 25th December |
1Jan |
Every 1st January |
25Dec2 |
Every 25th December, every 2nd year |
A negative day counts back from the end of that month, the same way -1Month does
for an ordinary month (§5):
| Rule | Meaning |
|---|---|
-1Aug |
The last day of August (the 31st) |
-2Aug |
The second-to-last day of August (the 30th) |
-1Feb |
The last day of February |
The date is worked out per year rather than fixed, so -1Feb lands on the 28th in a
common year and the 29th in a leap year.
February 29th. 29Feb happens only in leap years — the other years are
skipped, as §5 describes. Add a suffix to say otherwise:
| Rule | 2024 (leap) | 2025 |
|---|---|---|
29Feb |
29 Feb | (nothing) |
29Feb(CLAMP) |
29 Feb | 28 Feb |
29Feb(NEXT) |
29 Feb | 1 Mar |
A day the month can never have is refused outright — there is no 31st of April
in any year, so 31Apr is a typo rather than a schedule. Saying what you want
makes it meaningful again: 31Apr(CLAMP) is the 30th and 31Apr(NEXT) is
1 May.
Example — -1Feb → 2024-02-29, 2025-02-28, 2026-02-28, …
7. "2nd Saturday of the month" style
Put an occurrence number in front of the day.
| Rule | Meaning |
|---|---|
2Sat |
The 2nd Saturday of the month |
1Mon |
The 1st Monday of the month |
-1Thu |
The last Thursday of the month |
2Sat2 |
The 2nd Saturday of every 2nd month |
Example — 2Sat → 2025-01-11, 2025-02-08, 2025-03-08, …
Occurrence vs. week — the underscore. An underscore changes the meaning from "the Nth occurrence" to "the day that falls in the Nth week of the month":
| Rule | Meaning |
|---|---|
2Wed |
The 2nd Wednesday of the month |
2_Wed |
The Wednesday that falls in the 2nd week of the month |
These differ when the month starts mid-week. For February 2024, 2Wed is the 14th
but 2_Wed is the 7th.
Week 1 is the week containing the 1st, even when most of it belongs to the
previous month, and weeks begin on your locale's first day (UK: Monday). February
2025 opens on a Saturday, so its week 1 is Jan 27 – Feb 2 and its week 2 is Feb 3 – 9
— making 2_Mon the 3rd, while 2Mon is the 10th.
The two forms agree whenever the day you ask for is not earlier in the week than the day the month opens on. A month starting on a Monday makes them identical throughout.
A day outside the month is not an occurrence. 1_Mon in January 2025 asks for the
Monday of Dec 30 – Jan 5, which is 30 December — so January simply has none, and the
rule resumes in months whose first week does contain a Monday. The same applies at the
end: -1_Sat asks for the Saturday of the month's last week, which in January 2025 is
1 February, so again there is no occurrence.
8. Weekdays and weekends
WD means weekdays, WE means weekend days.
| Rule | Meaning |
|---|---|
WD |
Every weekday |
WE |
Every weekend day |
Which days count as the weekend depends on your region — the engine uses your locale's week configuration (UK: Mon–Fri weekdays, Sat–Sun weekend).
You can also count occurrences of them within a month, exactly like named days, with an optional month interval:
| Rule | Meaning |
|---|---|
2WD |
The 2nd weekday of the month |
-1WD |
The last weekday of the month |
3WE |
The 3rd weekend day of the month |
-1WD2 |
The last weekday of every 2nd month |
Intervals and week-of-month
A trailing number (no leading occurrence number) gives two more forms, following
the same occurrence-vs-week underscore rule as 2Sat / 2_Sat:
| Rule | Meaning |
|---|---|
WD2 |
Every 2nd weekday, counting weekdays from the start (Mon, Wed, Fri, Tue, …) |
WD3 |
Every 3rd weekday |
WE2 |
Every 2nd weekend day |
WD_2 |
Every weekday in the 2nd week of each month |
WE_2 |
Every weekend day in the 2nd week of each month |
The no-underscore form counts individual days (WD2 = every other weekday).
The underscore form selects a whole week of the month (WD_2 = all weekdays in
its 2nd week), mirroring 2_Sat = "the Saturday in the 2nd week of the month".
Example — WD2 from Mon 6 Jan 2025 → Jan 6, 8, 10, 14, 16, 20, 22, 24, …
Example — WD_2 → Jan 6–10, Feb 3–7, … (the weekdays of each month's 2nd week)
Grouped day-sets
Wrap a dot-separated set of days in parentheses to match any of them:
| Rule | Meaning |
|---|---|
(Mon.Wed.Fri) |
Every Monday, Wednesday or Friday |
(WD) |
Every weekday (same as WD) |
(WE) |
Every weekend day |
Standalone, (Mon.Wed.Fri) is just shorthand for Mon, Wed, Fri. It's most useful
as the inner part of an __OF__ pattern — 2__(Mon.Wed.Fri)__OF__MONTH (§14) — or
an ISO-week rule (§15).
9. A one-off date
Write a full date in YYYY-MM-DD form (zero-padded):
2025-05-17
→ occurs on 17 May 2025, and only then.
One-off dates are most useful combined with other rules — see exclusions below.
10. Excluding dates with !
Put ! in front of any rule to remove its dates.
| Rule | Meaning |
|---|---|
!1Sat |
Not on the 1st Saturday of the month |
!BankHolidays |
Not on any bank holiday |
!2025-12-25 |
Not on 25 December 2025 |
Combine freely (remember: inclusions first, exclusions after):
Sat, !1Sat, !2Sat
→ every Saturday except the first two of each month. (Jan 2025 → 18th & 25th; Feb 2025 → 15th & 22nd.)
WD, !2025-12-25
→ every weekday, but skip Christmas Day 2025.
11. Categories, tags, events and feeds
These rules reference dates that live outside the rule itself. They only resolve when the app supplies the relevant data — on their own they contribute no dates.
Categories
A category is a named group of dates, written in PascalCase (it must start
with a capital and contain at least one lowercase letter):
BankHolidays
Use it like any other rule — most often as an exclusion:
WD, !BankHolidays
→ every working weekday.
Pick one member of a category by naming it in parentheses (also PascalCase):
BankHolidays(Christmas)
→ only the Christmas date(s) from the bank-holidays group.
You can define your own categories too, e.g. KarateDays or SchoolHolidays, and
reference them the same way.
Named selectors: Tag, Event, Feed
When you want to be explicit about what kind of thing you're referencing, use a named selector. The name inside the brackets starts with a capital and is letters/digits:
| Rule | Means |
|---|---|
Tag(Work) |
The dates of every event carrying the tag Work |
Event(Work) |
The occurrence dates of the single event series Work |
Feed(BankHolidays) |
The dates supplied by the external feed BankHolidays |
Tag(Work) and Event(Work) are deliberately different date sets — one is every
event sharing a tag, the other is one specific event — which is why the kind is
spelled out.
Example: WD, !Tag(Karate) → every weekday except days carrying the Karate tag.
WORKDAYalias:WORKDAYis accepted as a shorthand forTag(Work). It's rewritten before evaluation, so it behaves identically — standalone (WORKDAY,!WORKDAY) and nested (1__WORKDAY__OF__MONTH). Like any selector it needs a data source to resolve.
12. Move to a qualifying day with ~
Sometimes you want "the weekday nearest to a date" — e.g. a holiday observed on a
weekday when the real date lands on a weekend. Use the ~ operator:
{type} ~ {direction} {anchor}
{type}is the kind of day to land on. UsuallyWDorWE, but any rule works:WORKDAY, a weekday (Mon…Sun), a day-set ((Mon.Fri)), a tag (Tag(Work)), a moon phase (MOON(FULL)), or an interval (Day).{anchor}is the occasion you are adjusting around — e.g.25Dec.{direction}is one of:
| Symbol | Meaning |
|---|---|
< |
Strictly before |
<= |
On the date if it qualifies, otherwise before |
> |
Strictly after |
>= |
On the date if it qualifies, otherwise after |
= |
Nearest |
| Rule | Meaning |
|---|---|
WD~<25Dec |
The weekday just before Christmas |
WD~<=25Dec |
Christmas itself if it's a weekday, else the weekday before |
WD~>25Dec |
The weekday just after Christmas |
WD~=25Dec |
The nearest weekday to Christmas |
Example — WD~>25Dec (2025) → Christmas is Thursday, so the next weekday is
2025-12-26.
The type can be any rule, which is what makes the operator useful beyond weekdays:
| Rule | Meaning |
|---|---|
Tag(Work)~<25Dec |
The last work day before Christmas |
(Mon.Fri)~<25Dec |
The last Monday or Friday before Christmas |
MOON(FULL)~<25Dec |
The last full moon before Christmas |
-1Fri~<25Dec |
The last Friday of the month before Christmas |
Tag(Work) and WORKDAY need a data source, like any tag (§11).
What can go in the type slot
The test is whether the rule produces days. If it names days you could point at on a calendar, it can be adjusted around:
| Can be the type | Example |
|---|---|
| Weekday / weekend / every day | WD, WE, Day |
| A named weekday, or a day-set | Fri, (Mon.Fri) |
| An ordinal, including a negative one | 1Mon, -1Fri |
| A date, a yearly date, an interval | 2026-05-17, 25Dec, Monthly |
| A moon phase | MOON(FULL) |
| A tag, event, feed or category (needs its data) | Tag(Work), WORKDAY |
An __OF__ composite |
-1__WD__OF__MONTH |
An anchored period can be the type — Fri__OF__WEEK(25Dec)~<1Jan is the Friday
of Christmas week, taken before New Year's Day — but only one bracketed group is
allowed, so (1..2)__WD__OF__WEEK(25Dec)~<1Jan is out.
| Cannot be the type | Why |
|---|---|
LIMIT(...) |
A filter, not a producer — see below |
@&@(...) |
Resolved before the engine sees it (§11) |
| Two bracketed groups | (1..2)__WD__OF__WEEK(25Dec) — one group is the limit |
A LIMIT cannot be adjusted around, on either side. It filters days rather
than producing them, so there is nothing to land on. Worse, asking it a question
turns it inside out — a LIMIT matches the months it removes — so
LIMIT(Jan)~<25Dec quietly meant "the last day not in January before Christmas"
and answered the 24th. Both LIMIT(Jan)~<25Dec and WD~<LIMIT(Jan) are now
rejected outright. To limit an adjustment to certain months, put the LIMIT
alongside it as a separate rule instead (§16): {WD~<25Dec, LIMIT(Dec)}.
The question is asked once per anchor
This is the single most important thing to understand about ~, and the easiest
to get wrong.
{type}~={anchor} means "for each anchor, give me the nearest type". It is a
mapping, run once per anchor date. It does not survey the type dates and pick a
single best one.
That distinction is invisible until the anchor is common. Over 2025–2026:
| Rule | Result |
|---|---|
MOON(FULL)~=25Dec |
2 dates — one per Christmas |
MOON(FULL)~=Fri |
25 dates — every full moon there is |
There are 104 Fridays and 25 full moons, about four Fridays per lunar cycle, so
every full moon is the nearest one to some Friday. Ask 104 questions of 25
candidates and every candidate comes back. MOON(FULL)~=Fri is an expensive way
to write MOON(FULL), and only 2 of its 25 dates even fall on a Friday.
The same collapse happens whenever the anchor is no rarer than the type:
| Rule | Actually means |
|---|---|
MOON(FULL)~=Fri |
MOON(FULL) |
MOON(FULL)~=WD |
MOON(FULL) |
Mon~=Fri |
Mon |
25Dec~<=WD |
25Dec |
WD~<=Fri |
Fri |
The engine does not stop you writing these. They are not errors — each returns the correct answer to the question actually asked. They are just redundant, and the redundancy is silent, so check the dates when the anchor recurs often.
Put the occasion on the right. The anchor is the occasion you are adjusting
around; the type is the kind of day you want to land on. 25Dec~<=WD has the
sentence backwards — an occasion on the left, a kind of day on the right — and
duly returns the 25th of December unchanged.
When "closest" means anything at all
"The full moon closest to a Friday" is a reasonable English sentence. "The Monday closest to a Friday" is nonsense. The difference is not how often each happens — it is whether the distance varies.
| Question | Distances | |
|---|---|---|
| full moon → nearest Friday | 0, 1, 2 or 3 | varies, so a closest exists |
| full moon → nearest 22nd | 2 … 15 | varies |
| Monday → nearest Friday | always 3 | every candidate ties |
Mondays and Fridays ride the same seven-day cycle, so the gap between them is fixed forever and no Monday is closer to a Friday than any other. Full moons run on 29.53 days and drift against the week, so the gap moves through every value.
"The X closest to Y" is meaningful when X and Y run on cycles that drift against each other, and empty when they share a cycle.
That is the test to apply before writing a ~=, and it is about the two rules'
periods, not their frequencies.
What ~ deliberately does not do
Even where "closest" varies, English is ambiguous between two readings, and ~
only implements the first:
- Per anchor — for each 22nd, the nearest full moon. Many answers. This is
~. - Superlative — of all the candidate full moons, the single one nearest to a 22nd. One answer.
Reading 2 needs a bounded set of candidates to mean anything: over June and July
alone the answer might be 23 June, but widen the span and it changes. A rule has
to answer occursOn(date) from a date alone, so a rule whose dates shift
depending on how far you scroll is not a rule at all.
There is no syntax for reading 2, and asking for it with ~ silently gets you
reading 1 instead. See
Bounded_Superlative_Selection.md in the rule engine's repository for what
adding it would involve.
For "the X of the week of Y", use __OF__WEEK(...) instead (§14). ~ finds
the nearest match, which is not the same question and gives a different answer
near a week boundary. Christmas 2028 falls on a Monday, so Fri~=25Dec returns
Friday the 22nd — nearer, but the week before Christmas — while
Fri__OF__WEEK(25Dec) returns the 29th. Use ~ when you mean "nearest", and the
anchored period when you mean "in the same week".
Negate the type with a leading !. Put ! in front of the type to land on a
day that is not that type — e.g. the next non-working day:
| Rule | Meaning |
|---|---|
!WD~>25Dec |
The first non-weekday (weekend) after Christmas |
!WD~<25Dec |
The first non-weekday before Christmas |
Here the ! negates the type, not the whole rule — !WD~>25Dec still adds a
date (a weekend day), it doesn't exclude anything.
Example — !WD~>25Dec (2024) → Christmas is Wednesday, so the next weekend day is
2024-12-28 (Saturday).
13. Moving dates by a fixed amount with + and -
Sometimes you don't want the nearest sort of day — you want a plain "three days before that". Add or subtract a duration:
{rule} + P{n}{Unit}
{rule} - P{n}{Unit}
{Unit} is Day, Week, Month or Year. Every date the rule produces moves
by that amount.
| Rule | Meaning |
|---|---|
25Dec-P1Day |
Christmas Eve, every year |
25Dec+P1Day |
Boxing Day |
Mon-P1Day |
Every Sunday-before-a-Monday |
Event(Karate)-P1Day |
The day before every karate class |
Tag(Payday)-P1Week |
A week before each payday |
-1Thu+P2Day |
Two days after the last Thursday of the month |
Example — 25Dec-P1Day → 2025-12-24, 2026-12-24, 2027-12-24, …
Why the P
The P marks a duration, and without it the rule would be ambiguous. This
grammar already uses {number}{unit} to mean a position: 1Month is the 1st of
the month (§5) and -1Fri is the last Friday (§7). So 25Dec-1Day could not be
told apart from those, and a bare - clashes with ISO dates too. With the P
everything reads cleanly, including 2025-05-17-P1Day.
This is not the same as ~
The two look similar and answer very different questions.
| Asks | 25Dec, 2028 |
|
|---|---|---|
WD~<25Dec |
the nearest day of a type | 22nd — Christmas is a Monday, so it skips back to the Friday |
25Dec-P1Day |
exactly one day earlier | 24th — always, whatever weekday that is |
Use ~ when you mean "the working day before"; use -P1Day when you mean "the
day before". A shift never changes its mind based on what weekday something lands
on, which also means it can't quietly collapse the way an ill-posed ~ can (§12).
Months clamp to the end of the month
There is no 31st of February, so a month or year shift lands on the last day that exists:
| Rule | Result |
|---|---|
31Jan+P1Month |
28 Feb (29 Feb in a leap year) |
Clamping loses the original day, so shifts do not undo each other:
31Jan+P1Month-P1Month is the 28th of January, not the 31st. This surprises
people, so it is worth saying plainly — the 31st became the 28th on the way out
and there is nothing left to say it was ever the 31st.
Chaining
Shifts apply left to right, so you can stack them:
1Month+P1Month-P1Day
→ the 1st of the month, forward a month, back a day = the last day of every
month. (-1Month says the same thing more directly, §5.)
Limits
- A
LIMIT(...)cannot be shifted. It removes days rather than producing them, so there is nothing to move — the same reason it cannot be adjusted around (§12). - A shift of zero (
25Dec+P0Day) is rejected; write25Dec. ~and__&__bind looser than a shift, soWD~<25Dec-P1Dayis the weekday before Christmas Eve, andMon__&__Fri-P1Dayintersects against the shifted Friday.
14. Powerful patterns with __OF__
For advanced schedules, the engine has a "pick the Nth thing out of a period" pattern:
{which}__{inner}__OF__{period}
{period}isDAY,WEEK,MONTH,YEAR, or a specific month (Jan…Dec).{inner}is the kind of day to pick. UseWD,WE, a single day code (Mon…Sun), a grouped day-set ((Mon.Wed.Fri)), or a data-backed reference — a category (BankHolidays) or a selector (Tag(X),Event(X),Feed(X)).{which}selects which occurrence(s):
| Form | Meaning |
|---|---|
1 |
The 1st |
-1 |
The last (negative counts from the end) |
(1..5) |
The 1st through 5th (a range) |
(1.3.5) |
The 1st, 3rd and 5th only (a set) |
ALL |
Every one in the period (specific months only) |
Range/set members may be negative — (-3..-1) for the last three, (1.-1) for the
first and the last. A range's two endpoints must share a sign (use a set for mixed).
With MONTH / YEAR / WEEK / DAY
| Rule | Meaning |
|---|---|
1__WD__OF__MONTH |
First weekday of the month |
-1__WD__OF__MONTH |
Last weekday of the month |
(1..5)__WD__OF__MONTH |
First through fifth weekday of the month |
(1.3.5)__WD__OF__MONTH |
1st, 3rd and 5th weekday of the month |
-1__Fri__OF__MONTH |
Last Friday of the month |
-2__WE__OF__YEAR |
Second-to-last weekend day of the year |
Example — -1__Fri__OF__MONTH → 2025-01-31, 2025-02-28, 2025-03-28, …
Positions are counted over the whole period, whatever day the event begins on. An
event created on the 4th Thursday of November with the rule 4__Thu__OF__Nov fires
that very day; one created on the 3rd of January with 2__WD__OF__MONTH fires nothing
in January (the 2nd weekday, the 2nd, is already past) and picks up in February.
With a specific month (repeats yearly)
Here the period is a month code, and you can use ALL, plus an optional
year-interval suffix:
| Rule | Meaning |
|---|---|
4__Thu__OF__Nov |
4th Thursday of November (US Thanksgiving) |
-1__Mon__OF__May |
Last Monday of May (US Memorial Day) |
1__Sun__OF__Nov |
First Sunday of November |
ALL__Mon__OF__Jan |
Every Monday in January |
ALL__Mon__OF__(Jan.Jul) |
Every Monday in January and July |
4__Thu__OF__Nov2 |
4th Thursday of November, every 2nd year |
Example — 4__Thu__OF__Nov → 2023-11-23, 2024-11-28, 2025-11-27, …
With a tag, event, feed or category
The inner rule can also be a data-backed reference (§11). This is how you express "the Nth working day of the month" — model your work days as a tag or event and pick from them:
| Rule | Meaning |
|---|---|
1__Tag(Work)__OF__MONTH |
First day carrying the Work tag, each month |
-1__Tag(Work)__OF__MONTH |
Last work day of the month |
1__Event(Work)__OF__MONTH |
First occurrence of the Work event, each month |
-1__BankHolidays__OF__YEAR |
The last bank holiday of the year |
Example — -1__Tag(Work)__OF__MONTH → the last Work day in each month.
With a grouped day-set
The inner rule can be a parenthesised day-set (§8), so you can pick from a chosen handful of weekdays:
| Rule | Meaning |
|---|---|
2__(Mon.Wed.Fri)__OF__MONTH |
The 2nd day among Mon/Wed/Fri each month |
-1__(Mon.Wed.Fri)__OF__MONTH |
The last Mon/Wed/Fri of the month |
Example — 2__(Mon.Wed.Fri)__OF__MONTH (2024) → Jan 3, Feb 5, Mar 4, …
Anchoring the period to another rule
Put a rule in brackets after the period and it stops repeating on the calendar and pins itself to the period containing that rule's dates:
{which}__{inner}__OF__{period}({anchor})
1__WD__OF__MONTH is the first weekday of every month. 1__WD__OF__MONTH(25Dec)
is the first weekday of December — the month Christmas falls in.
| Rule | Meaning |
|---|---|
Fri__OF__WEEK(25Dec) |
The Friday of Christmas week |
Mon__OF__WEEK(3Tue) |
The Monday of the week holding the 3rd Tuesday |
WD__OF__WEEK(25Dec) |
Every weekday of Christmas week |
1__WORKDAY__OF__WEEK(25Dec) |
The first work day of Christmas week |
-1__WD__OF__MONTH(25Dec) |
The last weekday of December |
1__Mon__OF__YEAR(Event(Launch)) |
The first Monday of the launch year |
The anchor is any rule at all — a date, another rule, or a data-backed reference
(Tag(X), Event(X), Feed(X), a category). Several anchors landing in one period
describe the same period, so the result is not repeated.
Omitting {which} gives every matching day of that period, which is why
Fri__OF__WEEK(25Dec) reads as simply "the Friday of Christmas week" — there is only
one. Supply {which} to pick by position, using the same grammar as above.
Why not just use ~? Because "the week of" and "the nearest" are different
questions. Mon~<3Tue (§12) searches backwards for a Monday, so it needs you to know
that Monday precedes Tuesday; Mon__OF__WEEK(3Tue) does not. More importantly, the
nearest match can land in the wrong week: in 2028 Christmas is a Monday, so
Fri~=25Dec gives Friday 22 December — nearer, but the week before Christmas —
while Fri__OF__WEEK(25Dec) correctly gives the 29th.
Example — Fri__OF__WEEK(25Dec) → 2025-12-26, 2026-12-25, 2027-12-24, 2028-12-29
15. A specific ISO week of the year
To land on days inside a numbered ISO week of the year, use __OF__WEEK:
{days} __OF__ WEEK{number}
{days} may be a single day code, a grouped set (Mon.Wed.Fri), or (WD) / (WE).
| Rule | Meaning |
|---|---|
Mon__OF__WEEK5 |
The Monday of ISO week 5 |
(Mon.Wed.Fri)__OF__WEEK5 |
Mon, Wed and Fri of ISO week 5 |
(WD)__OF__WEEK5 |
Every weekday of ISO week 5 |
Week numbers run 1–53. ISO weeks start on Monday, and week 1 is the week containing the year's first Thursday. This is "a day inside week 5", which is different from "the 5th Monday of the year".
Example — Mon__OF__WEEK5 (2025) → 2025-01-27.
16. Limiting to certain months
LIMIT(...) restricts everything else to only the listed months. Write it last.
Mon, Thu, LIMIT(Jan.Mar)
→ every Monday and Thursday, but only in January and March.
WD, LIMIT(Jan.Feb.Mar)
→ every weekday in Q1 only.
Months inside LIMIT(...) are separated by dots.
Everything except certain months with !LIMIT(...)
Put a ! in front to flip it: !LIMIT(...) keeps the whole year except the
listed months.
Day, !LIMIT(Jan)
→ every day of the year except in January.
Mon, !LIMIT(Jul.Aug)
→ every Monday except during the summer break (July and August).
The ! here does not turn LIMIT into an inclusion — like plain LIMIT it
only ever removes dates. It just flips which months are removed: LIMIT(Jan)
removes everything but January; !LIMIT(Jan) removes only January. (See §20 on how
! changes meaning depending on where it sits.) So LIMIT(Jan) and !LIMIT(Jan)
are exact complements — together they cover the whole year with no overlap.
Scoping a LIMIT to a group with { }
Normally a LIMIT applies to the whole schedule. Wrap some rules in braces
{ … } to make a LIMIT (or any exclusion) apply only within that group:
{Mon, LIMIT(Jan.Mar)}, {Fri, LIMIT(Jul)}
→ Mondays in Jan & March, and Fridays in July — each LIMIT stays inside its
own group and doesn't touch the other.
{WD, !Fri}, Sun
→ (weekdays except Friday) plus every Sunday — the !Fri only applies to the
group, so it can't remove anything from the Sun rule.
A group must contain at least one normal (non-!) rule, and groups can't be nested.
17. Moon phases
For lunar schedules, use MOON(...).
MOON(FULL)
MOON(NEW)
You can target a named phase, an illumination percentage (0–100), or a range:
| Rule | Meaning |
|---|---|
MOON(FULL) |
The full moon |
MOON(NEW) |
The new moon |
MOON(100) |
Full moon (100% lit) |
MOON(0) |
New moon (0% lit) |
MOON(50..75) |
When the moon is 50–75% lit |
MOON(50, WAXING) |
50% lit while growing |
MOON(50..75, WANING) |
50–75% lit while shrinking |
A given brightness happens twice a month (once growing, once shrinking), so adding
WAXING or WANING picks just one side. (FIRST_HALF / SECOND_HALF are accepted
as synonyms; BOTH is the default.)
Named phases: NEW, WAXING_CRESCENT, FIRST_QUARTER, WAXING_GIBBOUS,
FULL, WANING_GIBBOUS, LAST_QUARTER, WANING_CRESCENT.
Example — MOON(FULL) (2025) → 2025-01-14, 2025-02-13, 2025-03-15, …
18. Easter and the days that move with it
Easter is worked out, not looked up. Write EASTER on its own for Easter Sunday, or
name the day you want in brackets:
EASTER
EASTER(GoodFriday)
EASTER(Monday)
| Rule | Meaning |
|---|---|
EASTER |
Easter Sunday |
EASTER(GoodFriday) |
Good Friday |
EASTER(Monday) |
Easter Monday |
EASTER(AshWednesday) |
Ash Wednesday, the start of Lent |
EASTER(MotheringSunday) |
Mothering Sunday |
EASTER(Pentecost) |
Whit Sunday |
EASTER(Orthodox) |
Orthodox Easter Sunday |
EASTER(GoodFriday, Orthodox) |
Orthodox Good Friday |
Example — EASTER → 2025-04-20, 2026-04-05, 2027-03-28, …
Example — EASTER(GoodFriday) (2026) → 2026-04-03; EASTER(Monday) → 2026-04-06
The days you can name, as offsets from Easter Sunday:
| Name | Days | Name | Days |
|---|---|---|---|
CleanMonday |
−48 | Sunday |
0 |
ShroveTuesday |
−47 | Monday |
+1 |
AshWednesday |
−46 | Ascension |
+39 |
MotheringSunday |
−21 | Pentecost / WhitSunday |
+49 |
PalmSunday |
−7 | WhitMonday |
+50 |
MaundyThursday |
−3 | TrinitySunday |
+56 |
GoodFriday |
−2 | CorpusChristi |
+60 |
HolySaturday |
−1 |
Two reckonings: Western (the default, and what the UK uses) or Orthodox.
The bracket takes up to one day and one reckoning, in either order, each optional:
EASTER(Monday, Orthodox) and EASTER(Orthodox, Monday) are the same rule. Any day
name works with either reckoning — it is only an offset — though AshWednesday,
ShroveTuesday, MotheringSunday and CorpusChristi are Western observances and
CleanMonday an Orthodox one.
Example — EASTER(Orthodox) (2026) → 2026-04-12
A few things worth knowing:
- Names are spelled exactly as in the table, like a category member
(
BankHolidays(GoodFriday)).EASTER(goodfriday)andEASTER(Tuesday)are rejected. EASTERis in capitals, likeMOONandWD, because the engine computes it. PascalCaseEasteris a category (§11) and still means whatever data you give it.- It is one date a year, so it composes with everything else here:
!EASTER(GoodFriday)excludes it,WD~<EASTER(GoodFriday)is the last working day before the break,Day__OF__WEEK(EASTER)is the week ending on Easter Sunday, andEASTER-P5Daystill works for a day that has no name. - Nothing to sync: unlike
BankHolidaysit needs no feed and works offline. For the England & Wales bank holidays specifically, both routes give the same two dates.
19. Intersections
Join rules with __&__ to require that every operand matches the same date:
13Month__&__Fri
→ Friday the 13th (the 13th of the month and a Friday).
| Rule | Meaning |
|---|---|
13Month__&__Fri |
The 13th of the month that is a Friday |
25Dec__&__MOON(FULL) |
Christmas Day that falls on a full moon |
Notes:
- Operands cannot be individually negated. To exclude the whole intersection,
put one
!in front of it:!13Month__&__Fri. - Intersections can be rare (a Christmas full moon is roughly every 19 years) — the engine searches a bounded horizon (~50 years) and then gives up.
20. Notes & limitations
A few things worth knowing:
- Data-backed rules need a data source. Categories (
BankHolidays), selectors (Tag(X),Event(X),Feed(X)) and theWORKDAYalias produce no dates unless the app supplies the matching data. On their own they resolve to nothing. MOON(...)andEASTER(...)are computed, not data-backed. They need no data source and work offline. Neither has anRRULEform, so an export writes their dates out to the export horizon instead.WORKDAYis an alias forTag(Work)— it behaves identically everywhere, including nested (1__WORKDAY__OF__MONTH). See §11 and §14.- How
!behaves depends on where it sits. On an ordinary rule it excludes (!Fri). On a~rule it negates the type (!WD~>25Dec= a non-weekday, still additive). On an intersection it excludes the whole thing (§19). On aLIMITit flips which months are removed (!LIMIT(Jan)= every month except January; §16) — it stays a filter, not an inclusion. You can't negate a single intersection operand, and you can't exclude a whole~rule. - Brace groups can't be nested, must hold at least one normal rule, and are
always additive (a leading
!on a group doesn't turn it into an exclusion).
21. Cheat sheet
| Pattern | Example | Reads as |
|---|---|---|
Day |
Day |
every day |
DayN |
Day3 |
every 3 days |
Weekly/Monthly/Yearly |
Monthly2 |
every 2 months |
Mon…Sun |
Fri |
every Friday |
<Day>N |
Fri2 |
every 2nd Friday |
NMonth |
5Month |
the 5th of each month |
-1Month |
-1Month |
last day of the month |
NDec |
25Dec |
every 25 December |
-NDec |
-1Feb |
last day of February |
N<Day> |
2Sat |
2nd Saturday of the month |
N_<Day> |
2_Sat |
Saturday in the 2nd week |
-N<Day> |
-1Thu |
last Thursday of the month |
WD / WE |
WD |
every weekday / weekend day |
NWD / NWE |
-1WD |
last weekday of the month |
WDn / WEn |
WD2 |
every 2nd weekday (by count) |
WD_n / WE_n |
WD_2 |
weekdays in the 2nd week of the month |
YYYY-MM-DD |
2025-05-17 |
that one date |
!<rule> |
!1Sat |
exclude |
<Category> |
BankHolidays |
a named group of dates |
<Cat>(Member) |
BankHolidays(Christmas) |
one date from a group |
Tag/Event/Feed(X) |
Tag(Work) |
dates from a tag/event/feed |
~= |
WD~=25Dec |
nearest weekday to a date |
!<type>~ |
!WD~>25Dec |
non-weekday after a date |
(Day.Day…) |
(Mon.Wed.Fri) |
any day in the set |
{ …, LIMIT(…) } |
{Mon, LIMIT(Jan.Mar)} |
scope a LIMIT to a group |
__OF__ |
-1__Fri__OF__MONTH |
last Friday of the month |
__OF__<Month> |
4__Thu__OF__Nov |
4th Thursday of November |
__OF__WEEK |
Mon__OF__WEEK5 |
Monday of ISO week 5 |
__OF__<period>(…) |
Fri__OF__WEEK(25Dec) |
the period containing another rule |
LIMIT(...) |
LIMIT(Jan.Mar) |
restrict to these months |
!LIMIT(...) |
!LIMIT(Jan.Mar) |
every month except these |
MOON(...) |
MOON(FULL) |
on the full moon |
EASTER(...) |
EASTER(GoodFriday) |
Easter, or a day that moves with it |
__&__ |
13Month__&__Fri |
matches both rules at once |
+P<n><Unit> |
25Dec+P1Day |
that many days/weeks/months/years later |
-P<n><Unit> |
25Dec-P1Day |
…and earlier — Christmas Eve |
(CLAMP) |
31Month(CLAMP) |
short months use their last day |
(NEXT) |
29Feb(NEXT) |
keep counting into the next month |
22. Recipes
| What you want | Rule |
|---|---|
| Every weekday except bank holidays | WD, !BankHolidays |
| Every Saturday except the first two of the month | Sat, !1Sat, !2Sat |
| Payday on the last weekday of the month | -1__WD__OF__MONTH |
| Payday on the last working day of the month | -1__Tag(Work)__OF__MONTH |
| Mondays and Thursdays, Q1 only | Mon, Thu, LIMIT(Jan.Feb.Mar) |
| US Thanksgiving | 4__Thu__OF__Nov |
| Fortnightly team meeting on Wednesdays | Wed2 |
| Bin collection every other Tuesday | Tue2 |
| The 5th of every other month | 5Month2 |
| Nearest weekday to Christmas | WD~=25Dec |
| Friday the 13th | 13Month__&__Fri |
| Full-moon events | MOON(FULL) |
| Good Friday / Easter Monday | EASTER(GoodFriday) / EASTER(Monday) |
| Weekdays, minus the Easter break | WD, !EASTER(GoodFriday), !EASTER(Monday) |
| Pancake day | EASTER(ShroveTuesday) |
| Mothering Sunday | EASTER(MotheringSunday) |