"This event only": the edit that quietly 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 stored rows — they’re expanded from the
rule on demand. That single fact is why editing a recurring event is one of the
sharpest edges in the whole 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 every calendar app asks the same question: this event, this and following, or all events? They’re three genuinely different operations against the data model:
- All events edits the master row and its rule. Easy — it’s the thing that actually exists.
- This and following splits the series: the old rule gets an end, 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 manufactures 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. Which is exactly the shape of #16: the popup appeared correctly, the other two options worked, and “this event only” silently did nothing. It wasn’t the save logic — it was the one branch that has to fabricate a row instead of updating one.
Why I let the provider handle it
It would be tempting to sidestep all of this by keeping my own event table and
doing the expansion myself. I deliberately don’t. Calendula reads and writes
through the system calendar provider, and recurrence exceptions are precisely
the kind of thing the provider — and the CalDAV sync adapter behind it — already
knows how to round-trip. An exception I write lands as a proper RECURRENCE-ID
override: DAVx5 pushes it to your server, and every other client that reads the
calendar sees 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 for two decades, and there’s no shortcut through it. The trade is that by living inside the standard, the difficulty is shared: when I get the original-instance bookkeeping right, the exception is correct everywhere, not just in my app.