xref: /plugin/davcal/vendor/sabre/vobject/lib/TimeZoneUtil.php (revision a1a3b6794e0e143a4a8b51d3185ce2d339be61ab)
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