fix: move EXDATE with the series when its times change (#248)
Dropping a single occurrence on a calendar with no _sync_id records it in the master row's EXDATE (the #47 fix; synced calendars use a cancelled exception instead and are unaffected). An EXDATE stamp is an absolute instant — it excludes an occurrence only while the series keeps generating one at exactly that instant — and nothing ever rewrote it when the anchor moved. buildEventUpdateValues shifts DTSTART on any time change and rewrites RRULE/DURATION/zone/all-day, so every occurrence regenerates elsewhere while the stamps stay behind matching nothing: the occurrence the user deleted comes back. updateEventFromOccurrence is worse — it splits the series by inserting a fresh event built from the form, which carries no EXDATE at all, so every exclusion in the tail is lost. Shift each stamp by the same wall-clock delta the anchor takes, re-resolved in the event's (possibly new) zone. Reusing the anchor's rule is what keeps an exclusion pinned to its occurrence when a DST boundary sits between the two, or the zone itself changed; a millisecond delta would bake in the offset that happened to apply at the edited occurrence. Parsing reads all-day-ness from the old form and writing takes it from the new one, so the VALUE=DATE and date-time forms convert into each other when the event switches — the wrong form matches no occurrence and loses the exclusion just as surely as the wrong instant. Stamps in a shape Calendula never writes (a TZID-parameterised or floating one from a sync adapter) are left exactly as they are, whole list included: a stale stamp excludes nothing, but a mangled one could exclude the wrong occurrence. For the split, the parent's stamps past the cutoff are re-timed onto the new series. The one *at* the split point is dropped: it names the occurrence being edited, which exists by definition, and honouring a stale exclusion for it would swallow the edit whole. The parent keeps its full list — stamps past the truncation are inert once it stops generating those occurrences. The write repeats the whole time/recurrence set for the reason #47 documents (an EXDATE-only update is not read as a recurrence change), and a failure rolls the new series back before the parent is truncated, so the split fails whole rather than landing with the exclusions dropped. Dropping the recurrence now clears EXDATE with the RRULE. Dormant rather than harmless: adding a recurrence back later would punch the old holes into the new one. This reaches every calendar type through the "all events" path, including synced ones, where an upstream EXDATE must move with the series for the same reason — and reaches a calendar move too, which copies EXDATE verbatim and then applies the field edits as a normal series update. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
13
CHANGELOG.md
13
CHANGELOG.md
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- **A deleted occurrence no longer comes back when the series is re-timed.**
|
||||
Delete a single occurrence of a repeating event, then change the time of the
|
||||
whole series, and the occurrence you had removed reappeared. Doing it the
|
||||
other way round — "This and all following events" — lost every removal in the
|
||||
part of the series being changed. Removals now travel with the event: they
|
||||
keep their place in the series across a time change, a move to a different
|
||||
day, a timezone change and a switch to or from an all-day event, and they
|
||||
carry over when a series is split. Repeating events on your device's own
|
||||
calendars are affected, including the birthday and anniversary calendars
|
||||
Calendula creates from your contacts ([#248]).
|
||||
|
||||
## [2.19.3] — 2026-08-22
|
||||
|
||||
### Added
|
||||
@@ -1471,3 +1483,4 @@ automatically, with zero telemetry and no internet permission.
|
||||
[#192]: https://codeberg.org/jlmakiola/calendula/issues/192
|
||||
[#196]: https://codeberg.org/jlmakiola/calendula/issues/196
|
||||
[#214]: https://codeberg.org/jlmakiola/calendula/issues/214
|
||||
[#248]: https://codeberg.org/jlmakiola/calendula/issues/248
|
||||
|
||||
Reference in New Issue
Block a user