'UTC', 'Z' => 'UTC', 'AUS Central Standard Time' => 'Australia/Darwin', 'AUS Eastern Standard Time' => 'Australia/Sydney', 'Acre' => 'America/Rio_Branco', 'Afghanistan' => 'Asia/Kabul', 'Afghanistan Standard Time' => 'Asia/Kabul', 'Africa_Central' => 'Africa/Maputo', 'Africa_Eastern' => 'Africa/Nairobi', 'Africa_FarWestern' => 'Africa/El_Aaiun', 'Africa_Southern' => 'Africa/Johannesburg', 'Africa_Western' => 'Africa/Lagos', 'Aktyubinsk' => 'Asia/Aqtobe', 'Alaska' => 'America/Juneau', 'Alaska_Hawaii' => 'America/Anchorage', 'Alaskan Standard Time' => 'America/Anchorage', 'Almaty' => 'Asia/Almaty', 'Amazon' => 'America/Manaus', 'America_Central' => 'America/Chicago', 'America_Eastern' => 'America/New_York', 'America_Mountain' => 'America/Denver', 'America_Pacific' => 'America/Los_Angeles', 'Anadyr' => 'Asia/Anadyr', 'Aqtau' => 'Asia/Aqtau', 'Aqtobe' => 'Asia/Aqtobe', 'Arab Standard Time' => 'Asia/Riyadh', 'Arabian' => 'Asia/Riyadh', 'Arabian Standard Time' => 'Asia/Dubai', 'Arabic Standard Time' => 'Asia/Baghdad', 'Argentina' => 'America/Buenos_Aires', 'Argentina Standard Time' => 'America/Buenos_Aires', 'Argentina_Western' => 'America/Mendoza', 'Armenia' => 'Asia/Yerevan', 'Armenian Standard Time' => 'Asia/Yerevan', 'Ashkhabad' => 'Asia/Ashgabat', 'Atlantic' => 'America/Halifax', 'Atlantic Standard Time' => 'America/Halifax', 'Australia_Central' => 'Australia/Adelaide', 'Australia_CentralWestern' => 'Australia/Eucla', 'Australia_Eastern' => 'Australia/Sydney', 'Australia_Western' => 'Australia/Perth', 'Azerbaijan' => 'Asia/Baku', 'Azerbaijan Standard Time' => 'Asia/Baku', 'Azores' => 'Atlantic/Azores', 'Azores Standard Time' => 'Atlantic/Azores', 'Baku' => 'Asia/Baku', 'Bangladesh' => 'Asia/Dhaka', 'Bering' => 'America/Adak', 'Bhutan' => 'Asia/Thimphu', 'Bolivia' => 'America/La_Paz', 'Borneo' => 'Asia/Kuching', 'Brasilia' => 'America/Sao_Paulo', 'British' => 'Europe/London', 'Brunei' => 'Asia/Brunei', 'Canada Central Standard Time' => 'America/Regina', 'Cape Verde Standard Time' => 'Atlantic/Cape_Verde', 'Cape_Verde' => 'Atlantic/Cape_Verde', 'Caucasus Standard Time' => 'Asia/Yerevan', 'Cen. Australia Standard Time' => 'Australia/Adelaide', 'Central America Standard Time' => 'America/Guatemala', 'Central Asia Standard Time' => 'Asia/Dhaka', 'Central Brazilian Standard Time' => 'America/Manaus', 'Central Europe Standard Time' => 'Europe/Budapest', 'Central European Standard Time' => 'Europe/Warsaw', 'Central Pacific Standard Time' => 'Pacific/Guadalcanal', 'Central Standard Time' => 'America/Chicago', 'Central Standard Time (Mexico)' => 'America/Mexico_City', 'Chamorro' => 'Pacific/Saipan', 'Changbai' => 'Asia/Harbin', 'Chatham' => 'Pacific/Chatham', 'Chile' => 'America/Santiago', 'China' => 'Asia/Shanghai', 'China Standard Time' => 'Asia/Shanghai', 'Choibalsan' => 'Asia/Choibalsan', 'Christmas' => 'Indian/Christmas', 'Cocos' => 'Indian/Cocos', 'Colombia' => 'America/Bogota', 'Cook' => 'Pacific/Rarotonga', 'Cuba' => 'America/Havana', 'Dacca' => 'Asia/Dhaka', 'Dateline Standard Time' => 'Etc/GMT+12', 'Davis' => 'Antarctica/Davis', 'Dominican' => 'America/Santo_Domingo', 'DumontDUrville' => 'Antarctica/DumontDUrville', 'Dushanbe' => 'Asia/Dushanbe', 'Dutch_Guiana' => 'America/Paramaribo', 'E. Africa Standard Time' => 'Africa/Nairobi', 'E. Australia Standard Time' => 'Australia/Brisbane', 'E. Europe Standard Time' => 'Europe/Minsk', 'E. South America Standard Time' => 'America/Sao_Paulo', 'East_Timor' => 'Asia/Dili', 'Easter' => 'Pacific/Easter', 'Eastern Standard Time' => 'America/New_York', 'Ecuador' => 'America/Guayaquil', 'Egypt Standard Time' => 'Africa/Cairo', 'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg', 'Europe_Central' => 'Europe/Paris', 'Europe_Eastern' => 'Europe/Bucharest', 'Europe_Western' => 'Atlantic/Canary', 'FLE Standard Time' => 'Europe/Kiev', 'Falkland' => 'Atlantic/Stanley', 'Fiji' => 'Pacific/Fiji', 'Fiji Standard Time' => 'Pacific/Fiji', 'French_Guiana' => 'America/Cayenne', 'French_Southern' => 'Indian/Kerguelen', 'Frunze' => 'Asia/Bishkek', 'GMT' => 'UTC', // seems better than 'Atlantic/Reykjavik' 'GMT Standard Time' => 'Europe/London', 'GTB Standard Time' => 'Europe/Istanbul', 'Galapagos' => 'Pacific/Galapagos', 'Gambier' => 'Pacific/Gambier', 'Georgia' => 'Asia/Tbilisi', 'Georgian Standard Time' => 'Etc/GMT-3', 'Gilbert_Islands' => 'Pacific/Tarawa', 'Goose_Bay' => 'America/Goose_Bay', 'Greenland Standard Time' => 'America/Godthab', 'Greenland_Central' => 'America/Scoresbysund', 'Greenland_Eastern' => 'America/Scoresbysund', 'Greenland_Western' => 'America/Godthab', 'Greenwich Standard Time' => 'Atlantic/Reykjavik', 'Guam' => 'Pacific/Guam', 'Gulf' => 'Asia/Dubai', 'Guyana' => 'America/Guyana', 'Hawaii_Aleutian' => 'Pacific/Honolulu', 'Hawaiian Standard Time' => 'Pacific/Honolulu', 'Hong_Kong' => 'Asia/Hong_Kong', 'Hovd' => 'Asia/Hovd', 'India' => 'Asia/Calcutta', 'India Standard Time' => 'Asia/Calcutta', 'Indian_Ocean' => 'Indian/Chagos', 'Indochina' => 'Asia/Saigon', 'Indonesia_Central' => 'Asia/Makassar', 'Indonesia_Eastern' => 'Asia/Jayapura', 'Indonesia_Western' => 'Asia/Jakarta', 'Iran' => 'Asia/Tehran', 'Iran Standard Time' => 'Asia/Tehran', 'Irish' => 'Europe/Dublin', 'Irkutsk' => 'Asia/Irkutsk', 'Israel' => 'Asia/Jerusalem', 'Israel Standard Time' => 'Asia/Jerusalem', 'Japan' => 'Asia/Tokyo', 'Jordan Standard Time' => 'Asia/Amman', 'Kamchatka' => 'Asia/Kamchatka', 'Karachi' => 'Asia/Karachi', 'Kashgar' => 'Asia/Kashgar', 'Kazakhstan_Eastern' => 'Asia/Almaty', 'Kazakhstan_Western' => 'Asia/Aqtobe', 'Kizilorda' => 'Asia/Qyzylorda', 'Korea' => 'Asia/Seoul', 'Korea Standard Time' => 'Asia/Seoul', 'Kosrae' => 'Pacific/Kosrae', 'Krasnoyarsk' => 'Asia/Krasnoyarsk', 'Kuybyshev' => 'Europe/Samara', 'Kwajalein' => 'Pacific/Kwajalein', 'Kyrgystan' => 'Asia/Bishkek', 'Lanka' => 'Asia/Colombo', 'Liberia' => 'Africa/Monrovia', 'Line_Islands' => 'Pacific/Kiritimati', 'Long_Shu' => 'Asia/Chongqing', 'Lord_Howe' => 'Australia/Lord_Howe', 'Macau' => 'Asia/Macau', 'Magadan' => 'Asia/Magadan', 'Malaya' => 'Asia/Kuala_Lumpur', 'Malaysia' => 'Asia/Kuching', 'Maldives' => 'Indian/Maldives', 'Marquesas' => 'Pacific/Marquesas', 'Marshall_Islands' => 'Pacific/Majuro', 'Mauritius' => 'Indian/Mauritius', 'Mauritius Standard Time' => 'Indian/Mauritius', 'Mawson' => 'Antarctica/Mawson', 'Mexico Standard Time' => 'America/Mexico_City', 'Mexico Standard Time 2' => 'America/Chihuahua', 'Mid-Atlantic Standard Time' => 'Atlantic/South_Georgia', 'Middle East Standard Time' => 'Asia/Beirut', 'Mongolia' => 'Asia/Ulaanbaatar', 'Montevideo Standard Time' => 'America/Montevideo', 'Morocco Standard Time' => 'Africa/Casablanca', 'Moscow' => 'Europe/Moscow', 'Mountain Standard Time' => 'America/Denver', 'Mountain Standard Time (Mexico)' => 'America/Chihuahua', 'Myanmar' => 'Asia/Rangoon', 'Myanmar Standard Time' => 'Asia/Rangoon', 'N. Central Asia Standard Time' => 'Asia/Novosibirsk', 'Namibia Standard Time' => 'Africa/Windhoek', 'Nauru' => 'Pacific/Nauru', 'Nepal' => 'Asia/Katmandu', 'Nepal Standard Time' => 'Asia/Katmandu', 'New Zealand Standard Time' => 'Pacific/Auckland', 'New_Caledonia' => 'Pacific/Noumea', 'New_Zealand' => 'Pacific/Auckland', 'Newfoundland' => 'America/St_Johns', 'Newfoundland Standard Time' => 'America/St_Johns', 'Niue' => 'Pacific/Niue', 'Norfolk' => 'Pacific/Norfolk', 'Noronha' => 'America/Noronha', 'North Asia East Standard Time' => 'Asia/Irkutsk', 'North Asia Standard Time' => 'Asia/Krasnoyarsk', 'North_Mariana' => 'Pacific/Saipan', 'Novosibirsk' => 'Asia/Novosibirsk', 'Omsk' => 'Asia/Omsk', 'Oral' => 'Asia/Oral', 'Pacific SA Standard Time' => 'America/Santiago', 'Pacific Standard Time' => 'America/Los_Angeles', 'Pacific Standard Time (Mexico)' => 'America/Tijuana', 'Pakistan' => 'Asia/Karachi', 'Pakistan Standard Time' => 'Asia/Karachi', 'Palau' => 'Pacific/Palau', 'Papua_New_Guinea' => 'Pacific/Port_Moresby', 'Paraguay' => 'America/Asuncion', 'Peru' => 'America/Lima', 'Philippines' => 'Asia/Manila', 'Phoenix_Islands' => 'Pacific/Enderbury', 'Pierre_Miquelon' => 'America/Miquelon', 'Pitcairn' => 'Pacific/Pitcairn', 'Ponape' => 'Pacific/Ponape', 'Qyzylorda' => 'Asia/Qyzylorda', 'Reunion' => 'Indian/Reunion', 'Romance Standard Time' => 'Europe/Paris', 'Rothera' => 'Antarctica/Rothera', 'Russian Standard Time' => 'Europe/Moscow', 'SA Eastern Standard Time' => 'Etc/GMT+3', 'SA Pacific Standard Time' => 'America/Bogota', 'SA Western Standard Time' => 'America/La_Paz', 'SE Asia Standard Time' => 'Asia/Bangkok', 'Sakhalin' => 'Asia/Sakhalin', 'Samara' => 'Europe/Samara', 'Samarkand' => 'Asia/Samarkand', 'Samoa' => 'Pacific/Apia', 'Samoa Standard Time' => 'Pacific/Apia', 'Seychelles' => 'Indian/Mahe', 'Shevchenko' => 'Asia/Aqtau', 'Singapore' => 'Asia/Singapore', 'Singapore Standard Time' => 'Asia/Singapore', 'Solomon' => 'Pacific/Guadalcanal', 'South Africa Standard Time' => 'Africa/Johannesburg', 'South_Georgia' => 'Atlantic/South_Georgia', 'Sri Lanka Standard Time' => 'Asia/Colombo', 'Suriname' => 'America/Paramaribo', 'Sverdlovsk' => 'Asia/Yekaterinburg', 'Syowa' => 'Antarctica/Syowa', 'Tahiti' => 'Pacific/Tahiti', 'Taipei' => 'Asia/Taipei', 'Taipei Standard Time' => 'Asia/Taipei', 'Tajikistan' => 'Asia/Dushanbe', 'Tashkent' => 'Asia/Tashkent', 'Tasmania Standard Time' => 'Australia/Hobart', 'Tbilisi' => 'Asia/Tbilisi', 'Tokelau' => 'Pacific/Fakaofo', 'Tokyo Standard Time' => 'Asia/Tokyo', 'Tonga' => 'Pacific/Tongatapu', 'Tonga Standard Time' => 'Pacific/Tongatapu', 'Truk' => 'Pacific/Truk', 'Turkey' => 'Europe/Istanbul', 'Turkmenistan' => 'Asia/Ashgabat', 'Tuvalu' => 'Pacific/Funafuti', 'US/Eastern' => 'America/New_York', 'US Eastern Standard Time' => 'Etc/GMT+5', 'US Mountain Standard Time' => 'America/Phoenix', 'Uralsk' => 'Asia/Oral', 'Uruguay' => 'America/Montevideo', 'Urumqi' => 'Asia/Urumqi', 'Uzbekistan' => 'Asia/Tashkent', 'Vanuatu' => 'Pacific/Efate', 'Venezuela' => 'America/Caracas', 'Venezuela Standard Time' => 'America/Caracas', 'Vladivostok' => 'Asia/Vladivostok', 'Vladivostok Standard Time' => 'Asia/Vladivostok', 'Volgograd' => 'Europe/Volgograd', 'Vostok' => 'Antarctica/Vostok', 'W. Australia Standard Time' => 'Australia/Perth', 'W. Central Africa Standard Time' => 'Africa/Lagos', 'W. Europe Standard Time' => 'Europe/Berlin', 'Wake' => 'Pacific/Wake', 'Wallis' => 'Pacific/Wallis', 'West Asia Standard Time' => 'Asia/Tashkent', 'West Pacific Standard Time' => 'Pacific/Port_Moresby', 'Yakutsk' => 'Asia/Yakutsk', 'Yakutsk Standard Time' => 'Asia/Yakutsk', 'Yekaterinburg' => 'Asia/Yekaterinburg', 'Yerevan' => 'Asia/Yerevan', 'Yukon' => 'America/Yakutat', ); /** * @var array Map of timezones acceptable by DateTimeZone but not strtotime. */ protected $_invalid_legacy = array( 'US/Eastern' => true, ); /** * @var array|bool List of system identifiers or false if none available. */ protected $_identifiers = false; /** * Initialize local cache and identifiers. * * @param Ai1ec_Registry_Object $registry Registry to use. * * @return void */ public function __construct( Ai1ec_Registry_Object $registry ) { parent::__construct( $registry ); $this->_cache = $this->_registry->get( 'cache.memory' ); $this->_init_identifiers(); } /** * Get default timezone to use in input/output. * * Approach is as follows: * - check user profile for timezone preference; * - if user has no preference - check site for timezone selection; * - if site has no selection - raise notice and use 'UTC'. * * @return string Olson timezone string identifier. */ public function get_default_timezone() { static $default_timezone = null; if ( null === $default_timezone ) { $candidates = array(); $candidates[] = (string)$this->_registry->get( 'model.meta-user' ) ->get_current( 'ai1ec_timezone' ); $candidates[] = (string)$this->_registry->get( 'model.option' ) ->get( 'timezone_string' ); $candidates[] = (string)$this->_registry->get( 'model.option' ) ->get( 'gmt_offset' ); $candidates = array_filter( $candidates, 'strlen' ); foreach ( $candidates as $timezone ) { $timezone = $this->get_name( $timezone ); if ( false !== $timezone ) { $default_timezone = $timezone; break; } } if ( null === $default_timezone ) { $default_timezone = 'UTC'; $this->_registry->get( 'notification.admin' )->store( sprintf( Ai1ec_I18n::__( 'Please select site timezone in %s Timezone dropdown menu.' ), '' . Ai1ec_I18n::__( 'Settings' ) . '' ), 'error' ); } } return $default_timezone; } /** * Attempt to decode GMT offset to some Olson timezone. * * @param float $zone GMT offset. * * @return string Valid Olson timezone name (UTC is last resort). */ public function decode_gmt_timezone( $zone ) { $auto_zone = timezone_name_from_abbr( null, $zone * 3600, true ); if ( false !== $auto_zone ) { return $auto_zone; } $auto_zone = timezone_name_from_abbr( null, ( (int) $zone ) * 3600, true ); if ( false !== $auto_zone ) { return $auto_zone; } $this->_registry->get( 'notification.admin' )->store( sprintf( Ai1ec_I18n::__( 'Timezone "UTC%+d" is not recognized. Please %suse valid%s timezone name, until then events will be created in UTC timezone.' ), $zone, '', '' ), 'error' ); return 'UTC'; } /** * Get valid timezone name from input. * * @param string $zone Name to check/parse. * * @return string Timezone name to use */ public function get_name( $zone ) { if ( is_numeric( $zone ) ) { $decoded_zone = $this->decode_gmt_timezone( $zone ); if ( 'UTC' !== $decoded_zone ) { $message = sprintf( Ai1ec_I18n::__( 'Selected timezone "UTC%+d" will be treated as %s.' ), $zone, $decoded_zone ); $this->_registry->get( 'notification.admin' ) ->store( $message ); } $zone = $decoded_zone; } if ( false === $this->_identifiers ) { return $zone; // anything should do, as zones are not supported } if ( ! isset( $this->_identifiers[$zone] ) ) { $zone = $this->_olson_lookup( $zone ); $valid_legacy = false; try { new DateTimeZone( $zone ); // throw away instantly $valid_legacy = true; } catch ( Exception $excpt ) { $valid_legacy = false; } if ( ! $valid_legacy || isset( $this->_invalid_legacy[$zone] ) ) { return $this->guess_zone( $zone ); } $this->_identifiers[$zone] = $zone; unset( $valid_legacy ); } return $zone; } /** * Quick map look-up to discard zones that have limited recognition. * * @param string $zone Name of timezone to lookup. * * @return string Timezone name to use. Might be the same as $zone. */ protected function _olson_lookup( $zone ) { if ( isset( $this->_zones[$zone] ) ) { return $this->_zones[$zone]; } return $zone; } /** * Check if timezone is set in wp_option * */ public function is_timezone_not_set() { $timezone = $this->_registry->get( 'model.option' ) ->get( 'timezone_string' ); return empty( $timezone ); } /** * Render options for select in settings * * @return array */ public function get_timezones( $only_zones = false ) { $zones = DateTimeZone::listIdentifiers(); if ( empty( $zones ) ) { return array(); } if ( ! $only_zones ) { $manual = __( 'Manual Offset', AI1EC_PLUGIN_NAME ); $options = array(); $options[$manual][] = array( 'text' => __( 'Choose your timezone', AI1EC_PLUGIN_NAME ), 'value' => '', 'args' => array( 'selected' => 'selected' ) ); } foreach ( $zones as $zone ) { $exploded_zone = explode( '/', $zone ); if ( ! isset( $exploded_zone[1] ) && ! $only_zones ) { $exploded_zone[1] = $exploded_zone[0]; $exploded_zone[0] = $manual; } $optgroup = $exploded_zone[0]; unset( $exploded_zone[0] ); $options[$optgroup][] = array( 'text' => implode( '/', $exploded_zone ), 'value' => $zone, ); } return $options; } /** * Guess valid timezone identifier from arbitrary input. * * @param string $meta_name Arbitrary input. * * @return string|bool Parsed timezone name or false if none found. */ public function guess_zone( $meta_name ) { if ( isset( $this->_zones[$meta_name] ) ) { return $this->_zones[$meta_name]; } $name_variants = array( strtr( $meta_name, ' ', '_' ), strtr( $meta_name, '_', ' ' ), ); if ( false !== ( $parenthesis_pos = strpos( $meta_name, '(' ) ) ) { foreach ( $name_variants as $name ) { $name_variants[] = substr( $name, 0, $parenthesis_pos - 1 ); } } foreach ( $name_variants as $name ) { if ( isset( $this->_zones[$name] ) ) { // cache to avoid future lookups and return $this->_zones[$meta_name] = $this->_zones[$name]; return $this->_zones[$name]; } } if ( isset( $meta_name{0} ) && '(' === $meta_name{0} && $closing_pos = strpos( $meta_name, ')' ) ) { $meta_name = trim( substr( $meta_name, $closing_pos + 1 ) ); return $this->guess_zone( $meta_name ); } if ( false === strpos( $meta_name, ' Standard ' ) && false !== ( $time_pos = strpos( $meta_name, ' Time' ) ) ) { $meta_name = substr( $meta_name, 0, $time_pos ) . ' Standard' . substr( $meta_name, $time_pos ); return $this->guess_zone( $meta_name ); } return false; } /** * Get timezone object instance. * * @param string $timezone Name of timezone to get instance for. * * @return DateTimeZone Instance of timezone object. * * @throws Ai1ec_Date_Timezone_Exception If an error occurs. */ public function get( $timezone ) { if ( 'sys.default' === $timezone ) { $timezone = $this->get_default_timezone(); } $name = $this->get_name( $timezone ); if ( ! $name ) { $name = $this->get_name( $this->get_default_timezone() ); } $zone = $this->_cache->get( $name, null ); if ( null === $zone ) { $exception = null; try { $zone = new DateTimeZone( $name ); } catch ( Exception $invalid_tz ) { $exception = $invalid_tz; } if ( null !== $exception ) { throw new Ai1ec_Date_Timezone_Exception( $exception->getMessage() ); } $this->_cache->set( $name, $zone ); } return $zone; } /** * Add system identifiers to object registry. * * @return bool Success */ protected function _init_identifiers() { $identifiers = DateTimeZone::listIdentifiers(); if ( ! $identifiers ) { return false; } $mapped = array(); foreach ( $identifiers as $zone ) { $zone = (string)$zone; $mapped[$zone] = true; $this->_zones[$zone] = $zone; } unset( $identifiers, $zone ); $this->_identifiers = $mapped; return true; } }