/** * Expand the event instances for an iCalendar VEVENT (or VTODO) * * Note: expansion here does not apply modifications to instances other than modifying start/end/due/duration. * * @param object $vResource A vComponent which is a VCALENDAR containing components needing expansion * @param object $range_start A RepeatRuleDateTime which is the beginning of the range for events, default -6 weeks * @param object $range_end A RepeatRuleDateTime which is the end of the range for events, default +6 weeks * * @return vComponent The original vComponent, with the instances of the internal components expanded. */ function expand_event_instances(vComponent $vResource, $range_start = null, $range_end = null, $return_floating_times = false) { global $c; $components = $vResource->GetComponents(); $clear_instance_props = array('DTSTART' => true, 'DUE' => true, 'DTEND' => true); if (empty($c->expanded_instances_include_rrule)) { $clear_instance_props += array('RRULE' => true, 'RDATE' => true, 'EXDATE' => true); } if (empty($range_start)) { $range_start = new RepeatRuleDateTime(); $range_start->modify('-6 weeks'); } if (empty($range_end)) { $range_end = clone $range_start; $range_end->modify('+6 months'); } $instances = array(); $expand = false; $dtstart = null; $is_date = false; $has_repeats = false; $dtstart_type = 'DTSTART'; foreach ($components as $k => $comp) { if ($comp->GetType() != 'VEVENT' && $comp->GetType() != 'VTODO' && $comp->GetType() != 'VJOURNAL') { continue; } if (!isset($dtstart)) { $dtstart_prop = $comp->GetProperty($dtstart_type); if (!isset($dtstart_prop) && $comp->GetType() != 'VTODO') { $dtstart_type = 'DUE'; $dtstart_prop = $comp->GetProperty($dtstart_type); } if (!isset($dtstart_prop)) { continue; } $dtstart = new RepeatRuleDateTime($dtstart_prop); if ($return_floating_times) { $dtstart->setAsFloat(); } if (DEBUG_RRULE) { printf("Component is: %s (floating: %s)\n", $comp->GetType(), $return_floating_times ? "yes" : "no"); } $is_date = $dtstart->isDate(); $instances[$dtstart->FloatOrUTC($return_floating_times)] = $comp; $rrule = $comp->GetProperty('RRULE'); $has_repeats = isset($rrule); } $p = $comp->GetProperty('RECURRENCE-ID'); if (isset($p) && $p->Value() != '') { $range = $p->GetParameterValue('RANGE'); $recur_utc = new RepeatRuleDateTime($p); if ($is_date) { $recur_utc->setAsDate(); } $recur_utc = $recur_utc->FloatOrUTC($return_floating_times); if (isset($range) && $range == 'THISANDFUTURE') { foreach ($instances as $k => $v) { if (DEBUG_RRULE) { printf("Removing overridden instance at: {$k}\n"); } if ($k >= $recur_utc) { unset($instances[$k]); } } } else { unset($instances[$recur_utc]); } } else { if (DEBUG_RRULE) { $p = $comp->GetProperty('SUMMARY'); $summary = isset($p) ? $p->Value() : 'not set'; $p = $comp->GetProperty('UID'); $uid = isset($p) ? $p->Value() : 'not set'; printf("Processing event '%s' with UID '%s' starting on %s\n", $summary, $uid, $dtstart->FloatOrUTC($return_floating_times)); print "Instances at start"; foreach ($instances as $k => $v) { print ' : ' . $k; } print "\n"; } } $instances += rrule_expand($dtstart, 'RRULE', $comp, $range_end, null, $return_floating_times); if (DEBUG_RRULE) { print "After rrule_expand"; foreach ($instances as $k => $v) { print ' : ' . $k; } print "\n"; } $instances += rdate_expand($dtstart, 'RDATE', $comp, $range_end, null, $return_floating_times); if (DEBUG_RRULE) { print "After rdate_expand"; foreach ($instances as $k => $v) { print ' : ' . $k; } print "\n"; } foreach (rdate_expand($dtstart, 'EXDATE', $comp, $range_end, null, $return_floating_times) as $k => $v) { unset($instances[$k]); } if (DEBUG_RRULE) { print "After exdate_expand"; foreach ($instances as $k => $v) { print ' : ' . $k; } print "\n"; } } $last_duration = null; $early_start = null; $new_components = array(); $start_utc = $range_start->FloatOrUTC($return_floating_times); $end_utc = $range_end->FloatOrUTC($return_floating_times); foreach ($instances as $utc => $comp) { if ($utc > $end_utc) { if (DEBUG_RRULE) { printf("We're done: {$utc} is out of the range.\n"); } break; } $end_type = $comp->GetType() == 'VTODO' ? 'DUE' : 'DTEND'; $duration = $comp->GetProperty('DURATION'); if (!isset($duration) || $duration->Value() == '') { $instance_start = $comp->GetProperty($dtstart_type); $dtsrt = new RepeatRuleDateTime($instance_start); if ($return_floating_times) { $dtsrt->setAsFloat(); } $instance_end = $comp->GetProperty($end_type); if (isset($instance_end)) { $dtend = new RepeatRuleDateTime($instance_end); $duration = Rfc5545Duration::fromTwoDates($dtsrt, $dtend); } else { if ($instance_start->GetParameterValue('VALUE') == 'DATE') { $duration = new Rfc5545Duration('P1D'); } else { $duration = new Rfc5545Duration(0); } } } else { $duration = new Rfc5545Duration($duration->Value()); } if ($utc < $start_utc) { if (isset($early_start) && isset($last_duration) && $duration->equals($last_duration)) { if ($utc < $early_start) { if (DEBUG_RRULE) { printf("Next please: {$utc} is before {$early_start} and before {$start_utc}.\n"); } continue; } } else { /** Calculate the latest possible start date when this event would overlap our range start */ $latest_start = clone $range_start; $latest_start->modify('-' . $duration); $early_start = $latest_start->FloatOrUTC($return_floating_times); $last_duration = $duration; if ($utc < $early_start) { if (DEBUG_RRULE) { printf("Another please: {$utc} is before {$early_start} and before {$start_utc}.\n"); } continue; } } } $component = clone $comp; $component->ClearProperties($clear_instance_props); $component->AddProperty($dtstart_type, $utc, $is_date ? array('VALUE' => 'DATE') : null); $component->AddProperty('DURATION', $duration); if ($has_repeats && $dtstart->FloatOrUTC($return_floating_times) != $utc) { $component->AddProperty('RECURRENCE-ID', $utc, $is_date ? array('VALUE' => 'DATE') : null); } $new_components[$utc] = $component; } // Add overriden instances foreach ($components as $k => $comp) { $p = $comp->GetProperty('RECURRENCE-ID'); if (isset($p) && $p->Value() != '') { $recurrence_id = $p->Value(); if (!isset($new_components[$recurrence_id])) { // The component we're replacing is outside the range. Unless the replacement // is *in* the range we will move along to the next one. $dtstart_prop = $comp->GetProperty($dtstart_type); if (!isset($dtstart_prop)) { continue; } // No start: no expansion. Note that we consider 'DUE' to be a start if DTSTART is missing $dtstart = new RepeatRuleDateTime($dtstart_prop); $is_date = $dtstart->isDate(); if ($return_floating_times) { $dtstart->setAsFloat(); } $dtstart = $dtstart->FloatOrUTC($return_floating_times); if ($dtstart > $end_utc) { continue; } // Start after end of range, skip it $end_type = $comp->GetType() == 'VTODO' ? 'DUE' : 'DTEND'; $duration = $comp->GetProperty('DURATION'); if (!isset($duration) || $duration->Value() == '') { $instance_end = $comp->GetProperty($end_type); if (isset($instance_end)) { $dtend = new RepeatRuleDateTime($instance_end); if ($return_floating_times) { $dtend->setAsFloat(); } $dtend = $dtend->FloatOrUTC($return_floating_times); } else { $dtend = $dtstart + ($is_date ? $dtstart + 86400 : 0); } } else { $duration = new Rfc5545Duration($duration->Value()); $dtend = $dtstart + $duration->asSeconds(); } if ($dtend < $start_utc) { continue; } // End before start of range: skip that too. } if (DEBUG_RRULE) { printf("Replacing overridden instance at %s\n", $recurrence_id); } $new_components[$recurrence_id] = $comp; } } $vResource->SetComponents($new_components); return $vResource; }
/** * Expand the instances for a STANDARD or DAYLIGHT component of a VTIMEZONE * * @param object $vResource is a VCALENDAR with a VTIMEZONE containing components needing expansion * @param object $range_start A RepeatRuleDateTime which is the beginning of the range for events. * @param object $range_end A RepeatRuleDateTime which is the end of the range for events. * @param int $offset_from The offset from UTC in seconds at the onset time. * * @return array of onset datetimes with UTC from/to offsets */ function expand_timezone_onsets(vCalendar $vResource, RepeatRuleDateTime $range_start, RepeatRuleDateTime $range_end) { global $c; $vtimezones = $vResource->GetComponents(); $vtz = $vtimezones[0]; $components = $vtz->GetComponents(); $instances = array(); $dtstart = null; $is_date = false; $has_repeats = false; $zone_tz = $vtz->GetPValue('TZID'); foreach ($components as $k => $comp) { if (DEBUG_EXPAND) { printf("Starting TZ expansion for component '%s' in timezone '%s'\n", $comp->GetType(), $zone_tz); foreach ($instances as $k => $v) { print ' : ' . $k; } print "\n"; } $dtstart_prop = $comp->GetProperty('DTSTART'); if (!isset($dtstart_prop)) { continue; } $dtstart = new RepeatRuleDateTime($dtstart_prop); $dtstart->setTimeZone('UTC'); $offset_from = $comp->GetPValue('TZOFFSETFROM'); $offset_from = $offset_from / 100 * 3600 + abs($offset_from) % 100 * 60 * ($offset_from < 0 ? -1 : 0); $offset_from *= -1; $offset_from = "{$offset_from} seconds"; dbg_error_log('tz/update', "%s of offset\n", $offset_from); $dtstart->modify($offset_from); $is_date = $dtstart->isDate(); $instances[$dtstart->UTC('Y-m-d\\TH:i:s\\Z')] = $comp; $rrule = $comp->GetProperty('RRULE'); $has_repeats = isset($rrule); if (!$has_repeats) { continue; } $recur = $comp->GetProperty('RRULE'); if (isset($recur)) { $recur = $recur->Value(); $this_start = clone $dtstart; $rule = new RepeatRule($this_start, $recur, $is_date); $i = 0; $result_limit = 1000; while ($date = $rule->next()) { $instances[$date->UTC('Y-m-d\\TH:i:s\\Z')] = $comp; if ($i++ >= $result_limit || $date > $range_end) { break; } } if (DEBUG_EXPAND) { print "After rrule_expand"; foreach ($instances as $k => $v) { print ' : ' . $k; } print "\n"; } } $properties = $comp->GetProperties('RDATE'); if (count($properties)) { foreach ($properties as $p) { $timezone = $p->GetParameterValue('TZID'); $rdate = $p->Value(); $rdates = explode(',', $rdate); foreach ($rdates as $k => $v) { $rdate = new RepeatRuleDateTime($v, $timezone, $is_date); if ($return_floating_times) { $rdate->setAsFloat(); } $instances[$rdate->UTC('Y-m-d\\TH:i:s\\Z')] = $comp; if ($rdate > $range_end) { break; } } } if (DEBUG_EXPAND) { print "After rdate_expand"; foreach ($instances as $k => $v) { print ' : ' . $k; } print "\n"; } } } ksort($instances); $onsets = array(); $start_utc = $range_start->UTC('Y-m-d\\TH:i:s\\Z'); $end_utc = $range_end->UTC('Y-m-d\\TH:i:s\\Z'); foreach ($instances as $utc => $comp) { if ($utc > $end_utc) { if (DEBUG_EXPAND) { printf("We're done: {$utc} is out of the range.\n"); } break; } if ($utc < $start_utc) { continue; } $onsets[$utc] = array('from' => $comp->GetPValue('TZOFFSETFROM'), 'to' => $comp->GetPValue('TZOFFSETTO'), 'name' => $comp->GetPValue('TZNAME'), 'type' => $comp->GetType()); } return $onsets; }