By: Tom Sydney Kerckhove <syd@cs-syd.eu>
Reconcile the components of a UID when importing a calendar A component with a RECURRENCE-ID replaces the instance of its series that it names rather than adding an occurrence beside it, and Google and Outlook write one for every "this event only" edit. Recurring one component at a time cannot know that, so the edited instance was imported twice: once where it used to be and once where it now is. 'deduplicateBasedOnId' was the hand-rolled version of this. It sorted groups by their descending first occurrence on the theory that the earliest-starting group is the original, then dropped any occurrence already seen. Because the key was the whole occurrence, start and end, it only worked when the override changed neither, which is the uncommon case: move the override's DTEND by a minute and the duplicate came back. It also deduplicated across UIDs, which nothing justifies. It is deleted. ical-recurrence 0.3.0.0 reconciles a UID's components, so the library does this now, and correctly. 'RecurringEvents' therefore has to carry what reconciliation needs from each component, so 'RecurringEvent' gains its SEQUENCE and its RECURRENCE-ID. There is still no UID field, because the map is keyed by it; 'recurringOf' puts the two back together for 'ICal.recur'. Both new fields are omitted at their defaults, so an old picked file still parses and means what it used to. The pipeline keeps its shape of one 'Static' plus a set of start and end pairs. What changes is the grouping key: reconciliation can put occurrences from two different components in one UID's recurrence set, and each occurrence carries the component it came from, so 'Recur' groups by that component rather than by UID. 'ResolveZones' and everything below it are unchanged apart from the renamed types. 'Pick' can no longer drop the components it does not want, so 'Static' gains an 'Inclusion' and 'Recur' drops the excluded occurrences after reconciliation instead. Both of the filters need this: * STATUS:CANCELLED on a component with a RECURRENCE-ID is how "delete this one instance" is written. Dropping that component before the recurrence set is reconciled makes the series' instance reappear at that time instead of disappearing, which was a live bug. * SMOS_NO_CALENDAR_IMPORT is a per-component opt-out, and an overriding component has a description of its own. Goldens, all checked by hand against a separately derived expectation: * test_resources/recurrence-id.* is new, and is the case that matters: a conforming RECURRENCE-ID moving one instance of a weekly series. Its .unresolved shows the series keeping the two instances it still has and no longer the one that was overridden. * uid.ics keeps its malformed RECURRENCE-ID, which is zoned where the series' DTSTART is floating (RFC 5545 section 3.8.4.4 forbids that) and names a Friday where the series generates Mondays. It matches no instance, so it is now an orphan: the override is kept, the series still produces the instance the override failed to name, and RecurrenceIdentifierUnmatched says the input needs fixing. That is one more occurrence than before, and the old output was luck rather than correctness: 'deduplicateBasedOnId' happened to drop the series' instance because the override's start and end were byte-identical to it. * ignored.recurring is no longer empty, because the component it holds is now picked with include: false rather than dropped. Everything downstream of it is still empty. * museum.recurring and exception.recurring gain a sequence field, because those two components have a SEQUENCE other than 0. Three more move for the ical bump alone rather than for anything here: museum.parsed and visit.parsed gain the X-WR-TIMEZONE that those calendars always had and the library now round-trips, and zoned-recurring.unresolved loses a duplicate instance, which is the already-fixed bug where DTSTART and the recurrence rule both produced the first instance with two different ends.
Total
43m45s
Created Started Finished
| Time to Start | Worker time | Duration | Time to finish | Idle | |
|---|---|---|---|---|---|
| Config | 41s | 1s | 1s | 43s | 41s |
| Eval | 46s | 40s | 40s | 1m27s | 2s |
| Build | 1m23s | 59m44s | 43m02s | 44m26s | 12s |
| Suite | 41s | 1h00m27s | 43m45s | 44m26s | 56s |