-
Notifications
You must be signed in to change notification settings - Fork 248
Fix: Event with RECURRENCE-ID overrides occurrence from orginal event
#864
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Codecov Report❌ Patch coverage is
❌ Your patch status has failed because the patch coverage (65.9%) is below the target coverage (80.0%). You can increase the patch coverage or adjust the target coverage. @@ Coverage Diff @@
## main #864 +/- ##
=======================================
- Coverage 68.0% 67.9% -0.1%
=======================================
Files 112 112
Lines 4267 4297 +30
Branches 956 973 +17
=======================================
+ Hits 2901 2917 +16
+ Misses 1038 1037 -1
- Partials 328 343 +15
... and 1 file with indirect coverage changes 🚀 New features to boost your workflow:
|
|
@axunonb I applied this fix to my earlier branch and minimized the ics data but that still seems to not generate all occurrences: main...ramonsmits:ical.net:occurence-issue I also pushed the same test but for v4 that does generate the expected occurrences: |
|
Yes, I didn't use the same DTSTART in Master and Override event. That seems to be the reason, and I got excited too soon ;-) |
|
@ramonsmits Should work now also when the Override Event has the same DTSTART |
Filter UID/RECURRENCE-ID combinations that replace other occurrences before using `.OrderedDistinct()`
830295e to
0c92c39
Compare
| .OrderedMergeMany() | ||
|
|
||
| // Remove duplicates and take advantage of being ordered to avoid full enumeration. | ||
| .OrderedDistinct() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the expected order? Looking at the implementation of OrderedDistinct it it keeps the order of the enumerator and the first of its "sequence" is kept. However, in the code above I do not see any sorting happening. Is there some order implied from the RecurringItems items?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It ordered by Period.StartTime
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually the question is legit. The sequence is ordered by StartTime, true, but OrderedDistinct() uses Ocurrences.Equals() to compare for equality, which compares more than just the StartTime, i.e. also the Source. But the sequence is not ordered by Source, so this could become a problem.
Actually the real core of the problem we are dealing with here is a shortcoming (or misuse) of Occurrences.Equals, which compares the Source property via UniqueComponent.Equals under the hood, which in turn only looks at the objects' Uid. In case of VEVENTs with RECURRENCE-ID there are multiple objects having the same UID. In the case of this issue they also have the same DTSTART, so the resulting Occurrence instances are considered equal although they aren't. OrderedDistinct() will therefore, more or less randomly, return only one of both (except if there are even more entries with the same StartDate and the other issue kicks in that the sequence is not properly ordered due to the Source property). As a consequence the filter for the RECURRENCE-ID that has formerly been placed after the OrderedDistinct() couldn't see it.
While this change is a significant improvement, it is not a final fix for that problem. Actually it should be clearly defined how Occurrence instances are compared for equality and I'd say this should not happen based on just the UID. Actually I'd say that two Occurrences should be considered equal only if the Sources are equal by reference.
|
btw, tested last against my own ical and it seems to now correctly generate the occurrences 💪 |
* Move tests from GetOccurrencesTests to RecurrenceTests * Delete GetOccurrencesTests * Minor refactoring of some unit tests
0c92c39 to
c79005e
Compare
minichma
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| } | ||
|
|
||
| [Test] | ||
| public void GetOccurrencesWithRecurrenceId_DateOnly_ShouldEnumerate() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new tests actually don't reproduce the original issue. Maybe add the test from the ticket?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will flag the tests with Category `RECURRENCE-ID" and add a comment referencing to the test covering the #863
Ical.Net/Calendar.cs
Outdated
| .Where(r => r.Uid != null) | ||
| // Create a value tuple instead of an anonymous type, because | ||
| // anonymous types don't work well as dictionary keys due to equality semantics. | ||
| .Select(r => ((r as IUniqueComponent)?.Uid, r.RecurrenceId!.Value)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fully agree that it is better to use tuples here rather than anonymous types but I don't think this is the root cause of the problem (as suspected in the issue's comments).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fully agree
Ical.Net/Calendar.cs
Outdated
| // Exclude occurrences that are overridden by other components with the same UID and RECURRENCE-ID. | ||
| // This must happen before .OrderedDistinct() because that method would remove duplicates | ||
| // based on the occurrence time, and we need to remove them based on UID + RECURRENCE-ID. | ||
| .Where(r => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably just a matter of taste: For simplicity I'd rather place the filter after the ToList(). The idea is that before the ToList() we only deal with the individual sequences of occurrences, but not with the occurrences themselves, which is done only after the ToList(). If the filter is placed here, the effect is the same, but the code is less linear to read. I suggest to place it after the OrderedMergeMany() and before the OrderedDistinct().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw, due to #455 it might be a good idea to keep this filter here, or at least before the OrderedMerge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will leave the position unchanged
| .OrderedMergeMany() | ||
|
|
||
| // Remove duplicates and take advantage of being ordered to avoid full enumeration. | ||
| .OrderedDistinct() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually the question is legit. The sequence is ordered by StartTime, true, but OrderedDistinct() uses Ocurrences.Equals() to compare for equality, which compares more than just the StartTime, i.e. also the Source. But the sequence is not ordered by Source, so this could become a problem.
Actually the real core of the problem we are dealing with here is a shortcoming (or misuse) of Occurrences.Equals, which compares the Source property via UniqueComponent.Equals under the hood, which in turn only looks at the objects' Uid. In case of VEVENTs with RECURRENCE-ID there are multiple objects having the same UID. In the case of this issue they also have the same DTSTART, so the resulting Occurrence instances are considered equal although they aren't. OrderedDistinct() will therefore, more or less randomly, return only one of both (except if there are even more entries with the same StartDate and the other issue kicks in that the sequence is not properly ordered due to the Source property). As a consequence the filter for the RECURRENCE-ID that has formerly been placed after the OrderedDistinct() couldn't see it.
While this change is a significant improvement, it is not a final fix for that problem. Actually it should be clearly defined how Occurrence instances are compared for equality and I'd say this should not happen based on just the UID. Actually I'd say that two Occurrences should be considered equal only if the Sources are equal by reference.
8adedbb to
bae25e0
Compare
* Allow more than one `VEVENT` with same `UID` and `RECURRENCE-ID` (threw before) * Use the latest `UID` / `RECURRENCE-ID`: * either last override in the calendar * or override with the highest `SEQUENCE`
bae25e0 to
3cb8fdd
Compare
|
minichma
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice!
|
@minichma Thanks for your support and review |
|
@axunonb urw |



Resolves #863
Resolves #865