"This event only" vs "all events": the edit that did nothing
A recurring event isn’t a list of events. It’s one row plus a rule. “Every
Tuesday at 10:00” is a single event with an
RRULE, and
the individual Tuesdays don’t exist as events of their own. They’re expanded
from the rule, and the provider keeps those expansions in its Instances table
only as a cache it can throw away and rebuild. That’s why editing a recurring
event is one of the sharpest edges in the calendar model, and why Calendula had
a bug (#16) where choosing “this event only” and saving bounced you back to the edit screen
without changing anything.
Three answers to one save
Tap save on a recurring event and calendar apps generally ask the same question: this event, this and following, or all events? Each answer is a different operation against the data model:
- All events edits the master row and its rule. That’s the easy one, because the master row actually exists.
- This and following splits the series. The old rule gets an end date, and a new event picks up from the edit point with the changes.
- This event only is the awkward one. You’re editing an instance that has no row of its own. To change it, you have to create one.
What “this event only” actually does
Under the hood, “this event only” doesn’t edit anything. It creates an
exception. The iCalendar model calls it a
RECURRENCE-ID
override: a new component that says “for the occurrence that would have landed
at this original time, use these values instead.” Android’s
CalendarContract
exposes this through the exceptions URI and an ORIGINAL_INSTANCE_TIME: you
insert a one-off event pinned to the timestamp of the occurrence you’re
replacing, and the provider excludes the original instance from the expansion in
its place.
So the operation is: identify the exact original instance time, insert an exception bound to it, and let the provider stop expanding the rule at that slot. Get the original-time bookkeeping wrong and the provider has nothing valid to attach the exception to. The write doesn’t land, and the UI, having nothing to show for the save, falls back to the edit screen. That matches #16 exactly: the popup appeared correctly, the other two options worked, and “this event only” silently did nothing. The general save logic was fine. The bug was in the one branch that has to create a row instead of updating one.
Why I let the provider handle it
I could sidestep all of this by keeping my own event table and doing the
expansion myself. I don’t. Calendula reads and writes through the system
calendar provider, and the provider and the CalDAV sync adapter behind it
already know how to round-trip recurrence exceptions. An exception I write lands as a proper RECURRENCE-ID
override. DAVx5 pushes it to your server, and other clients that read the
calendar see the same override. If I expanded rules into my own private rows,
I’d own every one of these edge cases forever, and my “edits” would be invisible
to everything else that reads your calendar.
The hard part stays hard either way. Recurrence has been accreting rules and corrections in RFC 5545 and its predecessor RFC 2445 since 1998, and I haven’t found a shortcut through it. Working inside the standard means the result is shared: when I get the original-instance bookkeeping right, the exception is correct in every client that reads the calendar.