From de22119775cd01ebf39df5afdb48d97fd5b8945a Mon Sep 17 00:00:00 2001 From: SebastianKrupinski Date: Mon, 21 Oct 2024 14:08:43 -0400 Subject: [PATCH] fix: RDATE and EXDATE property instances Signed-off-by: SebastianKrupinski --- apps/dav/lib/CalDAV/EventReader.php | 12 +- apps/dav/lib/CalDAV/Schedule/IMipService.php | 78 ++-- .../dav/tests/unit/CalDAV/EventReaderTest.php | 27 ++ .../unit/CalDAV/Schedule/IMipServiceTest.php | 427 +++++++++++++++++- 4 files changed, 496 insertions(+), 48 deletions(-) diff --git a/apps/dav/lib/CalDAV/EventReader.php b/apps/dav/lib/CalDAV/EventReader.php index 6e845c596ae..ac9092afc66 100644 --- a/apps/dav/lib/CalDAV/EventReader.php +++ b/apps/dav/lib/CalDAV/EventReader.php @@ -197,8 +197,12 @@ class EventReader { } // evaluate if RDATE exist and construct iterator if (isset($this->baseEvent->RDATE)) { + $dates = []; + foreach ($this->baseEvent->RDATE as $entry) { + $dates[] = $entry->getValue(); + } $this->rdateIterator = new EventReaderRDate( - $this->baseEvent->RDATE->getValue(), + implode(',', $dates), $this->baseEventStartDate ); } @@ -211,8 +215,12 @@ class EventReader { } // evaluate if EXDATE exist and construct iterator if (isset($this->baseEvent->EXDATE)) { + $dates = []; + foreach ($this->baseEvent->EXDATE as $entry) { + $dates[] = $entry->getValue(); + } $this->edateIterator = new EventReaderRDate( - $this->baseEvent->EXDATE->getValue(), + implode(',', $dates), $this->baseEventStartDate ); } diff --git a/apps/dav/lib/CalDAV/Schedule/IMipService.php b/apps/dav/lib/CalDAV/Schedule/IMipService.php index 5b6efba9909..e0d51edc177 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipService.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipService.php @@ -154,7 +154,7 @@ class IMipService { $data['meeting_when_html'] = $oldMeetingWhen !== $data['meeting_when'] ? sprintf("%s
%s", $oldMeetingWhen, $data['meeting_when']) : $data['meeting_when']; } - // generate occuring next string + // generate occurring next string if ($eventReaderCurrent->recurs()) { $data['meeting_occurring'] = $this->generateOccurringString($eventReaderCurrent); } @@ -163,7 +163,7 @@ class IMipService { } /** - * genarates a when string based on if a event has an recurrence or not + * generates a when string based on if a event has an recurrence or not * * @since 30.0.0 * @@ -179,7 +179,7 @@ class IMipService { } /** - * genarates a when string for a non recurring event + * generates a when string for a non recurring event * * @since 30.0.0 * @@ -188,8 +188,8 @@ class IMipService { * @return string */ public function generateWhenStringSingular(EventReader $er): string { - // calculate time differnce from now to start of event - $occuring = $this->minimizeInterval($this->timeFactory->getDateTime()->diff($er->recurrenceDate())); + // calculate time difference from now to start of event + $occurring = $this->minimizeInterval($this->timeFactory->getDateTime()->diff($er->recurrenceDate())); // extract start date $startDate = $this->l10n->l('date', $er->startDateTime(), ['width' => 'full']); // time of the day @@ -204,19 +204,19 @@ class IMipService { // Output produced in order: // In a day/week/month/year on July 1, 2024 for the entire day // In a day/week/month/year on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto) - // In 2 days/weeks/monthss/years on July 1, 2024 for the entire day - // In 2 days/weeks/monthss/years on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto) - return match ([($occuring[0] > 1), !empty($endTime)]) { - [false, false] => $this->l10n->t('In a %1$s on %2$s for the entire day', [$occuring[1], $startDate]), - [false, true] => $this->l10n->t('In a %1$s on %2$s between %3$s - %4$s', [$occuring[1], $startDate, $startTime, $endTime]), - [true, false] => $this->l10n->t('In %1$s %2$s on %3$s for the entire day', [$occuring[0], $occuring[1], $startDate]), - [true, true] => $this->l10n->t('In %1$s %2$s on %3$s between %4$s - %5$s', [$occuring[0], $occuring[1], $startDate, $startTime, $endTime]), + // In 2 days/weeks/months/years on July 1, 2024 for the entire day + // In 2 days/weeks/months/years on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto) + return match ([($occurring[0] > 1), !empty($endTime)]) { + [false, false] => $this->l10n->t('In a %1$s on %2$s for the entire day', [$occurring[1], $startDate]), + [false, true] => $this->l10n->t('In a %1$s on %2$s between %3$s - %4$s', [$occurring[1], $startDate, $startTime, $endTime]), + [true, false] => $this->l10n->t('In %1$s %2$s on %3$s for the entire day', [$occurring[0], $occurring[1], $startDate]), + [true, true] => $this->l10n->t('In %1$s %2$s on %3$s between %4$s - %5$s', [$occurring[0], $occurring[1], $startDate, $startTime, $endTime]), default => $this->l10n->t('Could not generate when statement') }; } /** - * genarates a when string based on recurrance precision/frequency + * generates a when string based on recurrence precision/frequency * * @since 30.0.0 * @@ -235,7 +235,7 @@ class IMipService { } /** - * genarates a when string for a daily precision/frequency + * generates a when string for a daily precision/frequency * * @since 30.0.0 * @@ -287,7 +287,7 @@ class IMipService { } /** - * genarates a when string for a weekly precision/frequency + * generates a when string for a weekly precision/frequency * * @since 30.0.0 * @@ -341,7 +341,7 @@ class IMipService { } /** - * genarates a when string for a monthly precision/frequency + * generates a when string for a monthly precision/frequency * * @since 30.0.0 * @@ -407,7 +407,7 @@ class IMipService { } /** - * genarates a when string for a yearly precision/frequency + * generates a when string for a yearly precision/frequency * * @since 30.0.0 * @@ -475,7 +475,7 @@ class IMipService { } /** - * genarates a when string for a fixed precision/frequency + * generates a when string for a fixed precision/frequency * * @since 30.0.0 * @@ -509,7 +509,7 @@ class IMipService { } /** - * genarates a occurring next string for a recurring event + * generates a occurring next string for a recurring event * * @since 30.0.0 * @@ -519,26 +519,26 @@ class IMipService { */ public function generateOccurringString(EventReader $er): string { - // reset to initial occurance + // reset to initial occurrence $er->recurrenceRewind(); // forward to current date $er->recurrenceAdvanceTo($this->timeFactory->getDateTime()); - // calculate time differnce from now to start of next event occurance and minimize it - $occuranceIn = $this->minimizeInterval($this->timeFactory->getDateTime()->diff($er->recurrenceDate())); - // store next occurance value - $occurance = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']); - // forward one occurance + // calculate time difference from now to start of next event occurrence and minimize it + $occurrenceIn = $this->minimizeInterval($this->timeFactory->getDateTime()->diff($er->recurrenceDate())); + // store next occurrence value + $occurrence = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']); + // forward one occurrence $er->recurrenceAdvance(); - // evaluate if occurance is valid + // evaluate if occurrence is valid if ($er->recurrenceDate() !== null) { - // store following occurance value - $occurance2 = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']); - // forward one occurance + // store following occurrence value + $occurrence2 = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']); + // forward one occurrence $er->recurrenceAdvance(); - // evaluate if occurance is valid + // evaluate if occurrence is valid if ($er->recurrenceDate()) { - // store following occurance value - $occurance3 = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']); + // store following occurrence value + $occurrence3 = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']); } } // generate localized when string @@ -551,13 +551,13 @@ class IMipService { // In 2 days/weeks/months/years on July 1, 2024 // In 2 days/weeks/months/years on July 1, 2024 then on July 3, 2024 // In 2 days/weeks/months/years on July 1, 2024 then on July 3, 2024 and July 5, 2024 - return match ([($occuranceIn[0] > 1), !empty($occurance2), !empty($occurance3)]) { - [false, false, false] => $this->l10n->t('In a %1$s on %2$s', [$occuranceIn[1], $occurance]), - [false, true, false] => $this->l10n->t('In a %1$s on %2$s then on %3$s', [$occuranceIn[1], $occurance, $occurance2]), - [false, true, true] => $this->l10n->t('In a %1$s on %2$s then on %3$s and %4$s', [$occuranceIn[1], $occurance, $occurance2, $occurance3]), - [true, false, false] => $this->l10n->t('In %1$s %2$s on %3$s', [$occuranceIn[0], $occuranceIn[1], $occurance]), - [true, true, false] => $this->l10n->t('In %1$s %2$s on %3$s then on %4$s', [$occuranceIn[0], $occuranceIn[1], $occurance, $occurance2]), - [true, true, true] => $this->l10n->t('In %1$s %2$s on %3$s then on %4$s and %5$s', [$occuranceIn[0], $occuranceIn[1], $occurance, $occurance2, $occurance3]), + return match ([($occurrenceIn[0] > 1), !empty($occurrence2), !empty($occurrence3)]) { + [false, false, false] => $this->l10n->t('In a %1$s on %2$s', [$occurrenceIn[1], $occurrence]), + [false, true, false] => $this->l10n->t('In a %1$s on %2$s then on %3$s', [$occurrenceIn[1], $occurrence, $occurrence2]), + [false, true, true] => $this->l10n->t('In a %1$s on %2$s then on %3$s and %4$s', [$occurrenceIn[1], $occurrence, $occurrence2, $occurrence3]), + [true, false, false] => $this->l10n->t('In %1$s %2$s on %3$s', [$occurrenceIn[0], $occurrenceIn[1], $occurrence]), + [true, true, false] => $this->l10n->t('In %1$s %2$s on %3$s then on %4$s', [$occurrenceIn[0], $occurrenceIn[1], $occurrence, $occurrence2]), + [true, true, true] => $this->l10n->t('In %1$s %2$s on %3$s then on %4$s and %5$s', [$occurrenceIn[0], $occurrenceIn[1], $occurrence, $occurrence2, $occurrence3]), default => $this->l10n->t('Could not generate next recurrence statement') }; diff --git a/apps/dav/tests/unit/CalDAV/EventReaderTest.php b/apps/dav/tests/unit/CalDAV/EventReaderTest.php index 3c94147efb4..74cc22dff75 100644 --- a/apps/dav/tests/unit/CalDAV/EventReaderTest.php +++ b/apps/dav/tests/unit/CalDAV/EventReaderTest.php @@ -533,6 +533,15 @@ class EventReaderTest extends TestCase { // test set by constructor $this->assertTrue($er->recurringConcludes()); + /** test rdate (multiple property instances) recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240703'); + $vCalendar->VEVENT[0]->add('RDATE', '20240705'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertTrue($er->recurringConcludes()); + /** test rrule and rdate recurrance with rdate as last date */ $vCalendar = clone $this->vCalendar1a; $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;COUNT=6;BYDAY=MO,WE,FR'); @@ -578,6 +587,15 @@ class EventReaderTest extends TestCase { // test set by constructor $this->assertEquals(2, $er->recurringConcludesAfter()); + /** test rdate (multiple property instances) recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240703'); + $vCalendar->VEVENT[0]->add('RDATE', '20240705'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals(2, $er->recurringConcludesAfter()); + /** test rrule and rdate recurrance */ $vCalendar = clone $this->vCalendar1a; $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;COUNT=6;BYDAY=MO,WE,FR'); @@ -624,6 +642,15 @@ class EventReaderTest extends TestCase { // test set by constructor $this->assertEquals((new \DateTime('20240705T000000', (new DateTimeZone('America/Toronto')))), $er->recurringConcludesOn()); + /** test rdate (multiple property instances) recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240703'); + $vCalendar->VEVENT[0]->add('RDATE', '20240705'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new \DateTime('20240705T000000', (new DateTimeZone('America/Toronto')))), $er->recurringConcludesOn()); + /** test rrule and rdate recurrance with rdate as last date */ $vCalendar = clone $this->vCalendar1a; $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;COUNT=6;BYDAY=MO,WE,FR'); diff --git a/apps/dav/tests/unit/CalDAV/Schedule/IMipServiceTest.php b/apps/dav/tests/unit/CalDAV/Schedule/IMipServiceTest.php index ef5a0b17c6e..f1c003625b2 100644 --- a/apps/dav/tests/unit/CalDAV/Schedule/IMipServiceTest.php +++ b/apps/dav/tests/unit/CalDAV/Schedule/IMipServiceTest.php @@ -1248,7 +1248,7 @@ class IMipServiceTest extends TestCase { } - public function testGenerateOccurringString(): void { + public function testGenerateOccurringStringWithRrule(): void { // construct l10n return(s) $this->l10n->method('l')->willReturnCallback( @@ -1285,7 +1285,7 @@ class IMipServiceTest extends TestCase { (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), ); - /** test patrial day recurring event in 1 day with single occurance remaining */ + /** test patrial day recurring event in 1 day with single occurrence remaining */ $vCalendar = clone $this->vCalendar1a; $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1'); // construct event reader @@ -1296,7 +1296,7 @@ class IMipServiceTest extends TestCase { $this->service->generateOccurringString($eventReader) ); - /** test patrial day recurring event in 1 day with two occurances remaining */ + /** test patrial day recurring event in 1 day with two occurrences remaining */ $vCalendar = clone $this->vCalendar1a; $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2'); // construct event reader @@ -1307,7 +1307,7 @@ class IMipServiceTest extends TestCase { $this->service->generateOccurringString($eventReader) ); - /** test patrial day recurring event in 1 day with three occurances remaining */ + /** test patrial day recurring event in 1 day with three occurrences remaining */ $vCalendar = clone $this->vCalendar1a; $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3'); // construct event reader @@ -1318,7 +1318,7 @@ class IMipServiceTest extends TestCase { $this->service->generateOccurringString($eventReader) ); - /** test patrial day recurring event in 2 days with single occurance remaining */ + /** test patrial day recurring event in 2 days with single occurrence remaining */ $vCalendar = clone $this->vCalendar1a; $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1'); // construct event reader @@ -1329,7 +1329,7 @@ class IMipServiceTest extends TestCase { $this->service->generateOccurringString($eventReader) ); - /** test patrial day recurring event in 2 days with two occurances remaining */ + /** test patrial day recurring event in 2 days with two occurrences remaining */ $vCalendar = clone $this->vCalendar1a; $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2'); // construct event reader @@ -1340,7 +1340,7 @@ class IMipServiceTest extends TestCase { $this->service->generateOccurringString($eventReader) ); - /** test patrial day recurring event in 2 days with three occurances remaining */ + /** test patrial day recurring event in 2 days with three occurrences remaining */ $vCalendar = clone $this->vCalendar1a; $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3'); // construct event reader @@ -1352,4 +1352,417 @@ class IMipServiceTest extends TestCase { ); } + public function testGenerateOccurringStringWithRdate(): void { + + // construct l10n return(s) + $this->l10n->method('l')->willReturnCallback( + function ($v1, $v2, $v3) { + return match (true) { + $v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 1, 2024', + $v1 === 'date' && $v2 == (new \DateTime('20240703T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 3, 2024', + $v1 === 'date' && $v2 == (new \DateTime('20240705T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 5, 2024' + }; + } + ); + $this->l10n->method('t')->willReturnMap([ + ['In a %1$s on %2$s', ['day', 'July 1, 2024'], 'In a day on July 1, 2024'], + ['In a %1$s on %2$s then on %3$s', ['day', 'July 1, 2024', 'July 3, 2024'], 'In a day on July 1, 2024 then on July 3, 2024'], + ['In a %1$s on %2$s then on %3$s and %4$s', ['day', 'July 1, 2024', 'July 3, 2024', 'July 5, 2024'], 'In a day on July 1, 2024 then on July 3, 2024 and July 5, 2024'], + ['In %1$s %2$s on %3$s', [2, 'days', 'July 1, 2024'], 'In 2 days on July 1, 2024'], + ['In %1$s %2$s on %3$s then on %4$s', [2, 'days', 'July 1, 2024', 'July 3, 2024'], 'In 2 days on July 1, 2024 then on July 3, 2024'], + ['In %1$s %2$s on %3$s then on %4$s and %5$s', [2, 'days', 'July 1, 2024', 'July 3, 2024', 'July 5, 2024'], 'In 2 days on July 1, 2024 then on July 3, 2024 and July 5, 2024'], + ]); + + // construct time factory return(s) + $this->timeFactory->method('getDateTime')->willReturnOnConsecutiveCalls( + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + ); + + /** test patrial day recurring event in 1 day with single occurrence remaining */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240701T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with single occurrence remaining' + ); + + /** test patrial day recurring event in 1 day with two occurrences remaining */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240701T080000,20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024 then on July 3, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with two occurrences remaining' + ); + + /** test patrial day recurring event in 1 day with three occurrences remaining */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240701T080000,20240703T080000,20240705T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024 then on July 3, 2024 and July 5, 2024', + $this->service->generateOccurringString($eventReader), + '' + ); + + /** test patrial day recurring event in 2 days with single occurrences remaining */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240701T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024', + $this->service->generateOccurringString($eventReader), + '' + ); + + /** test patrial day recurring event in 2 days with two occurrences remaining */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240701T080000'); + $vCalendar->VEVENT[0]->add('RDATE', '20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024 then on July 3, 2024', + $this->service->generateOccurringString($eventReader), + '' + ); + + /** test patrial day recurring event in 2 days with three occurrences remaining */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240701T080000'); + $vCalendar->VEVENT[0]->add('RDATE', '20240703T080000'); + $vCalendar->VEVENT[0]->add('RDATE', '20240705T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024 then on July 3, 2024 and July 5, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 2 days with three occurrences remaining' + ); + } + + public function testGenerateOccurringStringWithOneExdate(): void { + + // construct l10n return(s) + $this->l10n->method('l')->willReturnCallback( + function ($v1, $v2, $v3) { + return match (true) { + $v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 1, 2024', + $v1 === 'date' && $v2 == (new \DateTime('20240705T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 5, 2024', + $v1 === 'date' && $v2 == (new \DateTime('20240707T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 7, 2024' + }; + } + ); + $this->l10n->method('t')->willReturnMap([ + ['In a %1$s on %2$s', ['day', 'July 1, 2024'], 'In a day on July 1, 2024'], + ['In a %1$s on %2$s then on %3$s', ['day', 'July 1, 2024', 'July 5, 2024'], 'In a day on July 1, 2024 then on July 5, 2024'], + ['In a %1$s on %2$s then on %3$s and %4$s', ['day', 'July 1, 2024', 'July 5, 2024', 'July 7, 2024'], 'In a day on July 1, 2024 then on July 5, 2024 and July 7, 2024'], + ['In %1$s %2$s on %3$s', [2, 'days', 'July 1, 2024'], 'In 2 days on July 1, 2024'], + ['In %1$s %2$s on %3$s then on %4$s', [2, 'days', 'July 1, 2024', 'July 5, 2024'], 'In 2 days on July 1, 2024 then on July 5, 2024'], + ['In %1$s %2$s on %3$s then on %4$s and %5$s', [2, 'days', 'July 1, 2024', 'July 5, 2024', 'July 7, 2024'], 'In 2 days on July 1, 2024 then on July 5, 2024 and July 7, 2024'], + ]); + + // construct time factory return(s) + $this->timeFactory->method('getDateTime')->willReturnOnConsecutiveCalls( + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + ); + + /** test patrial day recurring event in 1 day with single occurrence remaining and one exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with single occurrence remaining and one exception' + ); + + /** test patrial day recurring event in 1 day with two occurrences remaining and one exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with two occurrences remaining and one exception' + ); + + /** test patrial day recurring event in 1 day with three occurrences remaining and one exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024 then on July 5, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with three occurrences remaining and one exception' + ); + + /** test patrial day recurring event in 1 day with four occurrences remaining and one exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=4'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024 then on July 5, 2024 and July 7, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with four occurrences remaining and one exception' + ); + + /** test patrial day recurring event in 2 days with single occurrences remaining and one exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 2 days with single occurrences remaining and one exception' + ); + + /** test patrial day recurring event in 2 days with two occurrences remaining and one exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 2 days with two occurrences remaining and one exception' + ); + + /** test patrial day recurring event in 2 days with three occurrences remaining and one exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024 then on July 5, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 2 days with three occurrences remaining and one exception' + ); + + /** test patrial day recurring event in 2 days with four occurrences remaining and one exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=4'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024 then on July 5, 2024 and July 7, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 2 days with four occurrences remaining and one exception' + ); + } + + public function testGenerateOccurringStringWithTwoExdate(): void { + + // construct l10n return(s) + $this->l10n->method('l')->willReturnCallback( + function ($v1, $v2, $v3) { + return match (true) { + $v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 1, 2024', + $v1 === 'date' && $v2 == (new \DateTime('20240705T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 5, 2024', + $v1 === 'date' && $v2 == (new \DateTime('20240709T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 9, 2024' + }; + } + ); + $this->l10n->method('t')->willReturnMap([ + ['In a %1$s on %2$s', ['day', 'July 1, 2024'], 'In a day on July 1, 2024'], + ['In a %1$s on %2$s then on %3$s', ['day', 'July 1, 2024', 'July 5, 2024'], 'In a day on July 1, 2024 then on July 5, 2024'], + ['In a %1$s on %2$s then on %3$s and %4$s', ['day', 'July 1, 2024', 'July 5, 2024', 'July 9, 2024'], 'In a day on July 1, 2024 then on July 5, 2024 and July 9, 2024'], + ['In %1$s %2$s on %3$s', [2, 'days', 'July 1, 2024'], 'In 2 days on July 1, 2024'], + ['In %1$s %2$s on %3$s then on %4$s', [2, 'days', 'July 1, 2024', 'July 5, 2024'], 'In 2 days on July 1, 2024 then on July 5, 2024'], + ['In %1$s %2$s on %3$s then on %4$s and %5$s', [2, 'days', 'July 1, 2024', 'July 5, 2024', 'July 9, 2024'], 'In 2 days on July 1, 2024 then on July 5, 2024 and July 9, 2024'], + ]); + + // construct time factory return(s) + $this->timeFactory->method('getDateTime')->willReturnOnConsecutiveCalls( + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + ); + + /** test patrial day recurring event in 1 day with single occurrence remaining and two exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with single occurrence remaining and two exception' + ); + + /** test patrial day recurring event in 1 day with two occurrences remaining and two exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with two occurrences remaining and two exception' + ); + + /** test patrial day recurring event in 1 day with three occurrences remaining and two exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024 then on July 5, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with three occurrences remaining and two exception' + ); + + /** test patrial day recurring event in 1 day with four occurrences remaining and two exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=5'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024 then on July 5, 2024 and July 9, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with four occurrences remaining and two exception' + ); + + /** test patrial day recurring event in 2 days with single occurrences remaining and two exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 2 days with single occurrences remaining and two exception' + ); + + /** test patrial day recurring event in 2 days with two occurrences remaining and two exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 2 days with two occurrences remaining and two exception' + ); + + /** test patrial day recurring event in 2 days with three occurrences remaining and two exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024 then on July 5, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 2 days with three occurrences remaining and two exception' + ); + + /** test patrial day recurring event in 2 days with five occurrences remaining and two exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=5'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024 then on July 5, 2024 and July 9, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 2 days with five occurrences remaining and two exception' + ); + } + } -- 2.39.5