/** * Creates a new component. * * By default this object will iterate over its own children, but this can * be overridden with the iterator argument * * @param string $name * @param Sabre\VObject\ElementList $iterator */ public function __construct() { parent::__construct(); $tz = new \DateTimeZone(\GO::user() ? \GO::user()->timezone : date_default_timezone_get()); //$tz = new \DateTimeZone("Europe/Amsterdam"); $transitions = $tz->getTransitions(); $start_of_year = mktime(0, 0, 0, 1, 1); $to = \GO\Base\Util\Date::get_timezone_offset(time()); if ($to < 0) { if (strlen($to) == 2) { $to = '-0' . $to * -1; } } else { if (strlen($to) == 1) { $to = '0' . $to; } $to = '+' . $to; } $STANDARD_TZOFFSETFROM = $STANDARD_TZOFFSETTO = $DAYLIGHT_TZOFFSETFROM = $DAYLIGHT_TZOFFSETTO = $to; $STANDARD_RRULE = ''; $DAYLIGHT_RRULE = ''; for ($i = 0, $max = count($transitions); $i < $max; $i++) { if ($transitions[$i]['ts'] > $start_of_year) { $weekday1 = $this->_getDay($transitions[$i]['time']); $weekday2 = $this->_getDay($transitions[$i + 1]['time']); if ($transitions[$i]['isdst']) { $dst_start = $transitions[$i]; $dst_end = $transitions[$i + 1]; } else { $dst_end = $transitions[$i]; $dst_start = $transitions[$i + 1]; } $STANDARD_TZOFFSETFROM = $this->_formatVtimezoneTransitionHour($dst_start['offset'] / 3600); $STANDARD_TZOFFSETTO = $this->_formatVtimezoneTransitionHour($dst_end['offset'] / 3600); $DAYLIGHT_TZOFFSETFROM = $this->_formatVtimezoneTransitionHour($dst_end['offset'] / 3600); $DAYLIGHT_TZOFFSETTO = $this->_formatVtimezoneTransitionHour($dst_start['offset'] / 3600); $DAYLIGHT_RRULE = "FREQ=YEARLY;BYDAY={$weekday1};BYMONTH=" . date('n', $dst_start['ts']); $STANDARD_RRULE = "FREQ=YEARLY;BYDAY={$weekday2};BYMONTH=" . date('n', $dst_end['ts']); break; } } $this->tzid = $tz->getName(); // $this->add("last-modified", "19870101T000000Z"); $rrule = new \Sabre\VObject\Recur\RRuleIterator($STANDARD_RRULE, new \DateTime('1970-01-01 ' . substr($STANDARD_TZOFFSETFROM, 1) . ':00')); $rrule->next(); $rrule->next(); $this->add($this->createComponent("standard", array('dtstart' => $rrule->current()->format('Ymd\\THis'), 'rrule' => $STANDARD_RRULE, 'tzoffsetfrom' => $STANDARD_TZOFFSETFROM . "00", 'tzoffsetto' => $STANDARD_TZOFFSETTO . "00"))); $rrule = new \Sabre\VObject\Recur\RRuleIterator($DAYLIGHT_RRULE, new \DateTime('1970-01-01 ' . substr($DAYLIGHT_TZOFFSETFROM, 1) . ':00')); $rrule->next(); $rrule->next(); $this->add($this->createComponent("daylight", array('dtstart' => $rrule->current()->format('Ymd\\THis'), 'rrule' => $DAYLIGHT_RRULE, 'tzoffsetfrom' => $DAYLIGHT_TZOFFSETFROM . "00", 'tzoffsetto' => $DAYLIGHT_TZOFFSETTO . "00"))); }
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(); } } } } } }