Event exceptions β
Recurring events repeat by rule. Real life doesn't. Christmas falls on a Sunday and bumps the regular morning service to an evening slot; a public holiday closes the building and you cancel one Wednesday's prayer meeting; a guest speaker means a one-week title change. Exceptions are how GCM handles those edits without rewriting the underlying RRULE.
Two mechanisms, one purpose β
GCM has two ways to make a single date behave differently from the rest of a series:
events.exception_datesβ adate[]column on the event itself. Add a date here and that occurrence disappears from the calendar. No table join, no extra row.event_exceptionstable β one row per override, with fields for canceling, retitling, retiming, or relocating one occurrence.
The first is for the simple case ("just skip this date"). The second is for everything else.
event_exceptions(
id, event_id, exception_date,
is_cancelled, override_title,
override_start_time, override_end_time,
override_location, created_at
)The exception_date is the date the parent event would have fallen on, before the override took effect. Every other field is optional β anything you leave null falls through to the parent event's value.
Cancelling one occurrence β
The most common case: a regular Sunday Service is suspended because the building is being painted, or a weekly prayer meeting is skipped for a public holiday.
Two equivalent ways to do it:
- Quick path β add the date to
events.exception_dates. The occurrence disappears from the calendar grid. - Auditable path β insert an
event_exceptionsrow withis_cancelled = true. The occurrence also disappears, but you now have a record of why it was cancelled (visible to admins when they open the date).
For occasional skips, the quick path is enough. For cancellations you might need to explain later (refund chasers, attendance disputes), the auditable path leaves a trail.
Moving one occurrence β
When the occurrence still happens but at a different time or place, use the event_exceptions row to override just the fields that change. The rest cascade from the parent event:
INSERT INTO event_exceptions (event_id, exception_date, is_cancelled,
override_start_time, override_location)
VALUES ('<id>', '2026-12-24', false,
'19:00', 'Sanctuary β candlelight setup');On December 24th the calendar still shows the event, but the time chip reads 7:00 PM and the location reads "Sanctuary β candlelight setup." The series keeps repeating on its normal Sunday morning rhythm afterwards.
Override fields available:
override_titleβ replace the title for that one date. "Sunday Service" becomes "Christmas Eve Service."override_start_time/override_end_timeβ shift the times. Use both if the duration also changes.override_locationβ point attendees somewhere else for the day.
If you need to override fields the exception table doesn't carry β colour, description, branch β the override isn't expressive enough. You'll need to fork: delete the occurrence with an exception, then create a separate one-off event for the changed date.
Exceptions vs RRULE updates β
A frequent question: should I edit the recurrence rule, or add an exception? The rule of thumb:
- One or two dates change β add exceptions. The rule keeps describing the normal pattern, and the exceptions describe the deviations.
- The pattern itself changes going forward β edit the RRULE. New end date, new weekday, new monthly mode. The rule should always describe what is normal, not what was normal.
- Going from weekly to biweekly mid-year β end the current rule with
UNTIL=<switchover>, then create a new event with the new rule starting after that date. Don't try to encode the transition inside a single RRULE βINTERVALchanges mid-series aren't representable.
The wrong move is to delete the recurring event when something changes, because deletion soft-removes every occurrence including past ones tied to attendance and reports. The right move is almost always: add an exception, or end the current series and start a new one.
How exceptions render β
In the month and upcoming views, exception logic runs client-side as part of the RRULE expansion:
- The browser asks the parent event "what dates would you fall on in this window?"
- It filters out any date in
events.exception_dates. - It filters out any date where
event_exceptions.is_cancelled = true. - For the dates that survive, it overlays any non-cancelled
event_exceptionsrow's override fields on top of the parent event's defaults.
That means an exception with is_cancelled = false plus override_title = 'Christmas Eve Service' shows up on the grid with the new title but the parent's time, location, and colour β unless those are also overridden.
When to think differently β
If you find yourself adding many exceptions to one event, the pattern is wrong. A Sunday Service that gets retitled six times a year for special services is a sign you should keep "Sunday Service" boring and add separate one-off events for the special dates β Christmas Eve, Easter, etc. Exceptions are best as the rare correction, not the normal mode.
For the rules that govern the normal pattern, see Recurring patterns. For one-off dates that aren't tied to a recurring series at all, just create a new event.