diff --git a/.patches/sabre-vobject-rdate.patch b/.patches/sabre-vobject-rdate.patch new file mode 100644 index 000000000..226a5e6c9 --- /dev/null +++ b/.patches/sabre-vobject-rdate.patch @@ -0,0 +1,89 @@ +From 099a37744a3aaf368bdd339f9c01e34553e8828e Mon Sep 17 00:00:00 2001 +From: SebastianKrupinski +Date: Wed, 19 Nov 2025 19:19:32 -0500 +Subject: [PATCH] fix: use RDATE in time range check and use all instances + +Signed-off-by: SebastianKrupinski +--- + lib/Component/VEvent.php | 2 +- + lib/Recur/EventIterator.php | 6 ++++- + tests/VObject/Component/VEventTest.php | 35 ++++++++++++++++++++++++++ + 3 files changed, 41 insertions(+), 2 deletions(-) + +diff --git a/lib/Component/VEvent.php b/lib/Component/VEvent.php +index e29e56322..6ca196d66 100644 +--- a/lib/Component/VEvent.php ++++ b/lib/Component/VEvent.php +@@ -41,7 +41,7 @@ class VEvent extends VObject\Component + */ + public function isInTimeRange(\DateTimeInterface $start, \DateTimeInterface $end): bool + { +- if ($this->RRULE) { ++ if ($this->RRULE || $this->RDATE) { + try { + $it = new EventIterator($this, null, $start->getTimezone()); + } catch (NoInstancesException $e) { +diff --git a/lib/Recur/EventIterator.php b/lib/Recur/EventIterator.php +index 2c93bcf4e..79a070ee4 100644 +--- a/lib/Recur/EventIterator.php ++++ b/lib/Recur/EventIterator.php +@@ -164,8 +164,12 @@ public function __construct($input, ?string $uid = null, ?\DateTimeZone $timeZon + } + + if (isset($this->masterEvent->RDATE)) { ++ $rdateValues = []; ++ foreach ($this->masterEvent->RDATE as $rdate) { ++ $rdateValues = array_merge($rdateValues, $rdate->getParts()); ++ } + $this->recurIterator = new RDateIterator( +- $this->masterEvent->RDATE->getParts(), ++ $rdateValues, + $this->startDate + ); + } elseif (isset($this->masterEvent->RRULE)) { +diff --git a/tests/VObject/Component/VEventTest.php b/tests/VObject/Component/VEventTest.php +index 50fa626cc..5944ea574 100644 +--- a/tests/VObject/Component/VEventTest.php ++++ b/tests/VObject/Component/VEventTest.php +@@ -89,6 +89,41 @@ public function timeRangeTestData(): array + $vevent9->RRULE = 'FREQ=DAILY'; + $tests[] = [$vevent9, new \DateTime('2016-10-31'), new \DateTime('2016-12-12'), true]; + ++ // Added this test to check events with RDATE property with multiple dates ++ $vevent10 = clone $vevent; ++ $vevent10->DTSTART = '20140901T000000Z'; ++ $vevent10->DTEND = '20140901T010000Z'; ++ $vevent10->add('RDATE', ['20141001T000000Z', '20141101T000000Z']); ++ // DTSTART is the first occurrence ++ $tests[] = [$vevent10, new \DateTime('2014-09-01'), new \DateTime('2014-09-02'), true]; ++ // RDATE adds additional occurrences on Oct 1 and Nov 1 ++ $tests[] = [$vevent10, new \DateTime('2014-10-01'), new \DateTime('2014-10-02'), true]; ++ $tests[] = [$vevent10, new \DateTime('2014-11-01'), new \DateTime('2014-11-02'), true]; ++ // No occurrence in December ++ $tests[] = [$vevent10, new \DateTime('2014-12-01'), new \DateTime('2014-12-31'), false]; ++ // Range that includes first occurrence ++ $tests[] = [$vevent10, new \DateTime('2014-08-01'), new \DateTime('2014-09-30'), true]; ++ // Range that spans all occurrences ++ $tests[] = [$vevent10, new \DateTime('2014-08-01'), new \DateTime('2014-12-31'), true]; ++ ++ // Added this test to check events with RDATE property with multiple instances ++ $vevent11 = clone $vevent; ++ $vevent11->DTSTART = '20140901T000000Z'; ++ $vevent11->DTEND = '20140901T010000Z'; ++ $vevent11->add('RDATE', '20141001T000000Z'); ++ $vevent11->add('RDATE', '20141101T000000Z'); ++ // DTSTART is the first occurrence ++ $tests[] = [$vevent11, new \DateTime('2014-09-01'), new \DateTime('2014-09-02'), true]; ++ // RDATE adds additional occurrences on Oct 1 and Nov 1 ++ $tests[] = [$vevent11, new \DateTime('2014-10-01'), new \DateTime('2014-10-02'), true]; ++ $tests[] = [$vevent11, new \DateTime('2014-11-01'), new \DateTime('2014-11-02'), true]; ++ // No occurrence in December ++ $tests[] = [$vevent11, new \DateTime('2014-12-01'), new \DateTime('2014-12-31'), false]; ++ // Range that includes first occurrence ++ $tests[] = [$vevent11, new \DateTime('2014-08-01'), new \DateTime('2014-09-30'), true]; ++ // Range that spans all occurrences ++ $tests[] = [$vevent11, new \DateTime('2014-08-01'), new \DateTime('2014-12-31'), true]; ++ + return $tests; + } + } diff --git a/composer.patches.json b/composer.patches.json index ae985da3f..73fb07c30 100644 --- a/composer.patches.json +++ b/composer.patches.json @@ -6,6 +6,9 @@ }, "sabre/dav": { "Check for instanceof INode instead of Node": ".patches/check-for-instanceof-INode-instead-of-Node-1595.patch" + }, + "sabre/vobject": { + "fix use RDATE in time range check and use all instances": ".patches/sabre-vobject-rdate.patch" } } }