From e0ce92cd6cb034b58b59dd7084ebd90fa3a55334 Mon Sep 17 00:00:00 2001 From: edalzell Date: Wed, 2 Sep 2026 12:57:45 -0700 Subject: [PATCH] fix: collapse multi-day events to one row collapse_multi_days still emitted one occurrence per day. Dedupe by entry id after generate. --- src/Types/MultiDayEvent.php | 19 ++++++++++++++++++ tests/Types/MultiDayEventsTest.php | 31 ++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/Types/MultiDayEvent.php b/src/Types/MultiDayEvent.php index 9dd2a46..7ed1941 100644 --- a/src/Types/MultiDayEvent.php +++ b/src/Types/MultiDayEvent.php @@ -45,6 +45,16 @@ public function end(): CarbonImmutable return $this->days->last()->end(); } + public function nextOccurrences(int $limit = 1): Collection + { + return $this->uniqueCollapsedOccurrences(parent::nextOccurrences($limit)); + } + + public function occurrencesBetween(string|CarbonInterface $from, string|CarbonInterface $to): Collection + { + return $this->uniqueCollapsedOccurrences(parent::occurrencesBetween($from, $to)); + } + public function start(): CarbonImmutable { return $this->days->first()->start(); @@ -139,4 +149,13 @@ private function getDayFromDate(CarbonInterface $date): ?Day { return $this->days->first(fn (Day $day, int $index) => $this->collapseMultiDays ? $index == 0 : $date->isSameDay($day->start())); } + + private function uniqueCollapsedOccurrences(Collection $occurrences): Collection + { + if (! $this->collapseMultiDays) { + return $occurrences; + } + + return $occurrences->unique(fn (Entry $occurrence) => $occurrence->id())->values(); + } } diff --git a/tests/Types/MultiDayEventsTest.php b/tests/Types/MultiDayEventsTest.php index c7f4ea3..e277730 100755 --- a/tests/Types/MultiDayEventsTest.php +++ b/tests/Types/MultiDayEventsTest.php @@ -36,6 +36,7 @@ 'timezone' => 'America/Vancouver', ]); + $this->entry = $entry; $this->event = EventFactory::createFromEntry($entry); $noEndTimeEntry = Entry::make() @@ -113,6 +114,36 @@ expect($this->event->nextOccurrences()[0]->start)->toEqual(Carbon::parse('2019-11-24')->setTimeFromTimeString('11:00:00')); }); +test('can collapse a multi day event into one occurrence', function () { + $event = EventFactory::createFromEntry($this->entry, collapseMultiDays: true); + + $occurrences = $event->occurrencesBetween( + Carbon::parse('2019-11-23')->startOfDay(), + Carbon::parse('2019-11-25')->endOfDay(), + ); + + expect($occurrences)->toHaveCount(1) + ->and($occurrences->first()->start)->toEqual(Carbon::parse('2019-11-23 19:00')->shiftTimezone('America/Vancouver')) + ->and($occurrences->first()->end)->toEqual(Carbon::parse('2019-11-25 15:00')->shiftTimezone('America/Vancouver')); +}); + +test('can collapse upcoming multi day occurrences', function () { + Carbon::setTestNowAndTimezone('2019-11-22', 'America/Vancouver'); + + $event = EventFactory::createFromEntry($this->entry, collapseMultiDays: true); + + expect($event->nextOccurrences(3))->toHaveCount(1); +}); + +test('does not collapse a multi day event by default', function () { + $occurrences = $this->event->occurrencesBetween( + Carbon::parse('2019-11-23')->startOfDay(), + Carbon::parse('2019-11-25')->endOfDay(), + ); + + expect($occurrences)->toHaveCount(3); +}); + test('day is all day when no start and end time', function () { $days = $this->allDayEvent->days();