sabre-io / vobject

:date: The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects
http://sabre.io/vobject/
BSD 3-Clause "New" or "Revised" License
569 stars 125 forks source link

Yearly recurrence with negative BYWEEKNO #621

Open zero0cool0 opened 1 year ago

zero0cool0 commented 1 year ago

I was testing some more complex recurrence rules and believe I've hit a bug in yearly recurrence rule that contains a negative week number. RFC 5545 Section 3.3.10 is a little vague about this; the way I read it, a negative week number should count the week numbers from the end of the year. So in this particular case below with a given interval of 3, shouldn't the expected date of the second recurrence be in year 2029 instead of 2028? More precisely, as 2029 has 52 weeks (assuming week start at Monday), I would expect Monday 2029-12-10 (week 50)

Happy to submit a PR about this, but wanted to verify first if this is expected behaviour or not.

$vcalendar = VObject\Reader::read(
"
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject 2.0//EN
BEGIN:VEVENT
UID:1102c450-e0d7-11e1-9b23-0800200c9a66
DTSTART:20260317T170000Z
RRULE:FREQ=YEARLY;INTERVAL=3;BYDAY=MO,TU;BYWEEKNO=-2
END:VEVENT
END:VCALENDAR
"
);

$newVCalendar = $vcalendar->expand(new DateTime('2026-01-01'), new DateTime('2030-12-31'));

print_r($newVCalendar->serialize());

Output:

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject 4.5.3//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
UID:1102c450-e0d7-11e1-9b23-0800200c9a66
DTSTART:20260317T170000Z
RECURRENCE-ID:20260317T170000Z
END:VEVENT
BEGIN:VEVENT
UID:1102c450-e0d7-11e1-9b23-0800200c9a66
DTSTART:20281211T170000Z
RECURRENCE-ID:20281211T170000Z
END:VEVENT
BEGIN:VEVENT
UID:1102c450-e0d7-11e1-9b23-0800200c9a66
DTSTART:20301209T170000Z
RECURRENCE-ID:20301209T170000Z
END:VEVENT
END:VCALENDAR
phil-davis commented 5 months ago

DTSTART:20260317T170000Z is not actually a date that matches the rule. There has been discussion about that sort of thing, and we try to schedule an event at DTSTART and then the events after that that match the rule.

I think that there should be events scheduled on:

But I can see why the code might schedule for 2028 - it skips the possible events in December 2026 and 2027, and schedules in the 3rd year, 2028.

I have no idea why it then only skips 2029 and schedules in 2030 - that is strange.

If you have code fixes, please make a PR and we can discuss.