1<?php
2/*********************************************************************
3    class.timezone.php
4
5    Database time zone get utils.
6
7    Peter Rotich <peter@osticket.com>
8    Copyright (c)  2006-2013 osTicket
9    http://www.osticket.com
10
11    Released under the GNU General Public License WITHOUT ANY WARRANTY.
12    See LICENSE.TXT for details.
13
14    vim: expandtab sw=4 ts=4 sts=4:
15**********************************************************************/
16
17// This class adopted from jstimezone
18
19class DbTimezone {
20    const HEMISPHERE_SOUTH = 's';
21    const DAY = 86400;
22    const HOUR = 3600;
23    const MINUTE = 60;
24    const SECOND = 1;
25    const BASELINE_YEAR = 2014;
26    const MAX_SCORE = 864000; // 10 days
27
28    static $AMBIGUITIES = array(
29        'America/Denver' =>       array('America/Mazatlan'),
30        'America/Chicago' =>      array('America/Mexico_City'),
31        'America/Santiago' =>     array('America/Asuncion', 'America/Campo_Grande'),
32        'America/Montevideo' =>   array('America/Sao_Paulo'),
33        // Europe/Minsk should not be in this list... but Windows.
34        'Asia/Beirut' =>          array('Asia/Amman', 'Asia/Jerusalem', 'Europe/Helsinki', 'Asia/Damascus', 'Africa/Cairo', 'Asia/Gaza', 'Europe/Minsk'),
35        'Pacific/Auckland' =>     array('Pacific/Fiji'),
36        'America/Los_Angeles' =>  array('America/Santa_Isabel'),
37        'America/New_York' =>     array('America/Havana'),
38        'America/Halifax' =>      array('America/Goose_Bay'),
39        'America/Godthab' =>      array('America/Miquelon'),
40        'Asia/Dubai' =>           array('Asia/Yerevan'),
41        'Asia/Jakarta' =>         array('Asia/Krasnoyarsk'),
42        'Asia/Shanghai' =>        array('Asia/Irkutsk', 'Australia/Perth'),
43        'Australia/Sydney' =>     array('Australia/Lord_Howe'),
44        'Asia/Tokyo' =>           array('Asia/Yakutsk'),
45        'Asia/Dhaka' =>           array('Asia/Omsk'),
46        // In the real world Yerevan is not ambigous for Baku... but Windows.
47        'Asia/Baku' =>            array('Asia/Yerevan'),
48        'Australia/Brisbane' =>   array('Asia/Vladivostok'),
49        'Pacific/Noumea' =>       array('Asia/Vladivostok'),
50        'Pacific/Majuro' =>       array('Asia/Kamchatka', 'Pacific/Fiji'),
51        'Pacific/Tongatapu' =>    array('Pacific/Apia'),
52        'Asia/Baghdad' =>         array('Europe/Minsk', 'Europe/Moscow'),
53        'Asia/Karachi' =>         array('Asia/Yekaterinburg'),
54        'Africa/Johannesburg' =>  array('Asia/Gaza', 'Africa/Cairo')
55    );
56
57    static $olsonTimezones = array(
58        '-720,0' => 'Etc/GMT+12',
59        '-660,0' => 'Pacific/Pago_Pago',
60        '-660,1,s' => 'Pacific/Apia', // Why? Because windows... cry!
61        '-600,1' => 'America/Adak',
62        '-600,0' => 'Pacific/Honolulu',
63        '-570,0' => 'Pacific/Marquesas',
64        '-540,0' => 'Pacific/Gambier',
65        '-540,1' => 'America/Anchorage',
66        '-480,1' => 'America/Los_Angeles',
67        '-480,0' => 'Pacific/Pitcairn',
68        '-420,0' => 'America/Phoenix',
69        '-420,1' => 'America/Denver',
70        '-360,0' => 'America/Guatemala',
71        '-360,1' => 'America/Chicago',
72        '-360,1,s' => 'Pacific/Easter',
73        '-300,0' => 'America/Bogota',
74        '-300,1' => 'America/New_York',
75        '-270,0' => 'America/Caracas',
76        '-240,1' => 'America/Halifax',
77        '-240,0' => 'America/Santo_Domingo',
78        '-240,1,s' => 'America/Santiago',
79        '-210,1' => 'America/St_Johns',
80        '-180,1' => 'America/Godthab',
81        '-180,0' => 'America/Argentina/Buenos_Aires',
82        '-180,1,s' => 'America/Montevideo',
83        '-120,0' => 'America/Noronha',
84        '-120,1' => 'America/Noronha',
85        '-60,1' => 'Atlantic/Azores',
86        '-60,0' => 'Atlantic/Cape_Verde',
87        '0,0' => 'UTC',
88        '0,1' => 'Europe/London',
89        '60,1' => 'Europe/Berlin',
90        '60,0' => 'Africa/Lagos',
91        '60,1,s' => 'Africa/Windhoek',
92        '120,1' => 'Asia/Beirut',
93        '120,0' => 'Africa/Johannesburg',
94        '180,0' => 'Asia/Baghdad',
95        '180,1' => 'Europe/Moscow',
96        '210,1' => 'Asia/Tehran',
97        '240,0' => 'Asia/Dubai',
98        '240,1' => 'Asia/Baku',
99        '270,0' => 'Asia/Kabul',
100        '300,1' => 'Asia/Yekaterinburg',
101        '300,0' => 'Asia/Karachi',
102        '330,0' => 'Asia/Kolkata',
103        '345,0' => 'Asia/Kathmandu',
104        '360,0' => 'Asia/Dhaka',
105        '360,1' => 'Asia/Omsk',
106        '390,0' => 'Asia/Rangoon',
107        '420,1' => 'Asia/Krasnoyarsk',
108        '420,0' => 'Asia/Jakarta',
109        '480,0' => 'Asia/Shanghai',
110        '480,1' => 'Asia/Irkutsk',
111        '525,0' => 'Australia/Eucla',
112        '525,1,s' => 'Australia/Eucla',
113        '540,1' => 'Asia/Yakutsk',
114        '540,0' => 'Asia/Tokyo',
115        '570,0' => 'Australia/Darwin',
116        '570,1,s' => 'Australia/Adelaide',
117        '600,0' => 'Australia/Brisbane',
118        '600,1' => 'Asia/Vladivostok',
119        '600,1,s' => 'Australia/Sydney',
120        '630,1,s' => 'Australia/Lord_Howe',
121        '660,1' => 'Asia/Kamchatka',
122        '660,0' => 'Pacific/Noumea',
123        '690,0' => 'Pacific/Norfolk',
124        '720,1,s' => 'Pacific/Auckland',
125        '720,0' => 'Pacific/Majuro',
126        '765,1,s' => 'Pacific/Chatham',
127        '780,0' => 'Pacific/Tongatapu',
128        '780,1,s' => 'Pacific/Apia',
129        '840,0' => 'Pacific/Kiritimati'
130    );
131
132
133    function get_date_offset($checks) {
134        static $fragment =
135            "time_to_sec(timediff('%s', convert_tz('%s', @@session.time_zone, '+00:00'))) DIV 60";
136
137        if (!is_array($checks))
138            $checks = func_get_args();
139        $dates = array();
140        foreach ($checks as $time) {
141            $date = date('Y-m-d h:i:s', $time);
142            $dates[] = sprintf($fragment, $date, $date);
143        }
144
145        $sql = 'SELECT '.implode(',', $dates);
146        return db_fetch_row(db_query($sql));
147    }
148
149    function lookup_key() {
150        list($january_offset, $june_offset) =
151            $this->get_date_offset(
152                mktime(0, 0, 0, 1, 2, self::BASELINE_YEAR),
153                mktime(0, 0, 0, 6, 2, self::BASELINE_YEAR));
154        $diff = $january_offset - $june_offset;
155
156        if ($diff < 0) {
157            return $january_offset . ",1";
158        } else if ($diff > 0) {
159            return $june_offset . ",1," . self::HEMISPHERE_SOUTH;
160        }
161
162        return $january_offset . ",0";
163    }
164
165    function get_from_database() {
166        // Attempt to fetch timezone direct from the database
167        $TZ = db_timezone();
168
169        // Translate ambiguous 'GMT' timezone
170        if ($TZ === 'GMT') {
171            // PHP assumes GMT == UTC, MySQL assumes GMT == Europe/London.
172            // To shore up the difference, assuming use of MySQL, use the
173            // timezone in PHP which honors BST (British Summer Time)
174            return 'Europe/London';
175        }
176
177        return Format::timezone($TZ);
178    }
179
180    function dst_dates($year) {
181        $yearstart = mktime(0, 0, 1, 1, 1, $year);
182        $yearend = mktime(23, 59, 59, 12, 31, $year);
183        $current = $yearstart;
184        list($date_offset) = $this->get_date_offset($current);
185        $dst_start = null;
186        $dst_end = null;
187
188        $checks = array();
189        while ($current < $yearend - 86400) {
190            $checks[] = $current;
191            $current += 86400;
192        }
193
194        foreach ($this->get_date_offset($checks) as $i=>$offset) {
195            if ($offset !== $date_offset) {
196                if ($offset < $date_offset) {
197                    $dst_start = $checks[$i];
198                }
199                if ($offset > $date_offset) {
200                    $dst_end = $checks[$i];
201                }
202            }
203        }
204        // $offset will remain the last item in ::get_date_offset($checks)
205
206        if ($dst_start && $dst_end) {
207            return array(
208                's' => $this->find_dst_fold($dst_start),
209                'e' => $this->find_dst_fold($dst_end),
210            );
211        }
212
213        return false;
214    }
215
216    function find_dst_fold($a_date, $padding=self::DAY, $iterator=self::HOUR) {
217        $date_start = $a_date - $padding;
218        $date_end = $a_date + $padding;
219        list($date_offset) = $this->get_date_offset($date_start);
220
221        $current = $date_start;
222
223        $dst_change = null;
224        while ($current < $date_end - $iterator) {
225            $checks = array();
226            for ($i=0; $i<12; $i++) {
227                $checks[] = $current;
228                $current += $iterator;
229            }
230
231            foreach ($this->get_date_offset($checks) as $i=>$offset) {
232                if ($offset !== $date_offset) {
233                    $dst_change = $checks[$i];
234                    break;
235                }
236            }
237            if ($dst_change)
238                break;
239        }
240
241        if ($padding === self::DAY) {
242            return $this->find_dst_fold($dst_change, self::HOUR, self::MINUTE);
243        }
244
245        if ($padding === self::HOUR) {
246            return $this->find_dst_fold($dst_change, self::MINUTE, self::SECOND);
247        }
248
249        return $dst_change;
250    }
251
252    function windows7_adaptations($rule_list, $preliminary_timezone, $score, $sample) {
253        if ($score !== 'N/A') {
254            return $score;
255        }
256        if ($preliminary_timezone === 'Asia/Beirut') {
257            if ($sample['name'] === 'Africa/Cairo') {
258                if ($rule_list[6]['s'] === 1398376800 && $rule_list[6]['e'] === 1411678800) {
259                    return 0;
260                }
261            }
262            if ($sample['name'] === 'Asia/Jerusalem') {
263                if ($rule_list[6]['s'] === 1395964800 && $rule_list[6]['e'] === 1411858800) {
264                    return 0;
265                }
266            }
267        } else if ($preliminary_timezone === 'America/Santiago') {
268            if ($sample['name'] === 'America/Asuncion') {
269                if ($rule_list[6]['s'] === 1412481600 && $rule_list[6]['e'] === 1397358000) {
270                    return 0;
271                }
272            }
273            if ($sample['name'] === 'America/Campo_Grande') {
274                if ($rule_list[6]['s'] === 1413691200 && $rule_list[6]['e'] === 1392519600) {
275                    return 0;
276                }
277            }
278        } else if ($preliminary_timezone === 'America/Montevideo') {
279            if ($sample['name'] === 'America/Sao_Paulo') {
280                if ($rule_list[6]['s'] === 1413687600 && $rule_list[6]['e'] === 1392516000) {
281                    return 0;
282                }
283            }
284        } else if ($preliminary_timezone === 'Pacific/Auckland') {
285            if ($sample['name'] === 'Pacific/Fiji') {
286                if ($rule_list[6]['s'] === 1414245600 && $rule_list[6]['e'] === 1396101600) {
287                    return 0;
288                }
289            }
290        }
291
292        return $score;
293    }
294
295    function best_dst_match($rule_list, $preliminary_timezone) {
296        $self = $this;
297        $score_sample = function ($sample) use ($rule_list, $self, $preliminary_timezone) {
298            $score = 0;
299
300            for ($j = 0; $j < count($rule_list); $j++) {
301
302                // Both sample and current time zone report DST during the year.
303                if (!!$sample['rules'][$j] && !!$rule_list[$j]) {
304
305                    // The current time zone's DST rules are inside the sample's. Include.
306                    if ($rule_list[$j]['s'] >= $sample['rules'][$j]['s'] && $rule_list[$j]['e'] <= $sample['rules'][$j]['e']) {
307                        $score = 0;
308                        $score += abs($rule_list[$j]['s'] - $sample['rules'][$j]['s']);
309                        $score += abs($sample['rules'][$j]['e'] - $rule_list[$j]['e']);
310
311                    // The current time zone's DST rules are outside the sample's. Discard.
312                    } else {
313                        $score = 'N/A';
314                        break;
315                    }
316
317                    // The max score has been reached. Discard.
318                    if ($score > self::MAX_SCORE) {
319                        $score = 'N/A';
320                        break;
321                    }
322                }
323            }
324
325            $score = $self->windows7_adaptations($rule_list, $preliminary_timezone, $score, $sample);
326
327            return $score;
328        };
329        $scoreboard = array();
330        $dst_zones = self::$dst_rules['zones'];
331        $ambiguities = @self::$AMBIGUITIES[$preliminary_timezone];
332
333        foreach ($dst_zones as $sample) {
334            $score = $score_sample($sample);
335
336            if ($score !== 'N/A') {
337                $scoreboard[$sample['name']] = $score;
338            }
339        }
340
341        foreach ($scoreboard as $tz) {
342            if (in_array($tz, $ambiguities)) {
343                return $tz;
344            }
345        }
346
347        return $preliminary_timezone;
348    }
349
350    function get_by_dst($preliminary_timezone) {
351        $rules = array();
352        foreach (self::$dst_rules['years'] as $Y) {
353            $rules[] = $this->dst_dates($Y);
354        }
355        $has_dst = false;
356        foreach ($rules as $R) {
357            if ($R !== false) {
358                $has_dst = true; break;
359            }
360        }
361
362        if ($has_dst) {
363            return $this->best_dst_match($rules, $preliminary_timezone);
364        }
365
366        return $preliminary_timezone;
367    }
368
369    static function determine() {
370        $self = new static();
371        $preliminary_tz = $self->get_from_database();
372
373        if (!$preliminary_tz) {
374            $preliminary_tz = self::$olsonTimezones[$self->lookup_key()];
375
376            if (isset(self::$AMBIGUITIES[$preliminary_tz])) {
377                $preliminary_tz = $self->get_by_dst($preliminary_tz);
378            }
379        }
380
381        return $preliminary_tz;
382    }
383
384    // Rules compiled from jstz rules.js file by
385    // str_replace('000,', ',', var_export(json_decode('...', true)));
386    static $dst_rules = array('years'=>array(0=>2008,1=>2009,2=>2010,3=>2011,4=>2012,5=>2013,6=>2014,),'zones'=>array(0=>array('name'=>'Africa/Cairo','rules'=>array(0=>array('e'=>1219957200,'s'=>1209074400,),1=>array('e'=>1250802000,'s'=>1240524000,),2=>array('e'=>1285880400,'s'=>1284069600,),3=>false,4=>false,5=>false,6=>array('e'=>1411678800,'s'=>1406844000,),),),1=>array('name'=>'America/Asuncion','rules'=>array(0=>array('e'=>1205031600,'s'=>1224388800,),1=>array('e'=>1236481200,'s'=>1255838400,),2=>array('e'=>1270954800,'s'=>1286078400,),3=>array('e'=>1302404400,'s'=>1317528000,),4=>array('e'=>1333854000,'s'=>1349582400,),5=>array('e'=>1364094000,'s'=>1381032000,),6=>array('e'=>1395543600,'s'=>1412481600,),),),2=>array('name'=>'America/Campo_Grande','rules'=>array(0=>array('e'=>1203217200,'s'=>1224388800,),1=>array('e'=>1234666800,'s'=>1255838400,),2=>array('e'=>1266721200,'s'=>1287288000,),3=>array('e'=>1298170800,'s'=>1318737600,),4=>array('e'=>1330225200,'s'=>1350792000,),5=>array('e'=>1361070000,'s'=>1382241600,),6=>array('e'=>1392519600,'s'=>1413691200,),),),3=>array('name'=>'America/Goose_Bay','rules'=>array(0=>array('e'=>1225594860,'s'=>1205035260,),1=>array('e'=>1257044460,'s'=>1236484860,),2=>array('e'=>1289098860,'s'=>1268539260,),3=>array('e'=>1320555600,'s'=>1299988860,),4=>array('e'=>1352005200,'s'=>1331445600,),5=>array('e'=>1383454800,'s'=>1362895200,),6=>array('e'=>1414904400,'s'=>1394344800,),),),4=>array('name'=>'America/Havana','rules'=>array(0=>array('e'=>1224997200,'s'=>1205643600,),1=>array('e'=>1256446800,'s'=>1236488400,),2=>array('e'=>1288501200,'s'=>1268542800,),3=>array('e'=>1321160400,'s'=>1300597200,),4=>array('e'=>1352005200,'s'=>1333256400,),5=>array('e'=>1383454800,'s'=>1362891600,),6=>array('e'=>1414904400,'s'=>1394341200,),),),5=>array('name'=>'America/Mazatlan','rules'=>array(0=>array('e'=>1225008000,'s'=>1207472400,),1=>array('e'=>1256457600,'s'=>1238922000,),2=>array('e'=>1288512000,'s'=>1270371600,),3=>array('e'=>1319961600,'s'=>1301821200,),4=>array('e'=>1351411200,'s'=>1333270800,),5=>array('e'=>1382860800,'s'=>1365325200,),6=>array('e'=>1414310400,'s'=>1396774800,),),),6=>array('name'=>'America/Mexico_City','rules'=>array(0=>array('e'=>1225004400,'s'=>1207468800,),1=>array('e'=>1256454000,'s'=>1238918400,),2=>array('e'=>1288508400,'s'=>1270368000,),3=>array('e'=>1319958000,'s'=>1301817600,),4=>array('e'=>1351407600,'s'=>1333267200,),5=>array('e'=>1382857200,'s'=>1365321600,),6=>array('e'=>1414306800,'s'=>1396771200,),),),7=>array('name'=>'America/Miquelon','rules'=>array(0=>array('e'=>1225598400,'s'=>1205038800,),1=>array('e'=>1257048000,'s'=>1236488400,),2=>array('e'=>1289102400,'s'=>1268542800,),3=>array('e'=>1320552000,'s'=>1299992400,),4=>array('e'=>1352001600,'s'=>1331442000,),5=>array('e'=>1383451200,'s'=>1362891600,),6=>array('e'=>1414900800,'s'=>1394341200,),),),8=>array('name'=>'America/Santa_Isabel','rules'=>array(0=>array('e'=>1225011600,'s'=>1207476000,),1=>array('e'=>1256461200,'s'=>1238925600,),2=>array('e'=>1288515600,'s'=>1270375200,),3=>array('e'=>1319965200,'s'=>1301824800,),4=>array('e'=>1351414800,'s'=>1333274400,),5=>array('e'=>1382864400,'s'=>1365328800,),6=>array('e'=>1414314000,'s'=>1396778400,),),),9=>array('name'=>'America/Sao_Paulo','rules'=>array(0=>array('e'=>1203213600,'s'=>1224385200,),1=>array('e'=>1234663200,'s'=>1255834800,),2=>array('e'=>1266717600,'s'=>1287284400,),3=>array('e'=>1298167200,'s'=>1318734000,),4=>array('e'=>1330221600,'s'=>1350788400,),5=>array('e'=>1361066400,'s'=>1382238000,),6=>array('e'=>1392516000,'s'=>1413687600,),),),10=>array('name'=>'Asia/Amman','rules'=>array(0=>array('e'=>1225404000,'s'=>1206655200,),1=>array('e'=>1256853600,'s'=>1238104800,),2=>array('e'=>1288303200,'s'=>1269554400,),3=>array('e'=>1319752800,'s'=>1301608800,),4=>false,5=>false,6=>array('e'=>1414706400,'s'=>1395957600,),),),11=>array('name'=>'Asia/Damascus','rules'=>array(0=>array('e'=>1225486800,'s'=>1207260000,),1=>array('e'=>1256850000,'s'=>1238104800,),2=>array('e'=>1288299600,'s'=>1270159200,),3=>array('e'=>1319749200,'s'=>1301608800,),4=>array('e'=>1351198800,'s'=>1333058400,),5=>array('e'=>1382648400,'s'=>1364508000,),6=>array('e'=>1414702800,'s'=>1395957600,),),),12=>array('name'=>'Asia/Dubai','rules'=>array(0=>false,1=>false,2=>false,3=>false,4=>false,5=>false,6=>false,),),13=>array('name'=>'Asia/Gaza','rules'=>array(0=>array('e'=>1219957200,'s'=>1206655200,),1=>array('e'=>1252015200,'s'=>1238104800,),2=>array('e'=>1281474000,'s'=>1269640860,),3=>array('e'=>1312146000,'s'=>1301608860,),4=>array('e'=>1348178400,'s'=>1333058400,),5=>array('e'=>1380229200,'s'=>1364508000,),6=>array('e'=>1411678800,'s'=>1395957600,),),),14=>array('name'=>'Asia/Irkutsk','rules'=>array(0=>array('e'=>1224957600,'s'=>1206813600,),1=>array('e'=>1256407200,'s'=>1238263200,),2=>array('e'=>1288461600,'s'=>1269712800,),3=>false,4=>false,5=>false,6=>false,),),15=>array('name'=>'Asia/Jerusalem','rules'=>array(0=>array('e'=>1223161200,'s'=>1206662400,),1=>array('e'=>1254006000,'s'=>1238112000,),2=>array('e'=>1284246000,'s'=>1269561600,),3=>array('e'=>1317510000,'s'=>1301616000,),4=>array('e'=>1348354800,'s'=>1333065600,),5=>array('e'=>1382828400,'s'=>1364515200,),6=>array('e'=>1414278000,'s'=>1395964800,),),),16=>array('name'=>'Asia/Kamchatka','rules'=>array(0=>array('e'=>1224943200,'s'=>1206799200,),1=>array('e'=>1256392800,'s'=>1238248800,),2=>array('e'=>1288450800,'s'=>1269698400,),3=>false,4=>false,5=>false,6=>false,),),17=>array('name'=>'Asia/Krasnoyarsk','rules'=>array(0=>array('e'=>1224961200,'s'=>1206817200,),1=>array('e'=>1256410800,'s'=>1238266800,),2=>array('e'=>1288465200,'s'=>1269716400,),3=>false,4=>false,5=>false,6=>false,),),18=>array('name'=>'Asia/Omsk','rules'=>array(0=>array('e'=>1224964800,'s'=>1206820800,),1=>array('e'=>1256414400,'s'=>1238270400,),2=>array('e'=>1288468800,'s'=>1269720000,),3=>false,4=>false,5=>false,6=>false,),),19=>array('name'=>'Asia/Vladivostok','rules'=>array(0=>array('e'=>1224950400,'s'=>1206806400,),1=>array('e'=>1256400000,'s'=>1238256000,),2=>array('e'=>1288454400,'s'=>1269705600,),3=>false,4=>false,5=>false,6=>false,),),20=>array('name'=>'Asia/Yakutsk','rules'=>array(0=>array('e'=>1224954000,'s'=>1206810000,),1=>array('e'=>1256403600,'s'=>1238259600,),2=>array('e'=>1288458000,'s'=>1269709200,),3=>false,4=>false,5=>false,6=>false,),),21=>array('name'=>'Asia/Yekaterinburg','rules'=>array(0=>array('e'=>1224968400,'s'=>1206824400,),1=>array('e'=>1256418000,'s'=>1238274000,),2=>array('e'=>1288472400,'s'=>1269723600,),3=>false,4=>false,5=>false,6=>false,),),22=>array('name'=>'Asia/Yerevan','rules'=>array(0=>array('e'=>1224972000,'s'=>1206828000,),1=>array('e'=>1256421600,'s'=>1238277600,),2=>array('e'=>1288476000,'s'=>1269727200,),3=>array('e'=>1319925600,'s'=>1301176800,),4=>false,5=>false,6=>false,),),23=>array('name'=>'Australia/Lord_Howe','rules'=>array(0=>array('e'=>1207407600,'s'=>1223134200,),1=>array('e'=>1238857200,'s'=>1254583800,),2=>array('e'=>1270306800,'s'=>1286033400,),3=>array('e'=>1301756400,'s'=>1317483000,),4=>array('e'=>1333206000,'s'=>1349537400,),5=>array('e'=>1365260400,'s'=>1380987000,),6=>array('e'=>1396710000,'s'=>1412436600,),),),24=>array('name'=>'Australia/Perth','rules'=>array(0=>array('e'=>1206813600,'s'=>1224957600,),1=>false,2=>false,3=>false,4=>false,5=>false,6=>false,),),25=>array('name'=>'Europe/Helsinki','rules'=>array(0=>array('e'=>1224982800,'s'=>1206838800,),1=>array('e'=>1256432400,'s'=>1238288400,),2=>array('e'=>1288486800,'s'=>1269738000,),3=>array('e'=>1319936400,'s'=>1301187600,),4=>array('e'=>1351386000,'s'=>1332637200,),5=>array('e'=>1382835600,'s'=>1364691600,),6=>array('e'=>1414285200,'s'=>1396141200,),),),26=>array('name'=>'Europe/Minsk','rules'=>array(0=>array('e'=>1224979200,'s'=>1206835200,),1=>array('e'=>1256428800,'s'=>1238284800,),2=>array('e'=>1288483200,'s'=>1269734400,),3=>false,4=>false,5=>false,6=>false,),),27=>array('name'=>'Europe/Moscow','rules'=>array(0=>array('e'=>1224975600,'s'=>1206831600,),1=>array('e'=>1256425200,'s'=>1238281200,),2=>array('e'=>1288479600,'s'=>1269730800,),3=>false,4=>false,5=>false,6=>false,),),28=>array('name'=>'Pacific/Apia','rules'=>array(0=>false,1=>false,2=>false,3=>array('e'=>1301752800,'s'=>1316872800,),4=>array('e'=>1333202400,'s'=>1348927200,),5=>array('e'=>1365256800,'s'=>1380376800,),6=>array('e'=>1396706400,'s'=>1411826400,),),),29=>array('name'=>'Pacific/Fiji','rules'=>array(0=>false,1=>false,2=>array('e'=>1269698400,'s'=>1287842400,),3=>array('e'=>1327154400,'s'=>1319292000,),4=>array('e'=>1358604000,'s'=>1350741600,),5=>array('e'=>1390050000,'s'=>1382796000,),6=>array('e'=>1421503200,'s'=>1414850400,),),),),);
387}
388
389?>
390