/** * Exports this zone to a VTIMEZONE component. * * @return Horde_Icalendar_Vtimezone A VTIMEZONE component representing * this timezone. * @throws Horde_Timezone_Exception */ public function toVtimezone() { if (!count($this->_info)) { throw new Horde_Timezone_Exception('No rules found for timezone ' . $this->_name); } $tz = new Horde_Icalendar_Vtimezone(); $tz->setAttribute('TZID', $this->_name); if (count($this->_info[0]) <= 3) { // Zone has no start or end date, but DAYLIGHT and STANDARD // components are required to have a DTSTART attribute [RFC // 5545 3.8.2.4]. return $tz; } $startDate = $this->_getDate(0); $startOffset = $this->_getOffset(0); for ($i = 1, $c = count($this->_info); $i < $c; $i++) { $name = $this->_info[$i][2]; $endDate = count($this->_info[$i]) > 3 ? $this->_getDate($i) : null; if ($this->_info[$i][1] == '-') { // Standard time. $component = new Horde_Icalendar_Standard(); } elseif (preg_match('/\\d+(:(\\d+))?/', $this->_info[$i][1])) { // Indiviual rule not matching any ruleset. $component = new Horde_Icalendar_Daylight(); } else { // Represented by a ruleset. $startOffset = $this->_getOffset($i); $this->_tz->getRule($this->_info[$i][1])->addRules($tz, $this->_name, $name, $startOffset, $startDate, $endDate); $startDate = $endDate; // Continue, because addRules() already adds the // component to $tz. continue; } $component->setAttribute('DTSTART', $startDate); $component->setAttribute('TZOFFSETFROM', $startOffset); $startOffset = $this->_getOffset($i); $component->setAttribute('TZOFFSETTO', $startOffset); $component->setAttribute('TZNAME', $name); $tz->addComponent($component); } return $tz; }
/** * Adds rules from this ruleset to a VTIMEZONE component. * * @param Horde_Icalendar_Vtimezone $tz A VTIMEZONE component. * @param string $tzid The timezone ID of the component. * @param string $name A timezone name abbreviation. * May contain a placeholder that is * replaced the Rules' "Letter(s)" * entry. * @param array $startOffset An offset hash describing the * base offset of a timezone. * @param Horde_Date $start Start of the period to add rules * for. * @param Horde_Date $end End of the period to add rules * for. */ public function addRules(Horde_Icalendar_Vtimezone $tz, $tzid, $name, $startOffset, Horde_Date $start, Horde_Date $end = null) { $offset = $startOffset; foreach ($this->_rules as $rule) { $year = $rule[3]; if ($year[0] == 'o') { // TO is "only" $rule[3] = $rule[2]; } if ($rule[3][0] != 'm' && $rule[3] < $start->year) { // TO is not maximum and is before the searched period continue; } if ($end && $rule[2][0] != 'm' && $rule[2] > $end->year) { // FROM is not "minimum" and is after the searched period break; } if ($rule[2][0] != 'm' && $rule[2] < $start->year) { $rule[2] = $start->year; } if ($rule[8] == 0) { $component = new Horde_Icalendar_Standard(); $component->setAttribute('TZOFFSETFROM', $offset); $component->setAttribute('TZOFFSETTO', $startOffset); $offset = $startOffset; } else { $component = new Horde_Icalendar_Daylight(); $component->setAttribute('TZOFFSETFROM', $offset); $offset = $this->_getOffset($startOffset, $rule[8]); $component->setAttribute('TZOFFSETTO', $offset); } $month = Horde_Timezone::getMonth($rule[5]); // Retrieve time of rule start. preg_match('/(\\d+)(?::(\\d+))?(?::(\\d+))?(w|s|u)?/', $rule[7], $match); if (!isset($match[2])) { $match[2] = 0; } if ($rule[2] == $rule[3] && preg_match('/^\\d+$/', $rule[6])) { // Rule lasts only for a single year and starts on a specific // date. $rdate = new Horde_Date(array('year' => $rule[2], 'month' => Horde_Timezone::getMonth($rule[5]), 'mday' => $rule[6], 'hour' => $match[1], 'min' => $match[2], 'sec' => 0)); $component->setAttribute('DTSTART', $rdate); } elseif (substr($rule[6], 0, 4) == 'last') { // Rule starts on the last of a certain weekday of the month. $weekday = $this->_weekdays[substr($rule[6], 4, 3)]; $last = new Horde_Date(array('year' => $rule[2], 'month' => $month, 'mday' => Horde_Date_Utils::daysInMonth($month, $rule[2]), 'hour' => $match[1], 'min' => $match[2], 'sec' => 0)); while ($last->dayOfWeek() != $weekday) { $last->mday--; } $component->setAttribute('DTSTART', $last); if ($rule[3][0] == 'm') { $until = ''; } else { $last = new Horde_Date(array('year' => $rule[3], 'month' => $month, 'mday' => Horde_Date_Utils::daysInMonth($month, $rule[2]), 'hour' => $match[1], 'min' => $match[2], 'sec' => 0), $tzid); while ($last->dayOfWeek() != $weekday) { $last->mday--; } $last->setTimezone('UTC'); $until = ';UNTIL=' . $last->format('Ymd\\THIs') . 'Z'; } $component->setAttribute('RRULE', 'FREQ=YEARLY;BYDAY=-1' . Horde_String::upper(substr($rule[6], 4, 2)) . ';BYMONTH=' . $month . $until); } elseif (strpos($rule[6], '>=')) { // Rule starts on a certain weekday after a certain day of // month. list($weekday, $day) = explode('>=', $rule[6]); $weekdayInt = $this->_weekdays[substr($weekday, 0, 3)]; $first = new Horde_Date(array('year' => $rule[2], 'month' => $month, 'mday' => $day, 'hour' => $match[1], 'min' => $match[2], 'sec' => 0)); while ($first->dayOfWeek() != $weekdayInt) { $first->mday++; } $component->setAttribute('DTSTART', $first); if ($rule[3][0] == 'm') { $until = ''; } else { $last = new Horde_Date(array('year' => $rule[3], 'month' => $month, 'mday' => $day, 'hour' => $match[1], 'min' => $match[2], 'sec' => 0), $tzid); while ($last->dayOfWeek() != $weekday) { $last->mday++; } $last->setTimezone('UTC'); $until = ';UNTIL=' . $last->format('Ymd\\THIs') . 'Z'; } for ($days = array(), $i = $day, $lastDay = min(Horde_Date_Utils::daysInMonth($month, $rule[2]), $i + 6); $day > 1 && $i <= $lastDay; $i++) { $days[] = $i; } $component->setAttribute('RRULE', 'FREQ=YEARLY;BYMONTH=' . $month . ($days ? ';BYMONTHDAY=' . implode(',', $days) : '') . ';BYDAY=1' . Horde_String::upper(substr($weekday, 0, 2)) . $until); } elseif (strpos($rule[6], '<=')) { // Rule starts on a certain weekday before a certain day of // month. list($weekday, $day) = explode('>=', $rule[6]); $weekdayInt = $this->_weekdays[substr($weekday, 0, 3)]; $last = new Horde_Date(array('year' => $rule[2], 'month' => $month, 'mday' => $day, 'hour' => $match[1], 'min' => $match[2], 'sec' => 0)); while ($last->dayOfWeek() != $weekdayInt) { $last->mday--; } $component->setAttribute('DTSTART', $last); if ($rule[3][0] == 'm') { $until = ''; } else { $last = new Horde_Date(array('year' => $rule[3], 'month' => $month, 'mday' => $day, 'hour' => $match[1], 'min' => $match[2], 'sec' => 0), $tzid); while ($last->dayOfWeek() != $weekday) { $last->mday--; } $last->setTimezone('UTC'); $until = ';UNTIL=' . $last->format('Ymd\\THIs') . 'Z'; } for ($days = array(), $i = 1; $i <= $day; $i++) { $days[] = $i; } $component->setAttribute('RRULE', 'FREQ=YEARLY;BYMONTH=' . $month . ';BYMONTHDAY=' . implode(',', $days) . ';BYDAY=-1' . Horde_String::upper(substr($weekday, 0, 2)) . $until); } $component->setAttribute('TZNAME', sprintf($name, $rule[9])); $tz->addComponent($component); } }
/** * Adds rules from this ruleset to a VTIMEZONE component. * * @param Horde_Icalendar_Vtimezone $tz A VTIMEZONE component. * @param string $tzid The timezone ID of the component. * @param string $name A timezone name abbreviation. * May contain a placeholder that is * replaced the Rules' "Letter(s)" * entry. * @param array $startOffset An offset hash describing the * base offset of a timezone. * @param Horde_Date $start Start of the period to add rules * for. * @param Horde_Date $end End of the period to add rules * for. */ public function addRules(Horde_Icalendar_Vtimezone $tz, $tzid, $name, $startOffset, Horde_Date $start, Horde_Date $end = null) { foreach ($this->_rules as $ruleNo => $rule) { // The rule items are: // 0: "Rule" // 1: The rule name // 2: The start year or "minimum" // 3: The end year or "only" if only at the start year or "maximum" // 4: Type, whatever that means (unused) // 5: The start month // 6: The start day, either specific or as a rule // 7: The start time // 8: The offset to the base time // 9: The time name abbreviation $year = $rule[3]; if ($year[0] == 'o') { // TO is "only" $rule[3] = $rule[2]; } if ($rule[3][0] != 'm' && $rule[3] < $start->year) { // TO is not "maximum" and is before the searched period continue; } if ($end && $rule[2][0] != 'm' && $rule[2] > $end->year) { // FROM is not "minimum" and is after the searched period break; } if ($rule[2][0] != 'm' && $rule[2] < $start->year) { $rule[2] = $start->year; } // The month of rule start. $month = Horde_Timezone::getMonth($rule[5]); // The time of rule start. preg_match('/(\\d+)(?::(\\d+))?(?::(\\d+))?([wsguz])?/', $rule[7], $match); $hour = $match[1]; $minute = isset($match[2]) ? $match[2] : 0; if (!isset($match[4])) { $modifier = 'w'; } elseif ($match[4] == 'g' || $match[4] == 'z') { $modifier = 'u'; } else { $modifier = $match[4]; } // Find the start date. $first = $this->_getFirstMatch($rule, $rule[2]); $first->hour = $hour; $first->min = $minute; $previousOffset = $this->_findPreviousOffset($first, $ruleNo, $startOffset); if ($rule[8] == 0) { $component = new Horde_Icalendar_Standard(); $component->setAttribute('TZOFFSETFROM', $previousOffset); $component->setAttribute('TZOFFSETTO', $startOffset); } else { $component = new Horde_Icalendar_Daylight(); $component->setAttribute('TZOFFSETFROM', $previousOffset); $component->setAttribute('TZOFFSETTO', $this->_getOffset($startOffset, $rule[8])); } switch ($modifier) { case 's': $first->hour += ($previousOffset['ahead'] ? 1 : -1) * $previousOffset['hour'] - ($startOffset['ahead'] ? 1 : -1) * $startOffset['hour']; $first->min += ($previousOffset['ahead'] ? 1 : -1) * $previousOffset['minute'] - ($startOffset['ahead'] ? 1 : -1) * $startOffset['minute']; break; case 'u': $first->hour += ($previousOffset['ahead'] ? 1 : -1) * $previousOffset['hour']; $first->min += ($previousOffset['ahead'] ? 1 : -1) * $previousOffset['minute']; break; } $component->setAttribute('DTSTART', $first); // Find the end date. if ($rule[3][0] == 'm') { $until = ''; } else { $last = $this->_getFirstMatch($rule, $rule[3]); $last->hour = $hour; $last->min = $minute; switch ($modifier) { case 's': $last->hour -= ($startOffset['ahead'] ? 1 : -1) * $startOffset['hour']; $last->min -= ($startOffset['ahead'] ? 1 : -1) * $startOffset['minute']; break; case 'w': $last->hour -= ($previousOffset['ahead'] ? 1 : -1) * $previousOffset['hour']; $last->min -= ($previousOffset['ahead'] ? 1 : -1) * $previousOffset['minute']; break; } $until = ';UNTIL=' . $last->format('Ymd\\THis') . 'Z'; } if ($rule[2] != $rule[3]) { if (preg_match('/^\\d+$/', $rule[6])) { // Rule starts on a specific date. $component->setAttribute('RRULE', 'FREQ=YEARLY;BYMONTH=' . $month . ';BYMONTHDAY=' . $rule[6] . $until); } elseif (substr($rule[6], 0, 4) == 'last') { // Rule starts on the last of a certain weekday of the month. $component->setAttribute('RRULE', 'FREQ=YEARLY;BYDAY=-1' . Horde_String::upper(substr($rule[6], 4, 2)) . ';BYMONTH=' . $month . $until); } elseif (strpos($rule[6], '>=')) { // Rule starts on a certain weekday after a certain day of // month. list($weekday, $day) = explode('>=', $rule[6]); for ($days = array(), $i = $day, $lastDay = min(Horde_Date_Utils::daysInMonth($month, $rule[2]), $i + 6); $day > 1 && $i <= $lastDay; $i++) { $days[] = $i; } $component->setAttribute('RRULE', 'FREQ=YEARLY;BYMONTH=' . $month . ($days ? ';BYMONTHDAY=' . implode(',', $days) : '') . ';BYDAY=1' . Horde_String::upper(substr($weekday, 0, 2)) . $until); } elseif (strpos($rule[6], '<=')) { // Rule starts on a certain weekday before a certain day of // month. for ($days = array(), $i = 1; $i <= $day; $i++) { $days[] = $i; } $component->setAttribute('RRULE', 'FREQ=YEARLY;BYMONTH=' . $month . ';BYMONTHDAY=' . implode(',', $days) . ';BYDAY=-1' . Horde_String::upper(substr($weekday, 0, 2)) . $until); } else { continue; } } $component->setAttribute('TZNAME', sprintf($name, $rule[9])); $tz->addComponent($component); } }