function parse($rule, $start, $expected, $fastForward = null, $tz = 'UTC') { $dt = new DateTime($start, new DateTimeZone($tz)); $parser = new RRuleIterator($rule, $dt); if ($fastForward) { $parser->fastForward(new DateTime($fastForward)); } $result = []; while ($parser->valid()) { $item = $parser->current(); $result[] = $item->format('Y-m-d H:i:s'); if ($parser->isInfinite() && count($result) >= count($expected)) { break; } $parser->next(); } $this->assertEquals($expected, $result); }
/** * This method takes a VAVAILABILITY component and figures out all the * available times. * * @param FreeBusyData $fbData * @param VCalendar $vavailability * @return void */ protected function calculateAvailability(FreeBusyData $fbData, VCalendar $vavailability) { $vavailComps = iterator_to_array($vavailability->VAVAILABILITY); usort($vavailComps, function ($a, $b) { // We need to order the components by priority. Priority 1 // comes first, up until priority 9. Priority 0 comes after // priority 9. No priority implies priority 0. // // Yes, I'm serious. $priorityA = isset($a->PRIORITY) ? (int) $a->PRIORITY->getValue() : 0; $priorityB = isset($b->PRIORITY) ? (int) $b->PRIORITY->getValue() : 0; if ($priorityA === 0) { $priorityA = 10; } if ($priorityB === 0) { $priorityB = 10; } return $priorityA - $priorityB; }); // Now we go over all the VAVAILABILITY components and figure if // there's any we don't need to consider. // // This is can be because of one of two reasons: either the // VAVAILABILITY component falls outside the time we are interested in, // or a different VAVAILABILITY component with a higher priority has // already completely covered the time-range. $old = $vavailComps; $new = []; foreach ($old as $vavail) { list($compStart, $compEnd) = $vavail->getEffectiveStartEnd(); // We don't care about datetimes that are earlier or later than the // start and end of the freebusy report, so this gets normalized // first. if (is_null($compStart) || $compStart < $this->start) { $compStart = $this->start; } if (is_null($compEnd) || $compEnd > $this->end) { $compEnd = $this->end; } // If the item fell out of the timerange, we can just skip it. if ($compStart > $this->end || $compEnd < $this->start) { continue; } // Going through our existing list of components to see if there's // a higher priority component that already fully covers this one. foreach ($new as $higherVavail) { list($higherStart, $higherEnd) = $higherVavail->getEffectiveStartEnd(); if ((is_null($higherStart) || $higherStart < $compStart) && (is_null($higherEnd) || $higherEnd > $compEnd)) { // Component is fully covered by a higher priority // component. We can skip this component. continue 2; } } // We're keeping it! $new[] = $vavail; } // Lastly, we need to traverse the remaining components and fill in the // freebusydata slots. // // We traverse the components in reverse, because we want the higher // priority components to override the lower ones. foreach (array_reverse($new) as $vavail) { $busyType = isset($vavail->busyType) ? strtoupper($vavail->busyType) : 'BUSY-UNAVAILABLE'; list($vavailStart, $vavailEnd) = $vavail->getEffectiveStartEnd(); // Making the component size no larger than the requested free-busy // report range. if (!$vavailStart || $vavailStart < $this->start) { $vavailStart = $this->start; } if (!$vavailEnd || $vavailEnd > $this->end) { $vavailEnd = $this->end; } // Marking the entire time range of the VAVAILABILITY component as // busy. $fbData->add($vavailStart->getTimeStamp(), $vavailEnd->getTimeStamp(), $busyType); // Looping over the AVAILABLE components. if (isset($vavail->AVAILABLE)) { foreach ($vavail->AVAILABLE as $available) { list($availStart, $availEnd) = $available->getEffectiveStartEnd(); $fbData->add($availStart->getTimeStamp(), $availEnd->getTimeStamp(), 'FREE'); if ($available->RRULE) { // Our favourite thing: recurrence!! $rruleIterator = new Recur\RRuleIterator($available->RRULE->getValue(), $availStart); $rruleIterator->fastForward($vavailStart); $startEndDiff = $availStart->diff($availEnd); while ($rruleIterator->valid()) { $recurStart = $rruleIterator->current(); $recurEnd = $recurStart->add($startEndDiff); if ($recurStart > $vavailEnd) { // We're beyond the legal timerange. break; } if ($recurEnd > $vavailEnd) { // Truncating the end if it exceeds the // VAVAILABILITY end. $recurEnd = $vavailEnd; } $fbData->add($recurStart->getTimeStamp(), $recurEnd->getTimeStamp(), 'FREE'); $rruleIterator->next(); } } } } } }