STARTS_ON, ENDS_ON and ENDS_AFTER rules

Format:

{ {rules…}, STARTS_ON(YYYY-MM-DD) }
{ {rules…}, ENDS_ON(YYYY-MM-DD) }
{ {rules…}, ENDS_AFTER(N) }
{ {rules…}, STARTS_ON(YYYY-MM-DD), ENDS_ON(YYYY-MM-DD), ENDS_AFTER(N) }

STARTS_ON gives the pattern inside a brace group its own start date. Everything else in the group takes its phase and its floor from that date instead of the event's start date. ENDS_ON is the last day the group's pattern may fall on, inclusive, and ENDS_AFTER stops it after its Nth day. All three are modifiers, not rules: they produce no dates and remove none.

Why it exists

An event's start date phases its pattern (5Month2 counts every second month from it) and floors it (nothing falls before it). Tap a day on the calendar before the pattern begins and the two jobs collide: the series must now start on the tapped day, but the pattern must not move with it.

{5Month2, STARTS_ON(2024-02-05)}, 2024-01-10

→ from an event that starts on 1 January 2024: 10 January once, then the 5th of February, April and June. Without the anchor the pattern would count from January and land on the 5th of January, March and May.

The same happens at the far end. An event's end date caps everything in it, so a day tapped after the pattern ends is clipped unless the end date moves, and moving it lets the pattern run on to fill the gap:

{Mon, ENDS_ON(2024-01-15)}, 2024-02-05

→ from an event that starts on 2 January 2024: Mondays on 8 and 15 January, then 5 February once.

A count works the same way. An event that ends after N occurrences counts every day it is on, hand-picked ones included, so a day picked after the Nth was clipped and one picked before it pushed the pattern's last day out. ENDS_AFTER counts the group's own days:

{Mon, ENDS_AFTER(2)}, 2024-03-04

→ from an event that starts on 1 January 2024: Mondays on 1 and 8 January, then 4 March once. The count is of what the group produces after its own exclusions and limits, so an excluded Monday inside the group does not use one of the N. A day removed outside the group does.

Rules

  • Inside { } only. On their own at the top level they are rejected.
  • One of each per group. A group may carry all three; with both an ENDS_ON and an ENDS_AFTER, whichever comes first wins.
  • Never negated: !STARTS_ON(...), !ENDS_ON(...) and !ENDS_AFTER(...) are rejected.
  • Not the normal rule a group has to have: {STARTS_ON(2024-01-15)} is rejected.
  • A real date: STARTS_ON(2024-02-30) is rejected. A real count: ENDS_AFTER(0) is rejected.
  • Never ending before starting: {Mon, STARTS_ON(2024-02-05), ENDS_ON(2024-01-15)} is rejected. The same day for both is a one-day group, and allowed.
  • Cannot be shifted (+P/-P) or adjusted around (~), like LIMIT.
  • A LIMIT or an exclusion in the same group is scoped to the group exactly as it would be without the bounds.

Examples

{Mon, STARTS_ON(2024-01-15)}                    Mondays from 15 January 2024
{Mon2, STARTS_ON(2024-01-08)}                   every second Monday, counted from 8 January 2024
{Day, LIMIT(Feb), STARTS_ON(2024-02-27)}        27, 28, 29 February 2024, then every February
{5Month2, STARTS_ON(2024-02-05)}, 2024-01-10   the 5th of every other month from February, plus 10 January
{Mon, ENDS_ON(2024-01-15)}                      Mondays up to and including 15 January 2024
{Mon, ENDS_ON(2024-01-15)}, 2024-02-05          …plus 5 February once
{5Month2, STARTS_ON(2024-02-05), ENDS_ON(2024-05-31)}   5 February and 5 April 2024, and that is all
{Mon, ENDS_AFTER(4)}                            the first four Mondays
{Mon, !2024-01-08, ENDS_AFTER(4)}               four Mondays, skipping 8 January: 1, 15, 22, 29 January
{Mon, STARTS_ON(2024-01-15), ENDS_AFTER(3)}     three Mondays from 15 January

You never have to type it

The app writes them. Tap a day before a repeating event begins and the pattern is wrapped with its old start date as the anchor, the event's start date moves back to the tapped day, and the pattern carries on exactly as it was. Tap a day after it ends, whether on a date or after so many times, and the pattern is capped at its old end while the event's end date moves on. Tap the day off again and the wrapping goes away. The Repeat sheet shows the pattern without any of them, and reads "After N" back from the count.

Export

A group ended by ENDS_ON or ENDS_AFTER exports as the rule it holds, with UNTIL or COUNT on the RRULE; a day picked after the end is an ordinary RDATE. COUNT is only used where it means the same in both worlds, a lone group with nothing removed in or around it and no end on the series, because RFC 5545 counts before EXDATE removes; otherwise the group's last day becomes UNTIL. An RRULE is phased by DTSTART, so a group anchored by STARTS_ON later than the event's first day has no RRULE form and its dates are written out to the export horizon instead, like MOON and EASTER. UNTIL and COUNT read back on import as the series' end date and count, as they always did; the importer does not yet read RDATEs.

See the rule reference for the surrounding syntax.