Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions src/IcalParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
19 changes: 19 additions & 0 deletions tests/cal/75_weekly_tuesday_thursday.ics
Original file line number Diff line number Diff line change
@@ -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
36 changes: 36 additions & 0 deletions tests/events.recurring.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
});