123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769 |
- <?php
- declare(strict_types=1);
- /**
- * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
- namespace OCA\DAV\CalDAV;
- use DateTime;
- use DateTimeImmutable;
- use DateTimeInterface;
- use DateTimeZone;
- use InvalidArgumentException;
- use Sabre\VObject\Component\VCalendar;
- use Sabre\VObject\Component\VEvent;
- use Sabre\VObject\Reader;
- class EventReader {
- protected VEvent $baseEvent;
- protected DateTimeInterface $baseEventStartDate;
- protected DateTimeZone $baseEventStartTimeZone;
- protected DateTimeInterface $baseEventEndDate;
- protected DateTimeZone $baseEventEndTimeZone;
- protected bool $baseEventStartDateFloating = false;
- protected bool $baseEventEndDateFloating = false;
- protected int $baseEventDuration;
- protected ?EventReaderRRule $rruleIterator = null;
- protected ?EventReaderRDate $rdateIterator = null;
- protected ?EventReaderRRule $eruleIterator = null;
- protected ?EventReaderRDate $edateIterator = null;
- protected array $recurrenceModified;
- protected ?DateTimeInterface $recurrenceCurrentDate;
- protected array $dayNamesMap = [
- 'MO' => 'Monday', 'TU' => 'Tuesday', 'WE' => 'Wednesday', 'TH' => 'Thursday', 'FR' => 'Friday', 'SA' => 'Saturday', 'SU' => 'Sunday'
- ];
- protected array $monthNamesMap = [
- 1 => 'January', 2 => 'February', 3 => 'March', 4 => 'April', 5 => 'May', 6 => 'June',
- 7 => 'July', 8 => 'August', 9 => 'September', 10 => 'October', 11 => 'November', 12 => 'December'
- ];
- protected array $relativePositionNamesMap = [
- 1 => 'First', 2 => 'Second', 3 => 'Third', 4 => 'Fourth', 5 => 'Fifty',
- -1 => 'Last', -2 => 'Second Last', -3 => 'Third Last', -4 => 'Fourth Last', -5 => 'Fifty Last'
- ];
- /**
- * Initilizes the Event Reader
- *
- * There is several ways to set up the iterator.
- *
- * 1. You can pass a VCALENDAR component (as object or string) and a UID.
- * 2. You can pass an array of VEVENTs (all UIDS should match).
- * 3. You can pass a single VEVENT component (as object or string).
- *
- * Only the second method is recommended. The other 1 and 3 will be removed
- * at some point in the future.
- *
- * The $uid parameter is only required for the first method.
- *
- * @since 30.0.0
- *
- * @param VCalendar|VEvent|Array|String $input
- * @param string|null $uid
- * @param DateTimeZone|null $timeZone reference timezone for floating dates and times
- */
- public function __construct(VCalendar|VEvent|array|string $input, ?string $uid = null, ?DateTimeZone $timeZone = null) {
- // evaluate if the input is a string and convert it to and vobject if required
- if (is_string($input)) {
- $input = Reader::read($input);
- }
- // evaluate if input is a single event vobject and convert it to a collection
- if ($input instanceof VEvent) {
- $events = [$input];
- }
- // evaluate if input is a calendar vobject
- elseif ($input instanceof VCalendar) {
- // Calendar + UID mode.
- if ($uid === null) {
- throw new InvalidArgumentException('The UID argument is required when a VCALENDAR object is used');
- }
- // extract events from calendar
- $events = $input->getByUID($uid);
- // evaluate if any event where found
- if (count($events) === 0) {
- throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: ' . $uid);
- }
- // extract calendar timezone
- if (isset($input->VTIMEZONE) && isset($input->VTIMEZONE->TZID)) {
- $calendarTimeZone = new DateTimeZone($input->VTIMEZONE->TZID->getValue());
- }
- }
- // evaluate if input is a collection of event vobjects
- elseif (is_array($input)) {
- $events = $input;
- } else {
- throw new InvalidArgumentException('Invalid input data type');
- }
- // find base event instance and remove it from events collection
- foreach ($events as $key => $vevent) {
- if (!isset($vevent->{'RECURRENCE-ID'})) {
- $this->baseEvent = $vevent;
- unset($events[$key]);
- }
- }
- // No base event was found. CalDAV does allow cases where only
- // overridden instances are stored.
- //
- // In this particular case, we're just going to grab the first
- // event and use that instead. This may not always give the
- // desired result.
- if (!isset($this->baseEvent) && count($events) > 0) {
- $this->baseEvent = array_shift($events);
- }
- // determain the event starting time zone
- // we require this to align all other dates times
- // evaluate if timezone paramater was used (treat this as a override)
- if ($timeZone !== null) {
- $this->baseEventStartTimeZone = $timeZone;
- }
- // evaluate if event start date has a timezone parameter
- elseif (isset($this->baseEvent->DTSTART->parameters['TZID'])) {
- $this->baseEventStartTimeZone = new DateTimeZone($this->baseEvent->DTSTART->parameters['TZID']->getValue());
- }
- // evaluate if event parent calendar has a time zone
- elseif (isset($calendarTimeZone)) {
- $this->baseEventStartTimeZone = clone $calendarTimeZone;
- }
- // otherwise, as a last resort use the UTC timezone
- else {
- $this->baseEventStartTimeZone = new DateTimeZone('UTC');
- }
- // determain the event end time zone
- // we require this to align all other dates and times
- // evaluate if timezone paramater was used (treat this as a override)
- if ($timeZone !== null) {
- $this->baseEventEndTimeZone = $timeZone;
- }
- // evaluate if event end date has a timezone parameter
- elseif (isset($this->baseEvent->DTEND->parameters['TZID'])) {
- $this->baseEventEndTimeZone = new DateTimeZone($this->baseEvent->DTEND->parameters['TZID']->getValue());
- }
- // evaluate if event parent calendar has a time zone
- elseif (isset($calendarTimeZone)) {
- $this->baseEventEndTimeZone = clone $calendarTimeZone;
- }
- // otherwise, as a last resort use the start date time zone
- else {
- $this->baseEventEndTimeZone = clone $this->baseEventStartTimeZone;
- }
- // extract start date and time
- $this->baseEventStartDate = $this->baseEvent->DTSTART->getDateTime($this->baseEventStartTimeZone);
- $this->baseEventStartDateFloating = $this->baseEvent->DTSTART->isFloating();
- // determine event end date and duration
- // evaluate if end date exists
- // extract end date and calculate duration
- if (isset($this->baseEvent->DTEND)) {
- $this->baseEventEndDate = $this->baseEvent->DTEND->getDateTime($this->baseEventEndTimeZone);
- $this->baseEventEndDateFloating = $this->baseEvent->DTEND->isFloating();
- $this->baseEventDuration =
- $this->baseEvent->DTEND->getDateTime($this->baseEventEndTimeZone)->getTimeStamp() -
- $this->baseEventStartDate->getTimeStamp();
- }
- // evaluate if duration exists
- // extract duration and calculate end date
- elseif (isset($this->baseEvent->DURATION)) {
- $this->baseEventEndDate = DateTimeImmutable::createFromInterface($this->baseEventStartDate)
- ->add($this->baseEvent->DURATION->getDateInterval());
- $this->baseEventDuration = $this->baseEventEndDate->getTimestamp() - $this->baseEventStartDate->getTimestamp();
- }
- // evaluate if start date is floating
- // set duration to 24 hours and calculate the end date
- // according to the rfc any event without a end date or duration is a complete day
- elseif ($this->baseEventStartDateFloating == true) {
- $this->baseEventDuration = 86400;
- $this->baseEventEndDate = DateTimeImmutable::createFromInterface($this->baseEventStartDate)
- ->setTimestamp($this->baseEventStartDate->getTimestamp() + $this->baseEventDuration);
- }
- // otherwise, set duration to zero this should never happen
- else {
- $this->baseEventDuration = 0;
- $this->baseEventEndDate = $this->baseEventStartDate;
- }
- // evaluate if RRULE exist and construct iterator
- if (isset($this->baseEvent->RRULE)) {
- $this->rruleIterator = new EventReaderRRule(
- $this->baseEvent->RRULE->getParts(),
- $this->baseEventStartDate
- );
- }
- // 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(
- implode(',', $dates),
- $this->baseEventStartDate
- );
- }
- // evaluate if EXRULE exist and construct iterator
- if (isset($this->baseEvent->EXRULE)) {
- $this->eruleIterator = new EventReaderRRule(
- $this->baseEvent->EXRULE->getParts(),
- $this->baseEventStartDate
- );
- }
- // 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(
- implode(',', $dates),
- $this->baseEventStartDate
- );
- }
- // construct collection of modified events with recurrence id as hash
- foreach ($events as $vevent) {
- $this->recurrenceModified[$vevent->{'RECURRENCE-ID'}->getDateTime($this->baseEventStartTimeZone)->getTimeStamp()] = $vevent;
- }
- $this->recurrenceCurrentDate = clone $this->baseEventStartDate;
- }
- /**
- * retrieve date and time of event start
- *
- * @since 30.0.0
- *
- * @return DateTime
- */
- public function startDateTime(): DateTime {
- return DateTime::createFromInterface($this->baseEventStartDate);
- }
- /**
- * retrieve time zone of event start
- *
- * @since 30.0.0
- *
- * @return DateTimeZone
- */
- public function startTimeZone(): DateTimeZone {
- return $this->baseEventStartTimeZone;
- }
- /**
- * retrieve date and time of event end
- *
- * @since 30.0.0
- *
- * @return DateTime
- */
- public function endDateTime(): DateTime {
- return DateTime::createFromInterface($this->baseEventEndDate);
- }
- /**
- * retrieve time zone of event end
- *
- * @since 30.0.0
- *
- * @return DateTimeZone
- */
- public function endTimeZone(): DateTimeZone {
- return $this->baseEventEndTimeZone;
- }
- /**
- * is this an all day event
- *
- * @since 30.0.0
- *
- * @return bool
- */
- public function entireDay(): bool {
- return $this->baseEventStartDateFloating;
- }
- /**
- * is this a recurring event
- *
- * @since 30.0.0
- *
- * @return bool
- */
- public function recurs(): bool {
- return ($this->rruleIterator !== null || $this->rdateIterator !== null);
- }
- /**
- * event recurrence pattern
- *
- * @since 30.0.0
- *
- * @return string|null R - Relative or A - Absolute
- */
- public function recurringPattern(): ?string {
- if ($this->rruleIterator === null && $this->rdateIterator === null) {
- return null;
- }
- if ($this->rruleIterator?->isRelative()) {
- return 'R';
- }
- return 'A';
- }
- /**
- * event recurrence precision
- *
- * @since 30.0.0
- *
- * @return string|null daily, weekly, monthly, yearly, fixed
- */
- public function recurringPrecision(): ?string {
- if ($this->rruleIterator !== null) {
- return $this->rruleIterator->precision();
- }
- if ($this->rdateIterator !== null) {
- return 'fixed';
- }
- return null;
- }
- /**
- * event recurrence interval
- *
- * @since 30.0.0
- *
- * @return int|null
- */
- public function recurringInterval(): ?int {
- return $this->rruleIterator?->interval();
- }
- /**
- * event recurrence conclusion
- *
- * returns true if RRULE with UNTIL or COUNT (calculated) is used
- * returns true RDATE is used
- * returns false if RRULE or RDATE are absent, or RRRULE is infinite
- *
- * @since 30.0.0
- *
- * @return bool
- */
- public function recurringConcludes(): bool {
- // retrieve rrule conclusions
- if ($this->rruleIterator?->concludesOn() !== null ||
- $this->rruleIterator?->concludesAfter() !== null) {
- return true;
- }
- // retrieve rdate conclusions
- if ($this->rdateIterator?->concludesAfter() !== null) {
- return true;
- }
- return false;
- }
- /**
- * event recurrence conclusion iterations
- *
- * returns the COUNT value if RRULE is used
- * returns the collection count if RDATE is used
- * returns combined count of RRULE COUNT and RDATE if both are used
- * returns null if RRULE and RDATE are absent
- *
- * @since 30.0.0
- *
- * @return int|null
- */
- public function recurringConcludesAfter(): ?int {
- // construct count place holder
- $count = 0;
- // retrieve and add RRULE iterations count
- $count += (int)$this->rruleIterator?->concludesAfter();
- // retrieve and add RDATE iterations count
- $count += (int)$this->rdateIterator?->concludesAfter();
- // return count
- return !empty($count) ? $count : null;
- }
- /**
- * event recurrence conclusion date
- *
- * returns the last date of UNTIL or COUNT (calculated) if RRULE is used
- * returns the last date in the collection if RDATE is used
- * returns the highest date if both RRULE and RDATE are used
- * returns null if RRULE and RDATE are absent or RRULE is infinite
- *
- * @since 30.0.0
- *
- * @return DateTime|null
- */
- public function recurringConcludesOn(): ?DateTime {
- if ($this->rruleIterator !== null) {
- // retrieve rrule conclusion date
- $rrule = $this->rruleIterator->concludes();
- // evaluate if rrule conclusion is null
- // if this is null that means the recurrence is infinate
- if ($rrule === null) {
- return null;
- }
- }
- // retrieve rdate conclusion date
- if ($this->rdateIterator !== null) {
- $rdate = $this->rdateIterator->concludes();
- }
- // evaluate if both rrule and rdate have date
- if (isset($rdate) && isset($rrule)) {
- // return the highest date
- return (($rdate > $rrule) ? $rdate : $rrule);
- } elseif (isset($rrule)) {
- return $rrule;
- } elseif (isset($rdate)) {
- return $rdate;
- }
- return null;
- }
- /**
- * event recurrence days of the week
- *
- * returns collection of RRULE BYDAY day(s) ['MO','WE','FR']
- * returns blank collection if RRULE is absent, RDATE presents or absents has no affect
- *
- * @since 30.0.0
- *
- * @return array
- */
- public function recurringDaysOfWeek(): array {
- // evaluate if RRULE exists and return day(s) of the week
- return $this->rruleIterator !== null ? $this->rruleIterator->daysOfWeek() : [];
- }
- /**
- * event recurrence days of the week (named)
- *
- * returns collection of RRULE BYDAY day(s) ['Monday','Wednesday','Friday']
- * returns blank collection if RRULE is absent, RDATE presents or absents has no affect
- *
- * @since 30.0.0
- *
- * @return array
- */
- public function recurringDaysOfWeekNamed(): array {
- // evaluate if RRULE exists and extract day(s) of the week
- $days = $this->rruleIterator !== null ? $this->rruleIterator->daysOfWeek() : [];
- // convert numberic month to month name
- foreach ($days as $key => $value) {
- $days[$key] = $this->dayNamesMap[$value];
- }
- // return names collection
- return $days;
- }
- /**
- * event recurrence days of the month
- *
- * returns collection of RRULE BYMONTHDAY day(s) [7, 15, 31]
- * returns blank collection if RRULE is absent, RDATE presents or absents has no affect
- *
- * @since 30.0.0
- *
- * @return array
- */
- public function recurringDaysOfMonth(): array {
- // evaluate if RRULE exists and return day(s) of the month
- return $this->rruleIterator !== null ? $this->rruleIterator->daysOfMonth() : [];
- }
- /**
- * event recurrence days of the year
- *
- * returns collection of RRULE BYYEARDAY day(s) [57, 205, 365]
- * returns blank collection if RRULE is absent, RDATE presents or absents has no affect
- *
- * @since 30.0.0
- *
- * @return array
- */
- public function recurringDaysOfYear(): array {
- // evaluate if RRULE exists and return day(s) of the year
- return $this->rruleIterator !== null ? $this->rruleIterator->daysOfYear() : [];
- }
- /**
- * event recurrence weeks of the month
- *
- * returns collection of RRULE SETPOS weeks(s) [1, 3, -1]
- * returns blank collection if RRULE is absent or SETPOS is absent, RDATE presents or absents has no affect
- *
- * @since 30.0.0
- *
- * @return array
- */
- public function recurringWeeksOfMonth(): array {
- // evaluate if RRULE exists and RRULE is relative return relative position(s)
- return $this->rruleIterator?->isRelative() ? $this->rruleIterator->relativePosition() : [];
- }
- /**
- * event recurrence weeks of the month (named)
- *
- * returns collection of RRULE SETPOS weeks(s) [1, 3, -1]
- * returns blank collection if RRULE is absent or SETPOS is absent, RDATE presents or absents has no affect
- *
- * @since 30.0.0
- *
- * @return array
- */
- public function recurringWeeksOfMonthNamed(): array {
- // evaluate if RRULE exists and extract relative position(s)
- $positions = $this->rruleIterator?->isRelative() ? $this->rruleIterator->relativePosition() : [];
- // convert numberic relative position to relative label
- foreach ($positions as $key => $value) {
- $positions[$key] = $this->relativePositionNamesMap[$value];
- }
- // return positions collection
- return $positions;
- }
- /**
- * event recurrence weeks of the year
- *
- * returns collection of RRULE BYWEEKNO weeks(s) [12, 32, 52]
- * returns blank collection if RRULE is absent, RDATE presents or absents has no affect
- *
- * @since 30.0.0
- *
- * @return array
- */
- public function recurringWeeksOfYear(): array {
- // evaluate if RRULE exists and return weeks(s) of the year
- return $this->rruleIterator !== null ? $this->rruleIterator->weeksOfYear() : [];
- }
- /**
- * event recurrence months of the year
- *
- * returns collection of RRULE BYMONTH month(s) [3, 7, 12]
- * returns blank collection if RRULE is absent, RDATE presents or absents has no affect
- *
- * @since 30.0.0
- *
- * @return array
- */
- public function recurringMonthsOfYear(): array {
- // evaluate if RRULE exists and return month(s) of the year
- return $this->rruleIterator !== null ? $this->rruleIterator->monthsOfYear() : [];
- }
- /**
- * event recurrence months of the year (named)
- *
- * returns collection of RRULE BYMONTH month(s) [3, 7, 12]
- * returns blank collection if RRULE is absent, RDATE presents or absents has no affect
- *
- * @since 30.0.0
- *
- * @return array
- */
- public function recurringMonthsOfYearNamed(): array {
- // evaluate if RRULE exists and extract month(s) of the year
- $months = $this->rruleIterator !== null ? $this->rruleIterator->monthsOfYear() : [];
- // convert numberic month to month name
- foreach ($months as $key => $value) {
- $months[$key] = $this->monthNamesMap[$value];
- }
- // return months collection
- return $months;
- }
- /**
- * event recurrence relative positions
- *
- * returns collection of RRULE SETPOS value(s) [1, 5, -3]
- * returns blank collection if RRULE is absent, RDATE presents or absents has no affect
- *
- * @since 30.0.0
- *
- * @return array
- */
- public function recurringRelativePosition(): array {
- // evaluate if RRULE exists and return relative position(s)
- return $this->rruleIterator !== null ? $this->rruleIterator->relativePosition() : [];
- }
- /**
- * event recurrence relative positions (named)
- *
- * returns collection of RRULE SETPOS [1, 3, -1]
- * returns blank collection if RRULE is absent or SETPOS is absent, RDATE presents or absents has no affect
- *
- * @since 30.0.0
- *
- * @return array
- */
- public function recurringRelativePositionNamed(): array {
- // evaluate if RRULE exists and extract relative position(s)
- $positions = $this->rruleIterator?->isRelative() ? $this->rruleIterator->relativePosition() : [];
- // convert numberic relative position to relative label
- foreach ($positions as $key => $value) {
- $positions[$key] = $this->relativePositionNamesMap[$value];
- }
- // return positions collection
- return $positions;
- }
- /**
- * event recurrence date
- *
- * returns date of currently selected recurrence
- *
- * @since 30.0.0
- *
- * @return DateTime
- */
- public function recurrenceDate(): ?DateTime {
- if ($this->recurrenceCurrentDate !== null) {
- return DateTime::createFromInterface($this->recurrenceCurrentDate);
- } else {
- return null;
- }
- }
- /**
- * event recurrence rewind
- *
- * sets the current recurrence to the first recurrence in the collection
- *
- * @since 30.0.0
- *
- * @return void
- */
- public function recurrenceRewind(): void {
- // rewind and increment rrule
- if ($this->rruleIterator !== null) {
- $this->rruleIterator->rewind();
- }
- // rewind and increment rdate
- if ($this->rdateIterator !== null) {
- $this->rdateIterator->rewind();
- }
- // rewind and increment exrule
- if ($this->eruleIterator !== null) {
- $this->eruleIterator->rewind();
- }
- // rewind and increment exdate
- if ($this->edateIterator !== null) {
- $this->edateIterator->rewind();
- }
- // set current date to event start date
- $this->recurrenceCurrentDate = clone $this->baseEventStartDate;
- }
- /**
- * event recurrence advance
- *
- * sets the current recurrence to the next recurrence in the collection
- *
- * @since 30.0.0
- *
- * @return void
- */
- public function recurrenceAdvance(): void {
- // place holders
- $nextOccurrenceDate = null;
- $nextExceptionDate = null;
- $rruleDate = null;
- $rdateDate = null;
- $eruleDate = null;
- $edateDate = null;
- // evaludate if rrule is set and advance one interation past current date
- if ($this->rruleIterator !== null) {
- // forward rrule to the next future date
- while ($this->rruleIterator->valid() && $this->rruleIterator->current() <= $this->recurrenceCurrentDate) {
- $this->rruleIterator->next();
- }
- $rruleDate = $this->rruleIterator->current();
- }
- // evaludate if rdate is set and advance one interation past current date
- if ($this->rdateIterator !== null) {
- // forward rdate to the next future date
- while ($this->rdateIterator->valid() && $this->rdateIterator->current() <= $this->recurrenceCurrentDate) {
- $this->rdateIterator->next();
- }
- $rdateDate = $this->rdateIterator->current();
- }
- if ($rruleDate !== null && $rdateDate !== null) {
- $nextOccurrenceDate = ($rruleDate <= $rdateDate) ? $rruleDate : $rdateDate;
- } elseif ($rruleDate !== null) {
- $nextOccurrenceDate = $rruleDate;
- } elseif ($rdateDate !== null) {
- $nextOccurrenceDate = $rdateDate;
- }
- // evaludate if exrule is set and advance one interation past current date
- if ($this->eruleIterator !== null) {
- // forward exrule to the next future date
- while ($this->eruleIterator->valid() && $this->eruleIterator->current() <= $this->recurrenceCurrentDate) {
- $this->eruleIterator->next();
- }
- $eruleDate = $this->eruleIterator->current();
- }
- // evaludate if exdate is set and advance one interation past current date
- if ($this->edateIterator !== null) {
- // forward exdate to the next future date
- while ($this->edateIterator->valid() && $this->edateIterator->current() <= $this->recurrenceCurrentDate) {
- $this->edateIterator->next();
- }
- $edateDate = $this->edateIterator->current();
- }
- // evaludate if exrule and exdate are set and set nextExDate to the first next date
- if ($eruleDate !== null && $edateDate !== null) {
- $nextExceptionDate = ($eruleDate <= $edateDate) ? $eruleDate : $edateDate;
- } elseif ($eruleDate !== null) {
- $nextExceptionDate = $eruleDate;
- } elseif ($edateDate !== null) {
- $nextExceptionDate = $edateDate;
- }
- // if the next date is part of exrule or exdate find another date
- if ($nextOccurrenceDate !== null && $nextExceptionDate !== null && $nextOccurrenceDate == $nextExceptionDate) {
- $this->recurrenceCurrentDate = $nextOccurrenceDate;
- $this->recurrenceAdvance();
- } else {
- $this->recurrenceCurrentDate = $nextOccurrenceDate;
- }
- }
- /**
- * event recurrence advance
- *
- * sets the current recurrence to the next recurrence in the collection after the specific date
- *
- * @since 30.0.0
- *
- * @param DateTimeInterface $dt date and time to advance
- *
- * @return void
- */
- public function recurrenceAdvanceTo(DateTimeInterface $dt): void {
- while ($this->recurrenceCurrentDate !== null && $this->recurrenceCurrentDate < $dt) {
- $this->recurrenceAdvance();
- }
- }
- }
|