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(...) and EASTER(...) have their own pages, Moon rules and Easter rules.


Contents

  1. The three things to know first
  2. Every day
  3. Every N days / weeks / months / years
  4. A weekday every week
  5. A day of the month
  6. A day of the year
  7. "2nd Saturday of the month" style
  8. Weekdays and weekends
  9. A one-off date
  10. Excluding dates with !
  11. Categories, tags, events and feeds
  12. Move to a qualifying day with ~
  13. Moving dates by a fixed amount with + and -
  14. Powerful patterns with __OF__
  15. A specific ISO week of the year
  16. Limiting to certain months
  17. Moon phases
  18. Easter and the days that move with it
  19. Intersections
  20. Notes & limitations
  21. Cheat sheet
  22. 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     Yes every weekday except Friday
!Fri, WD     No 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, Monthly automatically 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, so NEXT never nudges the schedule along a month at a time.
  • -1Month is 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.

WORKDAY alias: WORKDAY is accepted as a shorthand for Tag(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. Usually WD or WE, but any rule works: WORKDAY, a weekday (MonSun), 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:

  1. Per anchor — for each 22nd, the nearest full moon. Many answers. This is ~.
  2. 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; write 25Dec.
  • ~ and __&__ bind looser than a shift, so WD~<25Dec-P1Day is the weekday before Christmas Eve, and Mon__&__Fri-P1Day intersects 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} is DAY, WEEK, MONTH, YEAR, or a specific month (JanDec).
  • {inner} is the kind of day to pick. Use WD, WE, a single day code (MonSun), 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) and EASTER(Tuesday) are rejected.
  • EASTER is in capitals, like MOON and WD, because the engine computes it. PascalCase Easter is 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, and EASTER-P5Day still works for a day that has no name.
  • Nothing to sync: unlike BankHolidays it 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 the WORKDAY alias produce no dates unless the app supplies the matching data. On their own they resolve to nothing.
  • MOON(...) and EASTER(...) are computed, not data-backed. They need no data source and work offline. Neither has an RRULE form, so an export writes their dates out to the export horizon instead.
  • WORKDAY is an alias for Tag(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 a LIMIT it 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
MonSun 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)