. */ /** * Handles timezones. * * This method extracts information from PHP's Timezone DB, and allows * configuring server's timezone information. * * @package Date * @since 4.6.0 */ namespace Textpattern\Date; class Timezone { /** * Stores a list of details about each timezone. * * @var array */ protected $details; /** * Stores a list of timezone offsets. * * @var array */ protected $offsets; /** * An array of accepted continents. * * @var array */ protected $continents = array( 'Africa', 'America', 'Antarctica', 'Arctic', 'Asia', 'Atlantic', 'Australia', 'Europe', 'Indian', 'Pacific', ); /** * Gets an array of safe timezones supported on this server. * * The following: * * * print_r(Txp::get('\Textpattern\Date\Timezone')->getTimeZones()); * * * Returns: * * * Array * ( * [America/New_York] => Array * ( * [continent] => America * [city] => New_York * [subcity] => * [offset] => -18000 * [dst] => 1 * ) * [Europe/London] => Array * ( * [continent] => Europe * [city] => London * [subcity] => * [offset] => 0 * [dst] => 1 * ) * ) * * * Offset is the timezone offset from UTC excluding daylight saving time, * DST is whether it's currently DST in the timezone. Identifiers are * sorted alphabetically. * * @return array|bool An array of timezones, or FALSE on failure */ public function getTimeZones() { if ($this->details === null) { $this->details = array(); if (($timezones = \DateTimeZone::listIdentifiers()) === false) { return false; } foreach ($timezones as $timezone) { $parts = explode('/', $timezone); if (in_array($parts[0], $this->continents, true) && $data = $this->getIdentifier($timezone)) { $this->details[$timezone] = $data; if (!isset($this->offsets[$data['offset']])) { $this->offsets[$data['offset']] = array(); } $this->offsets[$data['offset']][] = $timezone; } } ksort($this->details); } return $this->details; } /** * Gets timezone identifiers for the given timezone offset. * * More than one timezone might fit any given offset, thus the returned * value is ambiguous and merely useful for presentation purposes. * * * print_r(Txp::get('\Textpattern\Date\Timezone')->getOffsetIdentifiers(3600)); * * * Returns: * * * Array * ( * [0] => Africa/Malabo * [1] => Europe/Amsterdam * [2] => Europe/Berlin * [3] => Europe/Zurich * ) * * * @param int $offset Offset in seconds * @return array|bool An array of timezone identifiers, or FALSE */ public function getOffsetIdentifiers($offset) { if ($this->getTimeZones() && isset($this->offsets[$offset])) { return $this->offsets[$offset]; } return false; } /** * Whether DST is in effect. * * The given timestamp can either be a date format supported by DateTime, * UNIX timestamp or NULL to check current status. * * If timezone is NULL, checks the server default timezone. * * * echo Txp::get('\Textpattern\Date\Timezone')->isDst('2013/06/20', 'Europe/London'); * * * Returns TRUE, while this returns FALSE as the timezone does not use * daylight saving time: * * * echo Txp::get('\Textpattern\Date\Timezone')->isDst('2013/06/20', 'Africa/Accra'); * * * If it's winter this returns FALSE: * * * echo Txp::get('\Textpattern\Date\Timezone')->isDst(null, 'Europe/London'); * * * @param string|int|null $timestamp Time to check * @param string|null $timezone Timezone identifier * @return bool TRUE if timezone is using DST */ public function isDst($timestamp = null, $timezone = null) { static $DTZones = array(); if (!$timezone) { $timezone = $this->getTimeZone(); } if ($timestamp === null) { $timestamp = time(); } else { if ((string)intval($timestamp) !== (string)$timestamp) { $timestamp = strtotime($timestamp); } } try { if (!isset($DTZones[$timezone])) { $DTZones[$timezone] = new \DateTimeZone($timezone); } $transition = $DTZones[$timezone]->getTransitions($timestamp, $timestamp); $isdst = $transition[0]['isdst']; } catch (\Exception $e) { $isdst = false; } return (bool)$isdst; } /** * Gets the next daylight saving transition period for the given timezone. * * Returns FALSE if the timezone does not use DST, or will in the future * drop DST. * * * print_r(Txp::get('\Textpattern\Date\Timezone')->getDstPeriod('Europe/Helsinki')); * * * Returns: * * * Array * ( * [0] => Array * ( * [ts] => 1396141200 * [time] => 2014-03-30T01:00:00+0000 * [offset] => 10800 * [isdst] => 1 * [abbr] => EEST * ) * [1] => Array * ( * [ts] => 1414285200 * [time] => 2014-10-26T01:00:00+0000 * [offset] => 7200 * [isdst] => * [abbr] => EET * ) * ) * * * @param string|null $timezone The timezone identifier * @param int $from Next transitions starting from when * @return array|bool An array of next two transitions, or FALSE * @throws \Exception */ public function getDstPeriod($timezone = null, $from = null) { if (!$timezone) { $timezone = $this->getTimeZone(); } $timezone = new \DateTimeZone($timezone); if ($from === null) { $from = time(); } $transitions = $timezone->getTransitions(); $start = null; $end = null; foreach ($transitions as $transition) { if ($start !== null) { $end = $transition; break; } if ($transition['ts'] >= $from && $transition['isdst']) { $start = $transition; } } if ($start) { return array($start, $end); } return false; } /** * Gets timezone abbreviation. * * If the $timezone is NULL, uses the server default. Returns FALSE if * there is no abbreviation to give. * * * echo Txp::get('\Textpattern\Date\Timezone')->getTimeZoneAbbreviation('Europe/London'); * * * Returns 'GMT', while the following returns 'FALSE': * * * echo Txp::get('\Textpattern\Date\Timezone')->getTimeZoneAbbreviation('Africa/Accra', true); * * * As according to the timezone database, the timezone does not currently * use DST. * * @param string $timezone Timezone identifier * @param bool $dst TRUE to get the abbreviation during DST * @return string|bool The abbreviation, or FALSE on failure */ public function getTimeZoneAbbreviation($timezone = null, $dst = false) { try { if ($timezone === null) { $timezone = $this->getTimeZone(); } $timezone = new \DateTimeZone($timezone); $time = time(); if ($transitions = $timezone->getTransitions()) { $latest = end($transitions); if ($latest['ts'] <= $time) { $latest['ts'] = $time; $transitions = array($latest); } foreach ($transitions as $transition) { if ($time <= $transition['ts']) { if ($dst === true && $transition['isdst']) { return $transition['abbr']; } if ($dst === false && !$transition['isdst']) { return $transition['abbr']; } } } } } catch (\Exception $e) { } return false; } /** * Gets a timezone identifier. * * Extracts information about the given timezone. If the $timezone is NULL, * uses the server's default timezone. * * * print_r(Txp::get('\Textpattern\Date\Timezone')->getIdentifier('Europe/London')); * * * Returns: * * * Array * ( * [continent] => Europe * [city] => London * [subcity] => * [offset] => 0 * [dst] => 1 * ) * * * @param string|null $timezone Timezone identifier * @return array|bool An array, or FALSE on failure */ public function getIdentifier($timezone = null) { if ($timezone === null) { $timezone = $this->getTimeZone(); } if (isset($this->details[$timezone])) { return $this->details[$timezone]; } try { $dateTime = new \DateTime('now', new \DateTimeZone($timezone)); $data = array( 'continent' => '', 'city' => '', 'subcity' => '', 'offset' => $dateTime->getOffset(), 'dst' => false, ); if (strpos($timezone, '/') !== false) { $parts = array_pad(explode('/', $timezone), 3, ''); $data['continent'] = $parts[0]; $data['city'] = $parts[1]; $data['subcity'] = $parts[2]; } if ($dateTime->format('I')) { $data['offset'] -= 3600; $data['dst'] = true; } return $data; } catch (\Exception $e) { return false; } } /** * Sets the server default timezone. * * If an array of identifiers is given, the first one supported is used. * * * echo Txp::get('\Textpattern\Date\Timezone')->setTimeZone('UTC'); * * * Throws an exception if the identifier isn't valid. * * @param array|string $identifiers The timezone identifier * @return Timezone * @throws \Exception */ public function setTimeZone($identifiers) { foreach ((array)$identifiers as $identifier) { if (@date_default_timezone_set($identifier)) { return $this; } } throw new \Exception(gTxt('invalid_argument', array('{name}' => 'identifiers'))); } /** * Gets the server default timezone. * * * echo Txp::get('\Textpattern\Date\Timezone')->setTimeZone('Europe/London')->getTimeZone(); * * * The above returns 'Europe/London'. * * @return string|bool Timezone identifier */ public function getTimeZone() { return @date_default_timezone_get(); } }