diff --git a/src/IcalParser.php b/src/IcalParser.php index 7c43442..06cc14d 100644 --- a/src/IcalParser.php +++ b/src/IcalParser.php @@ -215,19 +215,31 @@ public function parseRecurrences($event): array { // This guard only works on WEEKLY, because the others have no fixed time interval // There may still be a bug with the others if (isset($event['RRULE']['INTERVAL']) && $recurring->getFreq() === "WEEKLY") { + $byDay = $recurring->getByDay(); + $hasMultipleDays = $byDay !== false && is_array($byDay) && count($byDay) > 1; $replacementList = []; - - foreach($recurrenceTimestamps as $timestamp) { + + foreach ($recurrenceTimestamps as $timestamp) { $tmp = new DateTime('now', $event['DTSTART']->getTimezone()); $tmp->setTimestamp($timestamp); - + $dayCount = $event['DTSTART']->diff($tmp)->format('%a'); - - if ($dayCount % ($event['RRULE']['INTERVAL'] * 7) == 0) { - $replacementList[] = $timestamp; + + if ($hasMultipleDays) { + // For multiple days in BYDAY, check if the occurrence is in a valid week + // A valid week is one where the week number from DTSTART is divisible by INTERVAL + $weekCount = intdiv($dayCount, 7); + if ($weekCount % $event['RRULE']['INTERVAL'] == 0) { + $replacementList[] = $timestamp; + } + } else { + // For single day in BYDAY, the day must be exactly INTERVAL*7 days from DTSTART + if ($dayCount % ($event['RRULE']['INTERVAL'] * 7) == 0) { + $replacementList[] = $timestamp; + } } } - + $recurrenceTimestamps = $replacementList; } diff --git a/tests/cal/75_weekly_tuesday_thursday.ics b/tests/cal/75_weekly_tuesday_thursday.ics new file mode 100644 index 0000000..2450700 --- /dev/null +++ b/tests/cal/75_weekly_tuesday_thursday.ics @@ -0,0 +1,19 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Test//Test//EN +BEGIN:VEVENT +DESCRIPTION:Test Event +RRULE:FREQ=WEEKLY;UNTIL=20240430T220000Z;INTERVAL=1;BYDAY=TU,TH;WKST=MO +UID:test-issue-75 +SUMMARY:Test Event TU,TH +DTSTART;VALUE=DATE:20240326 +DTEND;VALUE=DATE:20240327 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20240326T082436Z +TRANSP:TRANSPARENT +STATUS:CONFIRMED +SEQUENCE:0 +LOCATION: +END:VEVENT +END:VCALENDAR diff --git a/tests/events.recurring.phpt b/tests/events.recurring.phpt index 9606992..24ec02c 100644 --- a/tests/events.recurring.phpt +++ b/tests/events.recurring.phpt @@ -257,4 +257,40 @@ test('Recurring instances bi-weekly', function () { Assert::equal('31.1.2023 05:00:00', $events[0]['DTSTART']->format('j.n.Y H:i:s')); Assert::equal('14.2.2023 05:00:00', $events[1]['DTSTART']->format('j.n.Y H:i:s')); Assert::equal('28.2.2023 05:00:00', $events[2]['DTSTART']->format('j.n.Y H:i:s')); +}); + +test('Weekly recurring with Tuesday and Thursday', function () { +// https://github.com/OzzyCzech/icalparser/issues/75 + $cal = new IcalParser(); + + $cal->parseFile(__DIR__ . '/cal/75_weekly_tuesday_thursday.ics'); + $events = $cal->getEvents()->sorted(); + +// DTSTART;VALUE=DATE:20240326 (Tuesday, March 26, 2024) +// RRULE:FREQ=WEEKLY;UNTIL=20240430T220000Z;INTERVAL=1;BYDAY=TU,TH;WKST=MO +// Should return both Tuesdays and Thursdays: 6 Tuesdays + 5 Thursdays = 11 events + Assert::equal(11, $events->count()); + + // Week 1: Tue Mar 26, Thu Mar 28 + Assert::equal('26.3.2024 00:00:00', $events[0]['DTSTART']->format('j.n.Y H:i:s')); + Assert::equal('28.3.2024 00:00:00', $events[1]['DTSTART']->format('j.n.Y H:i:s')); + + // Week 2: Tue Apr 2, Thu Apr 4 + Assert::equal('2.4.2024 00:00:00', $events[2]['DTSTART']->format('j.n.Y H:i:s')); + Assert::equal('4.4.2024 00:00:00', $events[3]['DTSTART']->format('j.n.Y H:i:s')); + + // Week 3: Tue Apr 9, Thu Apr 11 + Assert::equal('9.4.2024 00:00:00', $events[4]['DTSTART']->format('j.n.Y H:i:s')); + Assert::equal('11.4.2024 00:00:00', $events[5]['DTSTART']->format('j.n.Y H:i:s')); + + // Week 4: Tue Apr 16, Thu Apr 18 + Assert::equal('16.4.2024 00:00:00', $events[6]['DTSTART']->format('j.n.Y H:i:s')); + Assert::equal('18.4.2024 00:00:00', $events[7]['DTSTART']->format('j.n.Y H:i:s')); + + // Week 5: Tue Apr 23, Thu Apr 25 + Assert::equal('23.4.2024 00:00:00', $events[8]['DTSTART']->format('j.n.Y H:i:s')); + Assert::equal('25.4.2024 00:00:00', $events[9]['DTSTART']->format('j.n.Y H:i:s')); + + // Week 6: Tue Apr 30 (last one before UNTIL) + Assert::equal('30.4.2024 00:00:00', $events[10]['DTSTART']->format('j.n.Y H:i:s')); }); \ No newline at end of file