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