1<?php 2 3namespace Sabre\VObject; 4 5/** 6 * Time zone name translation 7 * 8 * This file translates well-known time zone names into "Olson database" time zone names. 9 * 10 * @copyright Copyright (C) fruux GmbH (https://fruux.com/) 11 * @author Frank Edelhaeuser (fedel@users.sourceforge.net) 12 * @author Evert Pot (http://evertpot.com/) 13 * @license http://sabre.io/license/ Modified BSD License 14 */ 15class TimeZoneUtil { 16 17 public static $map = null; 18 19 /** 20 * List of microsoft exchange timezone ids. 21 * 22 * Source: http://msdn.microsoft.com/en-us/library/aa563018(loband).aspx 23 */ 24 public static $microsoftExchangeMap = array( 25 0 => 'UTC', 26 31 => 'Africa/Casablanca', 27 28 // Insanely, id #2 is used for both Europe/Lisbon, and Europe/Sarajevo. 29 // I'm not even kidding.. We handle this special case in the 30 // getTimeZone method. 31 2 => 'Europe/Lisbon', 32 1 => 'Europe/London', 33 4 => 'Europe/Berlin', 34 6 => 'Europe/Prague', 35 3 => 'Europe/Paris', 36 69 => 'Africa/Luanda', // This was a best guess 37 7 => 'Europe/Athens', 38 5 => 'Europe/Bucharest', 39 49 => 'Africa/Cairo', 40 50 => 'Africa/Harare', 41 59 => 'Europe/Helsinki', 42 27 => 'Asia/Jerusalem', 43 26 => 'Asia/Baghdad', 44 74 => 'Asia/Kuwait', 45 51 => 'Europe/Moscow', 46 56 => 'Africa/Nairobi', 47 25 => 'Asia/Tehran', 48 24 => 'Asia/Muscat', // Best guess 49 54 => 'Asia/Baku', 50 48 => 'Asia/Kabul', 51 58 => 'Asia/Yekaterinburg', 52 47 => 'Asia/Karachi', 53 23 => 'Asia/Calcutta', 54 62 => 'Asia/Kathmandu', 55 46 => 'Asia/Almaty', 56 71 => 'Asia/Dhaka', 57 66 => 'Asia/Colombo', 58 61 => 'Asia/Rangoon', 59 22 => 'Asia/Bangkok', 60 64 => 'Asia/Krasnoyarsk', 61 45 => 'Asia/Shanghai', 62 63 => 'Asia/Irkutsk', 63 21 => 'Asia/Singapore', 64 73 => 'Australia/Perth', 65 75 => 'Asia/Taipei', 66 20 => 'Asia/Tokyo', 67 72 => 'Asia/Seoul', 68 70 => 'Asia/Yakutsk', 69 19 => 'Australia/Adelaide', 70 44 => 'Australia/Darwin', 71 18 => 'Australia/Brisbane', 72 76 => 'Australia/Sydney', 73 43 => 'Pacific/Guam', 74 42 => 'Australia/Hobart', 75 68 => 'Asia/Vladivostok', 76 41 => 'Asia/Magadan', 77 17 => 'Pacific/Auckland', 78 40 => 'Pacific/Fiji', 79 67 => 'Pacific/Tongatapu', 80 29 => 'Atlantic/Azores', 81 53 => 'Atlantic/Cape_Verde', 82 30 => 'America/Noronha', 83 8 => 'America/Sao_Paulo', // Best guess 84 32 => 'America/Argentina/Buenos_Aires', 85 60 => 'America/Godthab', 86 28 => 'America/St_Johns', 87 9 => 'America/Halifax', 88 33 => 'America/Caracas', 89 65 => 'America/Santiago', 90 35 => 'America/Bogota', 91 10 => 'America/New_York', 92 34 => 'America/Indiana/Indianapolis', 93 55 => 'America/Guatemala', 94 11 => 'America/Chicago', 95 37 => 'America/Mexico_City', 96 36 => 'America/Edmonton', 97 38 => 'America/Phoenix', 98 12 => 'America/Denver', // Best guess 99 13 => 'America/Los_Angeles', // Best guess 100 14 => 'America/Anchorage', 101 15 => 'Pacific/Honolulu', 102 16 => 'Pacific/Midway', 103 39 => 'Pacific/Kwajalein', 104 ); 105 106 /** 107 * This method will try to find out the correct timezone for an iCalendar 108 * date-time value. 109 * 110 * You must pass the contents of the TZID parameter, as well as the full 111 * calendar. 112 * 113 * If the lookup fails, this method will return the default PHP timezone 114 * (as configured using date_default_timezone_set, or the date.timezone ini 115 * setting). 116 * 117 * Alternatively, if $failIfUncertain is set to true, it will throw an 118 * exception if we cannot accurately determine the timezone. 119 * 120 * @param string $tzid 121 * @param Sabre\VObject\Component $vcalendar 122 * @return DateTimeZone 123 */ 124 static public function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) { 125 126 // First we will just see if the tzid is a support timezone identifier. 127 // 128 // The only exception is if the timezone starts with (. This is to 129 // handle cases where certain microsoft products generate timezone 130 // identifiers that for instance look like: 131 // 132 // (GMT+01.00) Sarajevo/Warsaw/Zagreb 133 // 134 // Since PHP 5.5.10, the first bit will be used as the timezone and 135 // this method will return just GMT+01:00. This is wrong, because it 136 // doesn't take DST into account. 137 if ($tzid[0]!=='(') { 138 139 // PHP has a bug that logs PHP warnings even it shouldn't: 140 // https://bugs.php.net/bug.php?id=67881 141 // 142 // That's why we're checking if we'll be able to successfull instantiate 143 // \DateTimeZone() before doing so. Otherwise we could simply instantiate 144 // and catch the exception. 145 $tzIdentifiers = \DateTimeZone::listIdentifiers(); 146 147 try { 148 if ( 149 (in_array($tzid, $tzIdentifiers)) || 150 (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) || 151 (in_array($tzid, self::getIdentifiersBC())) 152 ) { 153 return new \DateTimeZone($tzid); 154 } 155 } catch(\Exception $e) { 156 } 157 158 } 159 160 self::loadTzMaps(); 161 162 // Next, we check if the tzid is somewhere in our tzid map. 163 if (isset(self::$map[$tzid])) { 164 return new \DateTimeZone(self::$map[$tzid]); 165 } 166 167 // Maybe the author was hyper-lazy and just included an offset. We 168 // support it, but we aren't happy about it. 169 if (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) { 170 171 // Note that the path in the source will never be taken from PHP 5.5.10 172 // onwards. PHP 5.5.10 supports the "GMT+0100" style of format, so it 173 // already gets returned early in this function. Once we drop support 174 // for versions under PHP 5.5.10, this bit can be taken out of the 175 // source. 176 // @codeCoverageIgnoreStart 177 return new \DateTimeZone('Etc/GMT' . $matches[1] . ltrim(substr($matches[2],0,2),'0')); 178 // @codeCoverageIgnoreEnd 179 } 180 181 if ($vcalendar) { 182 183 // If that didn't work, we will scan VTIMEZONE objects 184 foreach($vcalendar->select('VTIMEZONE') as $vtimezone) { 185 186 if ((string)$vtimezone->TZID === $tzid) { 187 188 // Some clients add 'X-LIC-LOCATION' with the olson name. 189 if (isset($vtimezone->{'X-LIC-LOCATION'})) { 190 191 $lic = (string)$vtimezone->{'X-LIC-LOCATION'}; 192 193 // Libical generators may specify strings like 194 // "SystemV/EST5EDT". For those we must remove the 195 // SystemV part. 196 if (substr($lic,0,8)==='SystemV/') { 197 $lic = substr($lic,8); 198 } 199 200 return self::getTimeZone($lic, null, $failIfUncertain); 201 202 } 203 // Microsoft may add a magic number, which we also have an 204 // answer for. 205 if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) { 206 $cdoId = (int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue(); 207 208 // 2 can mean both Europe/Lisbon and Europe/Sarajevo. 209 if ($cdoId===2 && strpos((string)$vtimezone->TZID, 'Sarajevo')!==false) { 210 return new \DateTimeZone('Europe/Sarajevo'); 211 } 212 213 if (isset(self::$microsoftExchangeMap[$cdoId])) { 214 return new \DateTimeZone(self::$microsoftExchangeMap[$cdoId]); 215 } 216 } 217 218 } 219 220 } 221 222 } 223 224 if ($failIfUncertain) { 225 throw new \InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: ' . $tzid); 226 } 227 228 // If we got all the way here, we default to UTC. 229 return new \DateTimeZone(date_default_timezone_get()); 230 231 } 232 233 /** 234 * This method will load in all the tz mapping information, if it's not yet 235 * done. 236 */ 237 static public function loadTzMaps() { 238 239 if (!is_null(self::$map)) return; 240 241 self::$map = array_merge( 242 include __DIR__ . '/timezonedata/windowszones.php', 243 include __DIR__ . '/timezonedata/lotuszones.php', 244 include __DIR__ . '/timezonedata/exchangezones.php', 245 include __DIR__ . '/timezonedata/php-workaround.php' 246 ); 247 248 } 249 250 /** 251 * This method returns an array of timezone identifiers, that are supported 252 * by DateTimeZone(), but not returned by DateTimeZone::listIdentifiers() 253 * 254 * We're not using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) because: 255 * - It's not supported by some PHP versions as well as HHVM. 256 * - It also returns identifiers, that are invalid values for new DateTimeZone() on some PHP versions. 257 * (See timezonedata/php-bc.php and timezonedata php-workaround.php) 258 * 259 * @return array 260 */ 261 static public function getIdentifiersBC() { 262 return include __DIR__ . '/timezonedata/php-bc.php'; 263 } 264 265} 266