Files
server/usr/share/psa-pear/pear/php/Horde/Timezone.php
2026-01-07 20:52:11 +01:00

305 lines
9.6 KiB
PHP

<?php
/**
* Copyright 2011-2017 Horde LLC (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*
* @author Jan Schneider <jan@horde.org>
* @package Timezone
*/
/**
* Base class for loading, parsing, and working with timezones.
*
* This class is the central point to fetch timezone information from the
* timezone (Olson) database, parse it, cache it, and generate VTIMEZONE
* objects.
*
* Usage:
* <code>
* $tz = new Horde_Timezone();
* $tz->getZone('America/New_York')->toVtimezone()->exportVcalendar();
* </code>
*
* Documentation on the database file formats can be found at
* ftp://ftp.iana.org/tz/tz-how-to.html
*
* @author Jan Schneider <jan@horde.org>
* @package Timezone
*/
class Horde_Timezone
{
/**
* Any configuration parameters for this class.
*
* @var array
*/
protected $_params;
/**
* File location of the downloaded timezone database.
*
* @var string
*/
protected $_tmpfile;
/**
* List of all Zone entries parsed into Horde_Timezone_Zone objects.
*
* @var array
*/
protected $_zones = array();
/**
* List of all Rule entries parsed into Horde_Timezone_Rule objects.
*
* @var array
*/
protected $_rules = array();
/**
* Alias map of all Link entries.
*
* @var array
*/
protected $_links = array();
/**
* List to map month descriptions used in the timezone database.
*
* @var array
*/
protected static $_months = array('Jan' => 1,
'Feb' => 2,
'Mar' => 3,
'Apr' => 4,
'May' => 5,
'Jun' => 6,
'Jul' => 7,
'Aug' => 8,
'Sep' => 9,
'Oct' => 10,
'Nov' => 11,
'Dec' => 12);
/**
* Constructor.
*
* @param array $params List of optional class parameters. Possible
* options:
* - location: (string) Location of the timezone
* database, defaults to
* ftp.iana.org/tz/tzdata-latest.tar.gz.
* - client: (Horde_Http_Client) A preconfigured
* HTTP client for downloading via HTTP.
* - temp: (string) Temporary directory.
* - cache: (Horde_Cache) A cache object.
* - cachettl: (integer) Cache lifetime in seconds,
* defaults to 7 days.
*/
public function __construct(array $params = array())
{
$this->_params = array_merge(
array('location' => 'ftp://ftp.iana.org/tz/tzdata-latest.tar.gz',
'cachettl' => 604800),
$params);
}
/**
* Returns the month number of a month name.
*
* @param string $month A month name.
*
* @return integer The month's number.
*/
public static function getMonth($month)
{
return self::$_months[substr($month, 0, 3)];
}
/**
* Returns an object representing an invidual timezone.
*
* Maps to a "Zone" entry in the timezone database. Works with
* zone aliases and other common timezone names too.
*
* @param string $zone A timezone name.
*
* @return Horde_Timezone_Zone A timezone object.
*/
public function getZone($zone)
{
if (!$this->_zones) {
$this->_extractAndParse();
}
$zone = Horde_Date::getTimezoneAlias($zone);
$alias = isset($this->_links[$zone]) ? $this->_links[$zone] : $zone;
if (!isset($this->_zones[$alias])) {
throw new Horde_Timezone_Exception(sprintf('Timezone %s not found', $zone));
}
$this->_zones[$alias]->setTzid($zone);
return $this->_zones[$alias];
}
/**
* Returns an object representing a set of named transition rules.
*
* Maps to a list Rule entries of the same name in the timezone database.
*
* @param string $rule A rule name.
*
* @return Horde_Timezone_Rule A rule object.
*/
public function getRule($rule)
{
if (!$this->_rules) {
$this->_extractAndParse();
}
if (!isset($this->_rules[$rule])) {
throw new Horde_Timezone_Exception(sprintf('Timezone rule %s not found', $rule));
}
return $this->_rules[$rule];
}
/**
* Downloads a timezone database.
*
* @throws Horde_Timezone_Exception if downloading fails.
*/
protected function _download()
{
$url = @parse_url($this->_params['location']);
if (!isset($url['scheme'])) {
throw new Horde_Timezone_Exception('"location" parameter is missing an URL scheme.');
}
if (!in_array($url['scheme'], array('http', 'ftp', 'file'))) {
throw new Horde_Timezone_Exception(sprintf('Unsupported URL scheme "%s"', $url['scheme']));
}
if ($url['scheme'] == 'http') {
if (isset($this->_params['client'])) {
$client = $this->_params['client'];
} else {
$client = new Horde_Http_Client();
}
$response = $client->get($this->_params['location']);
$this->_tmpfile = Horde_Util::getTempFile(
'', true,
isset($this->_params['temp']) ? $this->_params['temp'] : '');
stream_copy_to_stream($response->getStream(), fopen($this->_tmpfile, 'w'));
return;
}
if ($url['scheme'] == 'ftp') {
try {
$vfs = new Horde_Vfs_Ftp(array('hostspec' => $url['host'],
'username' => 'anonymous',
'password' => 'anonymous',
'pasv' => true));
$this->_tmpfile = $vfs->readFile(dirname($url['path']),
basename($url['path']));
} catch (Horde_Vfs_Exception $e) {
throw new Horde_Timezone_Exception($e);
}
} else {
$this->_tmpfile = $this->_params['location'];
unset($php_errormsg);
if (!is_readable($this->_tmpfile)) {
$e = new Horde_Timezone_Exception(sprintf('Unable to open file %s.', $this->_params['location']));
if (isset($php_errormsg)) {
$e->details = $php_errormsg;
}
throw $e;
}
}
}
/**
* Unpacks the downloaded timezone database and parses all files.
*/
protected function _extractAndParse()
{
if (isset($this->_params['cache'])) {
$result = @unserialize(
$this->_params['cache']->get(
'horde_timezone',
$this->_params['cachettl'])
);
if ($result) {
$this->_zones = $result['zones'];
$this->_rules = $result['rules'];
$this->_links = $result['links'];
return;
}
}
if (!$this->_tmpfile) {
$this->_download();
}
$tar = new Archive_Tar($this->_tmpfile);
foreach ($tar->listContent() as $file) {
if ($file['typeflag'] != 0) {
continue;
}
$this->_parse($tar->extractInString($file['filename']));
}
if (isset($this->_params['cache'])) {
$this->_params['cache']->set(
'horde_timezone',
serialize(array(
'zones' => $this->_zones,
'rules' => $this->_rules,
'links' => $this->_links
)),
$this->_params['cachettl']);
}
}
/**
* Parses a file from the timezone database.
*
* @param string $file A file location.
*/
protected function _parse($file)
{
$stream = new Horde_Support_StringStream($file);
$fp = $stream->fopen();
$zone = null;
while (($line = fgets($fp)) !== false) {
$line = trim($line);
if (!strlen($line) || $line[0] == '#') {
continue;
}
$column = preg_split('/\s+/', preg_replace('/\s*#.*$/', '', $line));
switch ($column[0]) {
case 'Rule':
if (!isset($this->_rules[$column[1]])) {
$this->_rules[$column[1]] = new Horde_Timezone_Rule($column[1]);
}
$this->_rules[$column[1]]->add($column);
$zone = null;
break;
case 'Link':
$this->_links[$column[2]] = $column[1];
$zone = null;
break;
case 'Zone':
$zone = $column[1];
$this->_zones[$zone] = new Horde_Timezone_Zone($zone, $this);
array_splice($column, 0, 2);
// Fall through.
default:
if (empty($zone) || !isset($this->_zones[$zone])) {
break;
}
$this->_zones[$zone]->add($column);
break;
}
}
}
}