Skip to content

Commit ac0ad56

Browse files
recur_expansion: EXDATE can be DATE and DTSTART can be DATE-TIME
When EXDATE is in DATE format and DTSTART is DATE-TIME, then to determine whether an occurrence shall be excluded, the occurrence shall be converted to DATE and then compared to EXDATE. This applies also for the very first EXDATE, when it coincides with DTSTART.
1 parent 38eeea5 commit ac0ad56

File tree

3 files changed

+42
-3
lines changed

3 files changed

+42
-3
lines changed

lib/ical/recur_expansion.js

+21-3
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,21 @@ class RecurExpansion {
198198
}
199199
}
200200

201+
/**
202+
* Compare two ICAL.Time objects. When the second parameter is a timeless date
203+
* and the first parameter is date-with-time, strip the time and compare only
204+
* the days.
205+
*
206+
* @private
207+
* @param {ICAL.Time} a The one object to compare
208+
* @param {ICAL.Time} b The other object to compare
209+
*/
210+
_compare_special(a, b) {
211+
if (!a.isDate && b.isDate)
212+
return new Time({ year: a.year, month: a.month, day: a.day }).compare(b);
213+
return a.compare(b);
214+
}
215+
201216
/**
202217
* Retrieve the next occurrence in the series.
203218
* @return {ICAL.Time}
@@ -248,9 +263,10 @@ class RecurExpansion {
248263

249264
// check the negative rules
250265
if (this.exDate) {
251-
compare = this.exDate.compare(this.last);
266+
//EXDATE can be in DATE format, but DTSTART is in DATE-TIME format
267+
compare = this._compare_special(this.last, this.exDate);
252268

253-
if (compare < 0) {
269+
if (compare > 0) {
254270
this._nextExDay();
255271
}
256272

@@ -397,10 +413,12 @@ class RecurExpansion {
397413
if (component.hasProperty('exdate')) {
398414
this.exDates = this._extractDates(component, 'exdate');
399415
// if we have a .last day we increment the index to beyond it.
416+
// When DTSTART is in DATE-TIME format, EXDATE is in DATE format and EXDATE is
417+
// the date of DTSTART, _compare_special finds this out and compareTime fails.
400418
this.exDateInc = binsearchInsert(
401419
this.exDates,
402420
this.last,
403-
(a, b) => a.compare(b)
421+
this._compare_special
404422
);
405423

406424
this.exDate = this.exDates[this.exDateInc];

samples/rdate_exdate.ics

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
BEGIN:VCALENDAR
2+
BEGIN:VEVENT
3+
UID:123
4+
DTSTART:20240609T030000Z
5+
RRULE:FREQ=DAILY;INTERVAL=1;COUNT=4
6+
EXDATE;VALUE=DATE:20240611
7+
END:VEVENT
8+
END:VCALENDAR

test/recur_expansion_test.js

+13
Original file line numberDiff line numberDiff line change
@@ -329,4 +329,17 @@ suite('recur_expansion', function() {
329329

330330
});
331331

332+
suite('EXDATE and DTSTART have different value type', function() {
333+
createSubject('rdate_exdate.ics');
334+
test('Compare EXDATE;VALUE=DATE and DTSTART;VALUE=DATE-TIME', function() {
335+
let dates = [], next;
336+
while ((next = subject.next()))
337+
dates.push(next.toJSDate());
338+
assert.deepEqual(dates, [
339+
new Date('2024-06-09T03:00:00.000Z'),
340+
new Date('2024-06-10T03:00:00.000Z'),
341+
new Date('2024-06-12T03:00:00.000Z')
342+
]);
343+
});
344+
});
332345
});

0 commit comments

Comments
 (0)