This class is the central point to fetch timezone information from the
timezone (Olson) database, parse it, cache it, and generate VTIMEZONE
objects.
Usage:
$tz = new Horde_Timezone();
$tz->getZone('America/New_York')->toVtimezone()->exportVcalendar();
Documentation on the database file formats can be found at
ftp://ftp.iana.org/tz/tz-how-to.html
/** * 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); } }
/** * Finds a date matching a rule definition. * * @param array $rule A rule definition hash from addRules(). * @param integer $year A year when the rule should be applied. * * @return Horde_Date The first matching date. */ protected function _getFirstMatch($rule, $year) { $month = Horde_Timezone::getMonth($rule[5]); if (preg_match('/^\\d+$/', $rule[6])) { // Rule starts on a specific date. $date = new Horde_Date(array('year' => $year, 'month' => $month, 'mday' => $rule[6])); } 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)]; $date = new Horde_Date(array('year' => $year, 'month' => $month, 'mday' => Horde_Date_Utils::daysInMonth($month, $rule[2]))); while ($date->dayOfWeek() != $weekday) { $date->mday--; } } 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)]; $date = new Horde_Date(array('year' => $year, 'month' => $month, 'mday' => $day)); while ($date->dayOfWeek() != $weekdayInt) { $date->mday++; } } 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)]; $date = new Horde_Date(array('year' => $year, 'month' => $month, 'mday' => $day)); while ($date->dayOfWeek() != $weekdayInt) { $date->mday--; } } else { throw new Horde_Timezone_Exception('Cannot parse rule ' . $rule[6]); } return $date; }
/** * Calculates a date from the date columns of a Zone line. * * @param integer $line A line number. * * @return Horde_Date The date of this line. */ protected function _getDate($line) { $date = array_slice($this->_info[$line], 3); $year = $date[0]; $month = isset($date[1]) ? Horde_Timezone::getMonth($date[1]) : 1; $day = isset($date[2]) ? $date[2] : 1; $time = isset($date[3]) && $date[3] != '-' ? $date[3] : 0; preg_match('/(\\d+)(?::(\\d+))?(?::(\\d+))?(w|s|u)?/', $time, $match); if (!isset($match[2])) { $match[2] = 0; } if (!isset($match[3])) { $match[3] = 0; } switch (substr($time, -1)) { case 's': // Standard time. Not sure what to do about this. break; case 'u': // UTC, add offset. $offset = $this->_getOffset($line); $factor = $offset['ahead'] ? 1 : -1; $match[1] += $factor * $offset['hour']; $match[2] += $factor * $offset['minute']; case 'w': default: // Wall time, nothing to do. break; } return new Horde_Date(array('year' => $year, 'month' => $month, 'mday' => $day, 'hour' => $match[1], 'min' => $match[2], 'sec' => $match[3])); }
public function __construct($zone) { parent::__construct(); $this->_zone = $zone; }