"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:

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.