Issues (1065)

Sources/Subs-Timezones.php (1 issue)

1
<?php
2
3
/**
4
 * This file provides some functions to simplify working with time zones.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines https://www.simplemachines.org
10
 * @copyright 2025 Simple Machines and individual contributors
11
 * @license https://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1.5
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
if (!defined('PHP_INT_MIN'))
20
	define('PHP_INT_MIN', ~PHP_INT_MAX);
21
22
/**
23
 * Returns an array that instructs SMF how to map specific time zones
24
 * (e.g. "America/Denver") onto the user-friendly "meta-zone" labels that
25
 * most people think of as time zones (e.g. "Mountain Time").
26
 *
27
 * @param string $when The date/time used to determine fallback values.
28
 *		May be a Unix timestamp or any string that strtotime() can understand.
29
 *		Defaults to 'now'.
30
 * @return array An array relating time zones to "meta-zones"
31
 */
32
function get_tzid_metazones($when = 'now')
33
{
34
	global $txt, $tztxt;
35
36
	// This should already have been loaded, but just in case...
37
	loadLanguage('Timezones');
38
39
	/*
40
		This array lists a series of representative time zones and their
41
		corresponding "meta-zone" labels.
42
43
		The term "representative" here means that a given time zone can
44
		represent others that use exactly the same rules for DST
45
		transitions, UTC offsets, and abbreviations. For example,
46
		Europe/Berlin can be representative for Europe/Rome,
47
		Europe/Paris, etc., because these cities all use exactly the
48
		same time zone rules and values.
49
50
		Meta-zone labels are the user friendly strings shown to the end
51
		user, e.g. "Mountain Standard Time". The values of this array
52
		are keys of strings defined in Timezones.{language}.php, which
53
		in turn are sprintf format strings used to generate the final
54
		label text.
55
56
		Sometimes several representative time zones will map onto the
57
		same meta-zone label. This usually happens when there are
58
		different rules for Daylight Saving time in locations that are
59
		otherwise the same. For example, both America/Denver and
60
		America/Phoenix map to North_America_Mountain, but the ultimate
61
		output will be 'Mountain Time (MST/MDT)' for America/Denver vs.
62
		'Mountain Standard Time (MST)' for America/Phoenix.
63
64
		If you are adding a new meta-zone to this list because the TZDB
65
		added a new time zone that doesn't fit any existing meta-zone,
66
		please also add a fallback in the get_tzid_fallbacks() function.
67
		This helps support SMF installs on servers using outdated
68
		versions of the TZDB.
69
	 */
70
	$tzid_metazones = array(
71
		// No DST
72
		'Africa/Abidjan' => 'GMT',
73
74
		// No DST
75
		'Africa/Algiers' => 'Europe_Central',
76
77
		// Uses DST
78
		'Africa/Casablanca' => 'Africa_Morocco',
79
80
		// No DST
81
		'Africa/Johannesburg' => 'Africa_South',
82
83
		// No DST
84
		'Africa/Lagos' => 'Africa_West',
85
86
		// No DST
87
		'Africa/Maputo' => 'Africa_Central',
88
89
		// No DST
90
		'Africa/Nairobi' => 'Africa_East',
91
92
		// Uses DST
93
		'America/Adak' => 'North_America_Hawaii_Aleutian',
94
95
		// Uses DST
96
		'America/Anchorage' => 'North_America_Alaska',
97
98
		// No DST
99
		'America/Argentina/Buenos_Aires' => 'South_America_Argentina',
100
101
		// Uses DST
102
		'America/Asuncion' => 'South_America_Paraguay',
103
104
		// No DST
105
		'America/Belize' => 'North_America_Central',
106
107
		// No DST
108
		'America/Bogota' => 'South_America_Colombia',
109
110
		// No DST
111
		'America/Caracas' => 'South_America_Venezuela',
112
113
		// No DST
114
		'America/Cayenne' => 'South_America_French_Guiana',
115
116
		// Uses DST
117
		'America/Chicago' => 'North_America_Central',
118
119
		// Uses DST
120
		'America/Chihuahua' => 'North_America_Mexico_Pacific',
121
122
		// Uses DST
123
		'America/Denver' => 'North_America_Mountain',
124
125
		// Uses DST
126
		'America/Nuuk' => 'North_America_Greenland_Western',
127
128
		// No DST
129
		'America/Guayaquil' => 'South_America_Ecuador',
130
131
		// No DST
132
		'America/Guyana' => 'South_America_Guyana',
133
134
		// Uses DST
135
		'America/Halifax' => 'North_America_Atlantic',
136
137
		// Uses DST
138
		'America/Havana' => 'North_America_Cuba',
139
140
		// No DST
141
		'America/Jamaica' => 'North_America_Eastern',
142
143
		// No DST
144
		'America/La_Paz' => 'South_America_Bolivia',
145
146
		// No DST
147
		'America/Lima' => 'South_America_Peru',
148
149
		// Uses DST
150
		'America/Los_Angeles' => 'North_America_Pacific',
151
152
		// No DST
153
		'America/Manaus' => 'South_America_Amazon',
154
155
		// Uses DST
156
		'America/Mexico_City' => 'North_America_Mexico_Central',
157
158
		// Uses DST
159
		'America/Miquelon' => 'North_America_St_Pierre_Miquelon',
160
161
		// No DST
162
		'America/Montevideo' => 'South_America_Uruguay',
163
164
		// Uses DST
165
		'America/New_York' => 'North_America_Eastern',
166
167
		// No DST
168
		'America/Noronha' => 'South_America_Noronha',
169
170
		// No DST
171
		'America/Paramaribo' => 'South_America_Suriname',
172
173
		// No DST
174
		'America/Phoenix' => 'North_America_Mountain',
175
176
		// No DST
177
		'America/Port_of_Spain' => 'North_America_Atlantic',
178
179
		// No DST
180
		'America/Punta_Arenas' => 'South_America_Chile_Magallanes',
181
182
		// No DST
183
		'America/Rio_Branco' => 'South_America_Acre',
184
185
		// Uses DST
186
		'America/Santiago' => 'South_America_Chile',
187
188
		// No DST
189
		'America/Sao_Paulo' => 'South_America_Brasilia',
190
191
		// Uses DST
192
		'America/Scoresbysund' => 'North_America_Greenland_Eastern',
193
194
		// Uses DST
195
		'America/St_Johns' => 'North_America_Newfoundland',
196
197
		// No DST
198
		'Antarctica/Casey' => 'Antarctica_Casey',
199
200
		// No DST
201
		'Antarctica/Davis' => 'Antarctica_Davis',
202
203
		// No DST
204
		'Antarctica/DumontDUrville' => 'Antarctica_DumontDUrville',
205
206
		// No DST
207
		'Antarctica/Macquarie' => 'Antarctica_Macquarie',
208
209
		// No DST
210
		'Antarctica/Mawson' => 'Antarctica_Mawson',
211
212
		// Uses DST
213
		'Antarctica/McMurdo' => 'Antarctica_McMurdo',
214
215
		// No DST
216
		'Antarctica/Palmer' => 'Antarctica_Palmer',
217
218
		// No DST
219
		'Antarctica/Rothera' => 'Antarctica_Rothera',
220
221
		// No DST
222
		'Antarctica/Syowa' => 'Antarctica_Syowa',
223
224
		// Uses DST
225
		'Antarctica/Troll' => 'Antarctica_Troll',
226
227
		// No DST
228
		'Antarctica/Vostok' => 'Antarctica_Vostok',
229
230
		// No DST
231
		'Asia/Almaty' => 'Asia_Kazakhstan_Eastern',
232
233
		// Uses DST
234
		'Asia/Amman' => 'Asia_Jordan',
235
236
		// No DST
237
		'Asia/Aqtau' => 'Asia_Kazakhstan_Western',
238
239
		// No DST
240
		'Asia/Ashgabat' => 'Asia_Turkmenistan',
241
242
		// No DST
243
		'Asia/Baku' => 'Asia_Azerbaijan',
244
245
		// No DST
246
		'Asia/Bangkok' => 'Asia_Southeast',
247
248
		// Uses DST
249
		'Asia/Beirut' => 'Asia_Libya',
250
251
		// No DST
252
		'Asia/Bishkek' => 'Asia_Kyrgystan',
253
254
		// No DST
255
		'Asia/Brunei' => 'Asia_Brunei',
256
257
		// Uses DST
258
		'Asia/Damascus' => 'Asia_Damascus',
259
260
		// No DST
261
		'Asia/Dhaka' => 'Asia_Bangladesh',
262
263
		// No DST
264
		'Asia/Dili' => 'Asia_East_Timor',
265
266
		// No DST
267
		'Asia/Dubai' => 'Asia_Gulf',
268
269
		// No DST
270
		'Asia/Dushanbe' => 'Asia_Tajikistan',
271
272
		// Uses DST
273
		'Asia/Gaza' => 'Asia_Palestine',
274
275
		// No DST
276
		'Asia/Hong_Kong' => 'Asia_Hong_Kong',
277
278
		// No DST
279
		'Asia/Hovd' => 'Asia_Mongolia_Western',
280
281
		// No DST
282
		'Asia/Irkutsk' => 'Asia_Irkutsk',
283
284
		// No DST
285
		'Asia/Jakarta' => 'Asia_Indonesia_Western',
286
287
		// No DST
288
		'Asia/Jayapura' => 'Asia_Indonesia_Eastern',
289
290
		// Uses DST
291
		'Asia/Jerusalem' => 'Asia_Israel',
292
293
		// No DST
294
		'Asia/Kabul' => 'Asia_Afghanistan',
295
296
		// No DST
297
		'Asia/Kamchatka' => 'Asia_Kamchatka',
298
299
		// No DST
300
		'Asia/Karachi' => 'Asia_Pakistan',
301
302
		// No DST
303
		'Asia/Kathmandu' => 'Asia_Nepal',
304
305
		// No DST
306
		'Asia/Kolkata' => 'Asia_India',
307
308
		// No DST
309
		'Asia/Krasnoyarsk' => 'Asia_Krasnoyarsk',
310
311
		// No DST
312
		'Asia/Kuala_Lumpur' => 'Asia_Malaysia',
313
314
		// No DST
315
		'Asia/Magadan' => 'Asia_Magadan',
316
317
		// No DST
318
		'Asia/Makassar' => 'Asia_Indonesia_Central',
319
320
		// No DST
321
		'Asia/Manila' => 'Asia_Philippines',
322
323
		// No DST
324
		'Asia/Omsk' => 'Asia_Omsk',
325
326
		// No DST
327
		'Asia/Riyadh' => 'Asia_Arabia',
328
329
		// No DST
330
		'Asia/Seoul' => 'Asia_Korea',
331
332
		// No DST
333
		'Asia/Shanghai' => 'Asia_China',
334
335
		// No DST
336
		'Asia/Singapore' => 'Asia_Singapore',
337
338
		// No DST
339
		'Asia/Taipei' => 'Asia_Taiwan',
340
341
		// No DST
342
		'Asia/Tashkent' => 'Asia_Uzbekistan',
343
344
		// No DST
345
		'Asia/Tbilisi' => 'Asia_Georgia',
346
347
		// Uses DST
348
		'Asia/Tehran' => 'Asia_Iran',
349
350
		// No DST
351
		'Asia/Thimphu' => 'Asia_Bhutan',
352
353
		// No DST
354
		'Asia/Tokyo' => 'Asia_Japan',
355
356
		// No DST
357
		'Asia/Ulaanbaatar' => 'Asia_Mongolia_Eastern',
358
359
		// No DST
360
		'Asia/Vladivostok' => 'Asia_Vladivostok',
361
362
		// No DST
363
		'Asia/Yakutsk' => 'Asia_Yakutsk',
364
365
		// No DST
366
		'Asia/Yangon' => 'Asia_Myanmar',
367
368
		// No DST
369
		'Asia/Yekaterinburg' => 'Asia_Yekaterinburg',
370
371
		// No DST
372
		'Asia/Yerevan' => 'Asia_Armenia',
373
374
		// Uses DST
375
		'Atlantic/Azores' => 'Atlantic_Azores',
376
377
		// No DST
378
		'Atlantic/Cape_Verde' => 'Atlantic_Cape_Verde',
379
380
		// No DST
381
		'Atlantic/South_Georgia' => 'Atlantic_South_Georgia',
382
383
		// No DST
384
		'Atlantic/Stanley' => 'Atlantic_Falkland',
385
386
		// Uses DST
387
		'Australia/Adelaide' => 'Australia_Central',
388
389
		// No DST
390
		'Australia/Brisbane' => 'Australia_Eastern',
391
392
		// No DST
393
		'Australia/Darwin' => 'Australia_Central',
394
395
		// No DST
396
		'Australia/Eucla' => 'Australia_CentralWestern',
397
398
		// Uses DST
399
		'Australia/Lord_Howe' => 'Australia_Lord_Howe',
400
401
		// Uses DST
402
		'Australia/Melbourne' => 'Australia_Eastern',
403
404
		// No DST
405
		'Australia/Perth' => 'Australia_Western',
406
407
		// Uses DST
408
		'Europe/Berlin' => 'Europe_Central',
409
410
		// Uses DST
411
		'Europe/Chisinau' => 'Europe_Moldova',
412
413
		// Uses DST
414
		'Europe/Dublin' => 'Europe_Eire',
415
416
		// Uses DST
417
		'Europe/Helsinki' => 'Europe_Eastern',
418
419
		// No DST
420
		'Europe/Istanbul' => 'Asia_Turkey',
421
422
		// No DST
423
		'Europe/Kaliningrad' => 'Europe_Eastern',
424
425
		// Uses DST
426
		'Europe/Lisbon' => 'Europe_Western',
427
428
		// Uses DST
429
		'Europe/London' => 'Europe_UK',
430
431
		// No DST
432
		'Europe/Minsk' => 'Europe_Minsk',
433
434
		// No DST
435
		'Europe/Moscow' => 'Europe_Moscow',
436
437
		// No DST
438
		'Europe/Samara' => 'Europe_Samara',
439
440
		// No DST
441
		'Europe/Volgograd' => 'Europe_Volgograd',
442
443
		// No DST
444
		'Indian/Chagos' => 'Indian_Chagos',
445
446
		// No DST
447
		'Indian/Christmas' => 'Indian_Christmas',
448
449
		// No DST
450
		'Indian/Cocos' => 'Indian_Cocos',
451
452
		// No DST
453
		'Indian/Kerguelen' => 'Indian_Kerguelen',
454
455
		// No DST
456
		'Indian/Mahe' => 'Indian_Seychelles',
457
458
		// No DST
459
		'Indian/Maldives' => 'Indian_Maldives',
460
461
		// No DST
462
		'Indian/Mauritius' => 'Indian_Mauritius',
463
464
		// No DST
465
		'Indian/Reunion' => 'Indian_Reunion',
466
467
		// Uses DST
468
		'Pacific/Apia' => 'Pacific_Apia',
469
470
		// Uses DST
471
		'Pacific/Auckland' => 'Pacific_New_Zealand',
472
473
		// No DST
474
		'Pacific/Bougainville' => 'Pacific_Bougainville',
475
476
		// Uses DST
477
		'Pacific/Chatham' => 'Pacific_Chatham',
478
479
		// No DST
480
		'Pacific/Chuuk' => 'Pacific_Chuuk',
481
482
		// Uses DST
483
		'Pacific/Easter' => 'Pacific_Easter',
484
485
		// No DST
486
		'Pacific/Efate' => 'Pacific_Vanuatu',
487
488
		// No DST
489
		'Pacific/Kanton' => 'Pacific_Phoenix_Islands',
490
491
		// No DST
492
		'Pacific/Fakaofo' => 'Pacific_Tokelau',
493
494
		// Uses DST
495
		'Pacific/Fiji' => 'Pacific_Fiji',
496
497
		// No DST
498
		'Pacific/Funafuti' => 'Pacific_Tuvalu',
499
500
		// No DST
501
		'Pacific/Galapagos' => 'Pacific_Galapagos',
502
503
		// No DST
504
		'Pacific/Gambier' => 'Pacific_Gambier',
505
506
		// No DST
507
		'Pacific/Guadalcanal' => 'Pacific_Solomon',
508
509
		// No DST
510
		'Pacific/Guam' => 'Pacific_Chamorro',
511
512
		// No DST
513
		'Pacific/Honolulu' => 'Pacific_Hawaii',
514
515
		// No DST
516
		'Pacific/Kiritimati' => 'Pacific_Line',
517
518
		// No DST
519
		'Pacific/Kwajalein' => 'Pacific_Marshall',
520
521
		// No DST
522
		'Pacific/Marquesas' => 'Pacific_Marquesas',
523
524
		// No DST
525
		'Pacific/Nauru' => 'Pacific_Nauru',
526
527
		// No DST
528
		'Pacific/Niue' => 'Pacific_Niue',
529
530
		// No DST
531
		'Pacific/Norfolk' => 'Pacific_Norfolk',
532
533
		// No DST
534
		'Pacific/Noumea' => 'Pacific_New_Caledonia',
535
536
		// No DST
537
		'Pacific/Pago_Pago' => 'Pacific_Samoa',
538
539
		// No DST
540
		'Pacific/Palau' => 'Pacific_Palau',
541
542
		// No DST
543
		'Pacific/Pitcairn' => 'Pacific_Pitcairn',
544
545
		// No DST
546
		'Pacific/Pohnpei' => 'Pacific_Pohnpei',
547
548
		// No DST
549
		'Pacific/Port_Moresby' => 'Pacific_Papua_New_Guinea',
550
551
		// No DST
552
		'Pacific/Rarotonga' => 'Pacific_Cook',
553
554
		// No DST
555
		'Pacific/Tahiti' => 'Pacific_Tahiti',
556
557
		// No DST
558
		'Pacific/Tarawa' => 'Pacific_Gilbert',
559
560
		// No DST
561
		'Pacific/Tongatapu' => 'Pacific_Tonga',
562
563
		// No DST
564
		'Pacific/Wake' => 'Pacific_Wake',
565
566
		// No DST
567
		'Pacific/Wallis' => 'Pacific_Wallis',
568
	);
569
570
	call_integration_hook('integrate_metazones', array(&$tzid_metazones, $when));
571
572
	// Fallbacks in case the server has an old version of the TZDB.
573
	$tzids = array_keys($tzid_metazones);
574
	$tzid_fallbacks = get_tzid_fallbacks($tzids, $when);
575
	foreach ($tzid_fallbacks as $orig_tzid => $alt_tzid)
576
	{
577
		// Skip any that are unchanged.
578
		if ($orig_tzid == $alt_tzid)
579
			continue;
580
581
		// Use fallback where possible.
582
		if (!empty($alt_tzid) && empty($tzid_metazones[$alt_tzid]))
583
		{
584
			$tzid_metazones[$alt_tzid] = $tzid_metazones[$orig_tzid];
585
			$txt[$alt_tzid] = $txt[$orig_tzid];
586
		}
587
588
		// Either way, get rid of the unknown time zone.
589
		unset($tzid_metazones[$orig_tzid]);
590
	}
591
592
	return $tzid_metazones;
593
}
594
595
/**
596
 * Returns an array of all the time zones in a country, ranked according
597
 * to population and/or political significance.
598
 *
599
 * @param string $country_code The two-character ISO-3166 code for a country.
600
 * @param string $when The date/time used to determine fallback values.
601
 *		May be a Unix timestamp or any string that strtotime() can understand.
602
 *		Defaults to 'now'.
603
 * @return array An array relating time zones to "meta-zones"
604
 */
605
function get_sorted_tzids_for_country($country_code, $when = 'now')
606
{
607
	static $country_tzids = array();
608
609
	/*
610
		This array lists all the individual time zones in each country,
611
		sorted by population (as reported in statistics available on
612
		Wikipedia in November 2020). Sorting this way enables us to
613
		consistently select the most appropriate individual time zone to
614
		represent all others that share its DST transition rules and values.
615
		For example, this ensures that New York will be preferred over
616
		random small towns in Indiana.
617
618
		If future versions of the time zone database add new time zone
619
		identifiers beyond those included here, they should be added to this
620
		list as appropriate. However, SMF will gracefully handle unexpected
621
		new time zones, so nothing will break in the meantime.
622
	 */
623
	$sorted_tzids = array(
624
		// '??' means international.
625
		'??' => array(
626
			'UTC',
627
		),
628
		'AD' => array(
629
			'Europe/Andorra',
630
		),
631
		'AE' => array(
632
			'Asia/Dubai',
633
		),
634
		'AF' => array(
635
			'Asia/Kabul',
636
		),
637
		'AG' => array(
638
			'America/Antigua',
639
		),
640
		'AI' => array(
641
			'America/Anguilla',
642
		),
643
		'AL' => array(
644
			'Europe/Tirane',
645
		),
646
		'AM' => array(
647
			'Asia/Yerevan',
648
		),
649
		'AO' => array(
650
			'Africa/Luanda',
651
		),
652
		'AQ' => array(
653
			// Sorted based on summer population.
654
			'Antarctica/McMurdo',
655
			'Antarctica/Casey',
656
			'Antarctica/Davis',
657
			'Antarctica/Mawson',
658
			'Antarctica/Rothera',
659
			'Antarctica/Syowa',
660
			'Antarctica/Palmer',
661
			'Antarctica/Troll',
662
			'Antarctica/DumontDUrville',
663
			'Antarctica/Vostok',
664
		),
665
		'AR' => array(
666
			'America/Argentina/Buenos_Aires',
667
			'America/Argentina/Cordoba',
668
			'America/Argentina/Tucuman',
669
			'America/Argentina/Salta',
670
			'America/Argentina/Jujuy',
671
			'America/Argentina/La_Rioja',
672
			'America/Argentina/San_Luis',
673
			'America/Argentina/Catamarca',
674
			'America/Argentina/Mendoza',
675
			'America/Argentina/San_Juan',
676
			'America/Argentina/Rio_Gallegos',
677
			'America/Argentina/Ushuaia',
678
		),
679
		'AS' => array(
680
			'Pacific/Pago_Pago',
681
		),
682
		'AT' => array(
683
			'Europe/Vienna',
684
		),
685
		'AU' => array(
686
			'Australia/Sydney',
687
			'Australia/Melbourne',
688
			'Australia/Brisbane',
689
			'Australia/Perth',
690
			'Australia/Adelaide',
691
			'Australia/Hobart',
692
			'Australia/Darwin',
693
			'Australia/Broken_Hill',
694
			'Australia/Currie',
695
			'Australia/Lord_Howe',
696
			'Australia/Eucla',
697
			'Australia/Lindeman',
698
			'Antarctica/Macquarie',
699
		),
700
		'AW' => array(
701
			'America/Aruba',
702
		),
703
		'AX' => array(
704
			'Europe/Mariehamn',
705
		),
706
		'AZ' => array(
707
			'Asia/Baku',
708
		),
709
		'BA' => array(
710
			'Europe/Sarajevo',
711
		),
712
		'BB' => array(
713
			'America/Barbados',
714
		),
715
		'BD' => array(
716
			'Asia/Dhaka',
717
		),
718
		'BE' => array(
719
			'Europe/Brussels',
720
		),
721
		'BF' => array(
722
			'Africa/Ouagadougou',
723
		),
724
		'BG' => array(
725
			'Europe/Sofia',
726
		),
727
		'BH' => array(
728
			'Asia/Bahrain',
729
		),
730
		'BI' => array(
731
			'Africa/Bujumbura',
732
		),
733
		'BJ' => array(
734
			'Africa/Porto-Novo',
735
		),
736
		'BL' => array(
737
			'America/St_Barthelemy',
738
		),
739
		'BM' => array(
740
			'Atlantic/Bermuda',
741
		),
742
		'BN' => array(
743
			'Asia/Brunei',
744
		),
745
		'BO' => array(
746
			'America/La_Paz',
747
		),
748
		'BQ' => array(
749
			'America/Kralendijk',
750
		),
751
		'BR' => array(
752
			'America/Sao_Paulo',
753
			'America/Bahia',
754
			'America/Fortaleza',
755
			'America/Manaus',
756
			'America/Recife',
757
			'America/Belem',
758
			'America/Maceio',
759
			'America/Campo_Grande',
760
			'America/Cuiaba',
761
			'America/Porto_Velho',
762
			'America/Rio_Branco',
763
			'America/Boa_Vista',
764
			'America/Santarem',
765
			'America/Araguaina',
766
			'America/Eirunepe',
767
			'America/Noronha',
768
		),
769
		'BS' => array(
770
			'America/Nassau',
771
		),
772
		'BT' => array(
773
			'Asia/Thimphu',
774
		),
775
		'BW' => array(
776
			'Africa/Gaborone',
777
		),
778
		'BY' => array(
779
			'Europe/Minsk',
780
		),
781
		'BZ' => array(
782
			'America/Belize',
783
		),
784
		'CA' => array(
785
			'America/Toronto',
786
			'America/Vancouver',
787
			'America/Edmonton',
788
			'America/Winnipeg',
789
			'America/Halifax',
790
			'America/Regina',
791
			'America/St_Johns',
792
			'America/Moncton',
793
			'America/Thunder_Bay',
794
			'America/Whitehorse',
795
			'America/Glace_Bay',
796
			'America/Yellowknife',
797
			'America/Swift_Current',
798
			'America/Dawson_Creek',
799
			'America/Goose_Bay',
800
			'America/Iqaluit',
801
			'America/Creston',
802
			'America/Fort_Nelson',
803
			'America/Inuvik',
804
			'America/Atikokan',
805
			'America/Rankin_Inlet',
806
			'America/Nipigon',
807
			'America/Cambridge_Bay',
808
			'America/Pangnirtung',
809
			'America/Dawson',
810
			'America/Blanc-Sablon',
811
			'America/Rainy_River',
812
			'America/Resolute',
813
		),
814
		'CC' => array(
815
			'Indian/Cocos',
816
		),
817
		'CD' => array(
818
			'Africa/Kinshasa',
819
			'Africa/Lubumbashi',
820
		),
821
		'CF' => array(
822
			'Africa/Bangui',
823
		),
824
		'CG' => array(
825
			'Africa/Brazzaville',
826
		),
827
		'CH' => array(
828
			'Europe/Zurich',
829
		),
830
		'CI' => array(
831
			'Africa/Abidjan',
832
		),
833
		'CK' => array(
834
			'Pacific/Rarotonga',
835
		),
836
		'CL' => array(
837
			'America/Santiago',
838
			'America/Punta_Arenas',
839
			'America/Coyhaique',
840
			'Pacific/Easter',
841
		),
842
		'CM' => array(
843
			'Africa/Douala',
844
		),
845
		'CN' => array(
846
			'Asia/Shanghai',
847
			'Asia/Urumqi',
848
		),
849
		'CO' => array(
850
			'America/Bogota',
851
		),
852
		'CR' => array(
853
			'America/Costa_Rica',
854
		),
855
		'CU' => array(
856
			'America/Havana',
857
		),
858
		'CV' => array(
859
			'Atlantic/Cape_Verde',
860
		),
861
		'CW' => array(
862
			'America/Curacao',
863
		),
864
		'CX' => array(
865
			'Indian/Christmas',
866
		),
867
		'CY' => array(
868
			'Asia/Nicosia',
869
			'Asia/Famagusta',
870
		),
871
		'CZ' => array(
872
			'Europe/Prague',
873
		),
874
		'DE' => array(
875
			'Europe/Berlin',
876
			'Europe/Busingen',
877
		),
878
		'DJ' => array(
879
			'Africa/Djibouti',
880
		),
881
		'DK' => array(
882
			'Europe/Copenhagen',
883
		),
884
		'DM' => array(
885
			'America/Dominica',
886
		),
887
		'DO' => array(
888
			'America/Santo_Domingo',
889
		),
890
		'DZ' => array(
891
			'Africa/Algiers',
892
		),
893
		'EC' => array(
894
			'America/Guayaquil',
895
			'Pacific/Galapagos',
896
		),
897
		'EE' => array(
898
			'Europe/Tallinn',
899
		),
900
		'EG' => array(
901
			'Africa/Cairo',
902
		),
903
		'EH' => array(
904
			'Africa/El_Aaiun',
905
		),
906
		'ER' => array(
907
			'Africa/Asmara',
908
		),
909
		'ES' => array(
910
			'Europe/Madrid',
911
			'Atlantic/Canary',
912
			'Africa/Ceuta',
913
		),
914
		'ET' => array(
915
			'Africa/Addis_Ababa',
916
		),
917
		'FI' => array(
918
			'Europe/Helsinki',
919
		),
920
		'FJ' => array(
921
			'Pacific/Fiji',
922
		),
923
		'FK' => array(
924
			'Atlantic/Stanley',
925
		),
926
		'FM' => array(
927
			'Pacific/Chuuk',
928
			'Pacific/Kosrae',
929
			'Pacific/Pohnpei',
930
		),
931
		'FO' => array(
932
			'Atlantic/Faroe',
933
		),
934
		'FR' => array(
935
			'Europe/Paris',
936
		),
937
		'GA' => array(
938
			'Africa/Libreville',
939
		),
940
		'GB' => array(
941
			'Europe/London',
942
		),
943
		'GD' => array(
944
			'America/Grenada',
945
		),
946
		'GE' => array(
947
			'Asia/Tbilisi',
948
		),
949
		'GF' => array(
950
			'America/Cayenne',
951
		),
952
		'GG' => array(
953
			'Europe/Guernsey',
954
		),
955
		'GH' => array(
956
			'Africa/Accra',
957
		),
958
		'GI' => array(
959
			'Europe/Gibraltar',
960
		),
961
		'GL' => array(
962
			'America/Nuuk',
963
			'America/Thule',
964
			'America/Scoresbysund',
965
			'America/Danmarkshavn',
966
		),
967
		'GM' => array(
968
			'Africa/Banjul',
969
		),
970
		'GN' => array(
971
			'Africa/Conakry',
972
		),
973
		'GP' => array(
974
			'America/Guadeloupe',
975
		),
976
		'GQ' => array(
977
			'Africa/Malabo',
978
		),
979
		'GR' => array(
980
			'Europe/Athens',
981
		),
982
		'GS' => array(
983
			'Atlantic/South_Georgia',
984
		),
985
		'GT' => array(
986
			'America/Guatemala',
987
		),
988
		'GU' => array(
989
			'Pacific/Guam',
990
		),
991
		'GW' => array(
992
			'Africa/Bissau',
993
		),
994
		'GY' => array(
995
			'America/Guyana',
996
		),
997
		'HK' => array(
998
			'Asia/Hong_Kong',
999
		),
1000
		'HN' => array(
1001
			'America/Tegucigalpa',
1002
		),
1003
		'HR' => array(
1004
			'Europe/Zagreb',
1005
		),
1006
		'HT' => array(
1007
			'America/Port-au-Prince',
1008
		),
1009
		'HU' => array(
1010
			'Europe/Budapest',
1011
		),
1012
		'ID' => array(
1013
			'Asia/Jakarta',
1014
			'Asia/Makassar',
1015
			'Asia/Pontianak',
1016
			'Asia/Jayapura',
1017
		),
1018
		'IE' => array(
1019
			'Europe/Dublin',
1020
		),
1021
		'IL' => array(
1022
			'Asia/Jerusalem',
1023
		),
1024
		'IM' => array(
1025
			'Europe/Isle_of_Man',
1026
		),
1027
		'IN' => array(
1028
			'Asia/Kolkata',
1029
		),
1030
		'IO' => array(
1031
			'Indian/Chagos',
1032
		),
1033
		'IQ' => array(
1034
			'Asia/Baghdad',
1035
		),
1036
		'IR' => array(
1037
			'Asia/Tehran',
1038
		),
1039
		'IS' => array(
1040
			'Atlantic/Reykjavik',
1041
		),
1042
		'IT' => array(
1043
			'Europe/Rome',
1044
		),
1045
		'JE' => array(
1046
			'Europe/Jersey',
1047
		),
1048
		'JM' => array(
1049
			'America/Jamaica',
1050
		),
1051
		'JO' => array(
1052
			'Asia/Amman',
1053
		),
1054
		'JP' => array(
1055
			'Asia/Tokyo',
1056
		),
1057
		'KE' => array(
1058
			'Africa/Nairobi',
1059
		),
1060
		'KG' => array(
1061
			'Asia/Bishkek',
1062
		),
1063
		'KH' => array(
1064
			'Asia/Phnom_Penh',
1065
		),
1066
		'KI' => array(
1067
			'Pacific/Tarawa',
1068
			'Pacific/Kiritimati',
1069
			'Pacific/Kanton',
1070
			'Pacific/Enderbury',
1071
		),
1072
		'KM' => array(
1073
			'Indian/Comoro',
1074
		),
1075
		'KN' => array(
1076
			'America/St_Kitts',
1077
		),
1078
		'KP' => array(
1079
			'Asia/Pyongyang',
1080
		),
1081
		'KR' => array(
1082
			'Asia/Seoul',
1083
		),
1084
		'KW' => array(
1085
			'Asia/Kuwait',
1086
		),
1087
		'KY' => array(
1088
			'America/Cayman',
1089
		),
1090
		'KZ' => array(
1091
			'Asia/Almaty',
1092
			'Asia/Aqtobe',
1093
			'Asia/Atyrau',
1094
			'Asia/Qostanay',
1095
			'Asia/Qyzylorda',
1096
			'Asia/Aqtau',
1097
			'Asia/Oral',
1098
		),
1099
		'LA' => array(
1100
			'Asia/Vientiane',
1101
		),
1102
		'LB' => array(
1103
			'Asia/Beirut',
1104
		),
1105
		'LC' => array(
1106
			'America/St_Lucia',
1107
		),
1108
		'LI' => array(
1109
			'Europe/Vaduz',
1110
		),
1111
		'LK' => array(
1112
			'Asia/Colombo',
1113
		),
1114
		'LR' => array(
1115
			'Africa/Monrovia',
1116
		),
1117
		'LS' => array(
1118
			'Africa/Maseru',
1119
		),
1120
		'LT' => array(
1121
			'Europe/Vilnius',
1122
		),
1123
		'LU' => array(
1124
			'Europe/Luxembourg',
1125
		),
1126
		'LV' => array(
1127
			'Europe/Riga',
1128
		),
1129
		'LY' => array(
1130
			'Africa/Tripoli',
1131
		),
1132
		'MA' => array(
1133
			'Africa/Casablanca',
1134
		),
1135
		'MC' => array(
1136
			'Europe/Monaco',
1137
		),
1138
		'MD' => array(
1139
			'Europe/Chisinau',
1140
		),
1141
		'ME' => array(
1142
			'Europe/Podgorica',
1143
		),
1144
		'MF' => array(
1145
			'America/Marigot',
1146
		),
1147
		'MG' => array(
1148
			'Indian/Antananarivo',
1149
		),
1150
		'MH' => array(
1151
			'Pacific/Majuro',
1152
			'Pacific/Kwajalein',
1153
		),
1154
		'MK' => array(
1155
			'Europe/Skopje',
1156
		),
1157
		'ML' => array(
1158
			'Africa/Bamako',
1159
		),
1160
		'MM' => array(
1161
			'Asia/Yangon',
1162
		),
1163
		'MN' => array(
1164
			'Asia/Ulaanbaatar',
1165
			'Asia/Choibalsan',
1166
			'Asia/Hovd',
1167
		),
1168
		'MO' => array(
1169
			'Asia/Macau',
1170
		),
1171
		'MP' => array(
1172
			'Pacific/Saipan',
1173
		),
1174
		'MQ' => array(
1175
			'America/Martinique',
1176
		),
1177
		'MR' => array(
1178
			'Africa/Nouakchott',
1179
		),
1180
		'MS' => array(
1181
			'America/Montserrat',
1182
		),
1183
		'MT' => array(
1184
			'Europe/Malta',
1185
		),
1186
		'MU' => array(
1187
			'Indian/Mauritius',
1188
		),
1189
		'MV' => array(
1190
			'Indian/Maldives',
1191
		),
1192
		'MW' => array(
1193
			'Africa/Blantyre',
1194
		),
1195
		'MX' => array(
1196
			'America/Mexico_City',
1197
			'America/Tijuana',
1198
			'America/Monterrey',
1199
			'America/Ciudad_Juarez',
1200
			'America/Chihuahua',
1201
			'America/Merida',
1202
			'America/Hermosillo',
1203
			'America/Cancun',
1204
			'America/Matamoros',
1205
			'America/Mazatlan',
1206
			'America/Bahia_Banderas',
1207
			'America/Ojinaga',
1208
		),
1209
		'MY' => array(
1210
			'Asia/Kuala_Lumpur',
1211
			'Asia/Kuching',
1212
		),
1213
		'MZ' => array(
1214
			'Africa/Maputo',
1215
		),
1216
		'NA' => array(
1217
			'Africa/Windhoek',
1218
		),
1219
		'NC' => array(
1220
			'Pacific/Noumea',
1221
		),
1222
		'NE' => array(
1223
			'Africa/Niamey',
1224
		),
1225
		'NF' => array(
1226
			'Pacific/Norfolk',
1227
		),
1228
		'NG' => array(
1229
			'Africa/Lagos',
1230
		),
1231
		'NI' => array(
1232
			'America/Managua',
1233
		),
1234
		'NL' => array(
1235
			'Europe/Amsterdam',
1236
		),
1237
		'NO' => array(
1238
			'Europe/Oslo',
1239
		),
1240
		'NP' => array(
1241
			'Asia/Kathmandu',
1242
		),
1243
		'NR' => array(
1244
			'Pacific/Nauru',
1245
		),
1246
		'NU' => array(
1247
			'Pacific/Niue',
1248
		),
1249
		'NZ' => array(
1250
			'Pacific/Auckland',
1251
			'Pacific/Chatham',
1252
		),
1253
		'OM' => array(
1254
			'Asia/Muscat',
1255
		),
1256
		'PA' => array(
1257
			'America/Panama',
1258
		),
1259
		'PE' => array(
1260
			'America/Lima',
1261
		),
1262
		'PF' => array(
1263
			'Pacific/Tahiti',
1264
			'Pacific/Marquesas',
1265
			'Pacific/Gambier',
1266
		),
1267
		'PG' => array(
1268
			'Pacific/Port_Moresby',
1269
			'Pacific/Bougainville',
1270
		),
1271
		'PH' => array(
1272
			'Asia/Manila',
1273
		),
1274
		'PK' => array(
1275
			'Asia/Karachi',
1276
		),
1277
		'PL' => array(
1278
			'Europe/Warsaw',
1279
		),
1280
		'PM' => array(
1281
			'America/Miquelon',
1282
		),
1283
		'PN' => array(
1284
			'Pacific/Pitcairn',
1285
		),
1286
		'PR' => array(
1287
			'America/Puerto_Rico',
1288
		),
1289
		'PS' => array(
1290
			'Asia/Gaza',
1291
			'Asia/Hebron',
1292
		),
1293
		'PT' => array(
1294
			'Europe/Lisbon',
1295
			'Atlantic/Madeira',
1296
			'Atlantic/Azores',
1297
		),
1298
		'PW' => array(
1299
			'Pacific/Palau',
1300
		),
1301
		'PY' => array(
1302
			'America/Asuncion',
1303
		),
1304
		'QA' => array(
1305
			'Asia/Qatar',
1306
		),
1307
		'RE' => array(
1308
			'Indian/Reunion',
1309
		),
1310
		'RO' => array(
1311
			'Europe/Bucharest',
1312
		),
1313
		'RS' => array(
1314
			'Europe/Belgrade',
1315
		),
1316
		'RU' => array(
1317
			'Europe/Moscow',
1318
			'Asia/Novosibirsk',
1319
			'Asia/Yekaterinburg',
1320
			'Europe/Samara',
1321
			'Asia/Omsk',
1322
			'Asia/Krasnoyarsk',
1323
			'Europe/Volgograd',
1324
			'Europe/Saratov',
1325
			'Asia/Barnaul',
1326
			'Europe/Ulyanovsk',
1327
			'Asia/Irkutsk',
1328
			'Asia/Vladivostok',
1329
			'Asia/Tomsk',
1330
			'Asia/Novokuznetsk',
1331
			'Europe/Astrakhan',
1332
			'Europe/Kirov',
1333
			'Europe/Kaliningrad',
1334
			'Asia/Chita',
1335
			'Asia/Yakutsk',
1336
			'Asia/Sakhalin',
1337
			'Asia/Kamchatka',
1338
			'Asia/Magadan',
1339
			'Asia/Anadyr',
1340
			'Asia/Khandyga',
1341
			'Asia/Ust-Nera',
1342
			'Asia/Srednekolymsk',
1343
		),
1344
		'RW' => array(
1345
			'Africa/Kigali',
1346
		),
1347
		'SA' => array(
1348
			'Asia/Riyadh',
1349
		),
1350
		'SB' => array(
1351
			'Pacific/Guadalcanal',
1352
		),
1353
		'SC' => array(
1354
			'Indian/Mahe',
1355
		),
1356
		'SD' => array(
1357
			'Africa/Khartoum',
1358
		),
1359
		'SE' => array(
1360
			'Europe/Stockholm',
1361
		),
1362
		'SG' => array(
1363
			'Asia/Singapore',
1364
		),
1365
		'SH' => array(
1366
			'Atlantic/St_Helena',
1367
		),
1368
		'SI' => array(
1369
			'Europe/Ljubljana',
1370
		),
1371
		'SJ' => array(
1372
			'Arctic/Longyearbyen',
1373
		),
1374
		'SK' => array(
1375
			'Europe/Bratislava',
1376
		),
1377
		'SL' => array(
1378
			'Africa/Freetown',
1379
		),
1380
		'SM' => array(
1381
			'Europe/San_Marino',
1382
		),
1383
		'SN' => array(
1384
			'Africa/Dakar',
1385
		),
1386
		'SO' => array(
1387
			'Africa/Mogadishu',
1388
		),
1389
		'SR' => array(
1390
			'America/Paramaribo',
1391
		),
1392
		'SS' => array(
1393
			'Africa/Juba',
1394
		),
1395
		'ST' => array(
1396
			'Africa/Sao_Tome',
1397
		),
1398
		'SV' => array(
1399
			'America/El_Salvador',
1400
		),
1401
		'SX' => array(
1402
			'America/Lower_Princes',
1403
		),
1404
		'SY' => array(
1405
			'Asia/Damascus',
1406
		),
1407
		'SZ' => array(
1408
			'Africa/Mbabane',
1409
		),
1410
		'TC' => array(
1411
			'America/Grand_Turk',
1412
		),
1413
		'TD' => array(
1414
			'Africa/Ndjamena',
1415
		),
1416
		'TF' => array(
1417
			'Indian/Kerguelen',
1418
		),
1419
		'TG' => array(
1420
			'Africa/Lome',
1421
		),
1422
		'TH' => array(
1423
			'Asia/Bangkok',
1424
		),
1425
		'TJ' => array(
1426
			'Asia/Dushanbe',
1427
		),
1428
		'TK' => array(
1429
			'Pacific/Fakaofo',
1430
		),
1431
		'TL' => array(
1432
			'Asia/Dili',
1433
		),
1434
		'TM' => array(
1435
			'Asia/Ashgabat',
1436
		),
1437
		'TN' => array(
1438
			'Africa/Tunis',
1439
		),
1440
		'TO' => array(
1441
			'Pacific/Tongatapu',
1442
		),
1443
		'TR' => array(
1444
			'Europe/Istanbul',
1445
		),
1446
		'TT' => array(
1447
			'America/Port_of_Spain',
1448
		),
1449
		'TV' => array(
1450
			'Pacific/Funafuti',
1451
		),
1452
		'TW' => array(
1453
			'Asia/Taipei',
1454
		),
1455
		'TZ' => array(
1456
			'Africa/Dar_es_Salaam',
1457
		),
1458
		'UA' => array(
1459
			'Europe/Kyiv',
1460
			'Europe/Zaporozhye',
1461
			'Europe/Simferopol',
1462
			'Europe/Uzhgorod',
1463
		),
1464
		'UG' => array(
1465
			'Africa/Kampala',
1466
		),
1467
		'UM' => array(
1468
			'Pacific/Midway',
1469
			'Pacific/Wake',
1470
		),
1471
		'US' => array(
1472
			'America/New_York',
1473
			'America/Los_Angeles',
1474
			'America/Chicago',
1475
			'America/Denver',
1476
			'America/Phoenix',
1477
			'America/Indiana/Indianapolis',
1478
			'America/Detroit',
1479
			'America/Kentucky/Louisville',
1480
			'Pacific/Honolulu',
1481
			'America/Anchorage',
1482
			'America/Boise',
1483
			'America/Juneau',
1484
			'America/Indiana/Vincennes',
1485
			'America/Sitka',
1486
			'America/Menominee',
1487
			'America/Indiana/Tell_City',
1488
			'America/Kentucky/Monticello',
1489
			'America/Nome',
1490
			'America/Indiana/Knox',
1491
			'America/North_Dakota/Beulah',
1492
			'America/Indiana/Winamac',
1493
			'America/Indiana/Petersburg',
1494
			'America/Indiana/Vevay',
1495
			'America/Metlakatla',
1496
			'America/North_Dakota/New_Salem',
1497
			'America/Indiana/Marengo',
1498
			'America/Yakutat',
1499
			'America/North_Dakota/Center',
1500
			'America/Adak',
1501
		),
1502
		'UY' => array(
1503
			'America/Montevideo',
1504
		),
1505
		'UZ' => array(
1506
			'Asia/Tashkent',
1507
			'Asia/Samarkand',
1508
		),
1509
		'VA' => array(
1510
			'Europe/Vatican',
1511
		),
1512
		'VC' => array(
1513
			'America/St_Vincent',
1514
		),
1515
		'VE' => array(
1516
			'America/Caracas',
1517
		),
1518
		'VG' => array(
1519
			'America/Tortola',
1520
		),
1521
		'VI' => array(
1522
			'America/St_Thomas',
1523
		),
1524
		'VN' => array(
1525
			'Asia/Ho_Chi_Minh',
1526
		),
1527
		'VU' => array(
1528
			'Pacific/Efate',
1529
		),
1530
		'WF' => array(
1531
			'Pacific/Wallis',
1532
		),
1533
		'WS' => array(
1534
			'Pacific/Apia',
1535
		),
1536
		'YE' => array(
1537
			'Asia/Aden',
1538
		),
1539
		'YT' => array(
1540
			'Indian/Mayotte',
1541
		),
1542
		'ZA' => array(
1543
			'Africa/Johannesburg',
1544
		),
1545
		'ZM' => array(
1546
			'Africa/Lusaka',
1547
		),
1548
		'ZW' => array(
1549
			'Africa/Harare',
1550
		),
1551
	);
1552
1553
	// Just in case...
1554
	$country_code = strtoupper(trim($country_code));
1555
1556
	// Avoid unnecessary repetition.
1557
	if (!isset($country_tzids[$country_code]))
1558
	{
1559
		call_integration_hook('integrate_country_timezones', array(&$sorted_tzids, $country_code, $when));
1560
1561
		$country_tzids[$country_code] = isset($sorted_tzids[$country_code]) ? $sorted_tzids[$country_code] : array();
1562
1563
		// If something goes wrong, we want an empty array, not false.
1564
		$recognized_country_tzids = array_filter((array) @timezone_identifiers_list(DateTimeZone::PER_COUNTRY, $country_code));
1565
1566
		// Make sure that no time zones are missing.
1567
		$country_tzids[$country_code] = array_unique(array_merge($country_tzids[$country_code], array_intersect($recognized_country_tzids, timezone_identifiers_list())));
1568
1569
		// Get fallbacks where necessary.
1570
		$country_tzids[$country_code] = array_unique(array_values(get_tzid_fallbacks($country_tzids[$country_code], $when)));
1571
1572
		// Filter out any time zones that are still undefined.
1573
		$country_tzids[$country_code] = array_intersect(array_filter($country_tzids[$country_code]), timezone_identifiers_list(DateTimeZone::ALL_WITH_BC));
1574
	}
1575
1576
	return $country_tzids[$country_code];
1577
}
1578
1579
/**
1580
 * Checks a list of time zone identifiers to make sure they are all defined in
1581
 * the installed version of the time zone database, and returns an array of
1582
 * key-value substitution pairs.
1583
 *
1584
 * For defined time zone identifiers, the substitution value will be identical
1585
 * to the original value. For undefined ones, the substitute will be a time zone
1586
 * identifier that was equivalent to the missing one at the specified time, or
1587
 * an empty string if there was no equivalent at that time.
1588
 *
1589
 * Note: These fallbacks do not need to include every new time zone ever. They
1590
 * only need to cover any that are used in $tzid_metazones.
1591
 *
1592
 * To find the date & time when a new time zone comes into effect, check
1593
 * the TZDB changelog at https://data.iana.org/time-zones/tzdb/NEWS
1594
 *
1595
 * @param array $tzids The time zone identifiers to check.
1596
 * @param string $when The date/time used to determine substitute values.
1597
 *		May be a Unix timestamp or any string that strtotime() can understand.
1598
 *		Defaults to 'now'.
1599
 * @return array Substitute values for any missing time zone identifiers.
1600
 */
1601
function get_tzid_fallbacks($tzids, $when = 'now')
1602
{
1603
	$tzids = (array) $tzids;
1604
1605
	$when = is_numeric($when) ? intval($when) : (is_int(@strtotime($when)) ? strtotime($when) : time());
1606
1607
	// 'ts' is the timestamp when the substitution first becomes valid.
1608
	// 'tzid' is the alternative time zone identifier to use.
1609
	$fallbacks = array(
1610
		// 1. Simple renames. PHP_INT_MIN because these are valid for all dates.
1611
		'Asia/Kolkata' => array(
1612
			array(
1613
				'ts' => PHP_INT_MIN,
1614
				'tzid' => 'Asia/Calcutta',
1615
			),
1616
		),
1617
		'Pacific/Chuuk' => array(
1618
			array(
1619
				'ts' => PHP_INT_MIN,
1620
				'tzid' => 'Pacific/Truk',
1621
			),
1622
		),
1623
		'Pacific/Kanton' => array(
1624
			array(
1625
				'ts' => PHP_INT_MIN,
1626
				'tzid' => 'Pacific/Enderbury',
1627
			),
1628
		),
1629
		'Pacific/Pohnpei' => array(
1630
			array(
1631
				'ts' => PHP_INT_MIN,
1632
				'tzid' => 'Pacific/Ponape',
1633
			),
1634
		),
1635
		'Asia/Yangon' => array(
1636
			array(
1637
				'ts' => PHP_INT_MIN,
1638
				'tzid' => 'Asia/Rangoon',
1639
			),
1640
		),
1641
		'America/Nuuk' => array(
1642
			array(
1643
				'ts' => PHP_INT_MIN,
1644
				'tzid' => 'America/Godthab',
1645
			),
1646
		),
1647
		'Europe/Busingen' => array(
1648
			array(
1649
				'ts' => PHP_INT_MIN,
1650
				'tzid' => 'Europe/Zurich',
1651
			),
1652
		),
1653
		'Europe/Kyiv' => array(
1654
			array(
1655
				'ts' => PHP_INT_MIN,
1656
				'tzid' => 'Europe/Kiev',
1657
			),
1658
		),
1659
1660
		// 2. Newly created time zones.
1661
1662
		// The initial entry in many of the following zones is set to '' because
1663
		// the records go back to eras before the adoption of standardized time
1664
		// zones, which means no substitutes are possible then.
1665
1666
		// The same as Tasmania, except it stayed on DST all year in 2010.
1667
		// Australia/Tasmania is an otherwise unused backwards compatibility
1668
		// link to Australia/Hobart, so we can borrow it here without conflict.
1669
		'Antarctica/Macquarie' => array(
1670
			array(
1671
				'ts' => PHP_INT_MIN,
1672
				'tzid' => 'Australia/Tasmania',
1673
			),
1674
			array(
1675
				'ts' => strtotime('2010-04-03T16:00:00+0000'),
1676
				'tzid' => 'Etc/GMT-11',
1677
			),
1678
			array(
1679
				'ts' => strtotime('2011-04-07T17:00:00+0000'),
1680
				'tzid' => 'Australia/Tasmania',
1681
			),
1682
		),
1683
1684
		// Added in version 2013a.
1685
		'Asia/Khandyga' => array(
1686
			array(
1687
				'ts' => PHP_INT_MIN,
1688
				'tzid' => '',
1689
			),
1690
			array(
1691
				'ts' => strtotime('1919-12-14T14:57:47+0000'),
1692
				'tzid' => 'Etc/GMT-8',
1693
			),
1694
			array(
1695
				'ts' => strtotime('1930-06-20T16:00:00+0000'),
1696
				'tzid' => 'Asia/Yakutsk',
1697
			),
1698
			array(
1699
				'ts' => strtotime('2003-12-31T15:00:00+0000'),
1700
				'tzid' => 'Asia/Vladivostok',
1701
			),
1702
			array(
1703
				'ts' => strtotime('2011-09-12T13:00:00+0000'),
1704
				'tzid' => 'Asia/Yakutsk',
1705
			),
1706
		),
1707
1708
		// Added in version 2013a.
1709
		'Asia/Ust-Nera' => array(
1710
			array(
1711
				'ts' => PHP_INT_MIN,
1712
				'tzid' => '',
1713
			),
1714
			array(
1715
				'ts' => strtotime('1919-12-14T14:27:06+0000'),
1716
				'tzid' => 'Etc/GMT-8',
1717
			),
1718
			array(
1719
				'ts' => strtotime('1930-06-20T16:00:00+0000'),
1720
				'tzid' => 'Asia/Yakutsk',
1721
			),
1722
			array(
1723
				'ts' => strtotime('1981-03-31T15:00:00+0000'),
1724
				'tzid' => 'Asia/Magadan',
1725
			),
1726
			array(
1727
				'ts' => strtotime('2011-09-12T12:00:00+0000'),
1728
				'tzid' => 'Asia/Vladivostok',
1729
			),
1730
		),
1731
1732
		// Created in version 2014b.
1733
		// This place uses two hours for DST. No substitutes are possible.
1734
		'Antarctica/Troll' => array(
1735
			array(
1736
				'ts' => PHP_INT_MIN,
1737
				'tzid' => '',
1738
			),
1739
		),
1740
1741
		// Diverged from Asia/Yakustsk in version 2014f.
1742
		'Asia/Chita' => array(
1743
			array(
1744
				'ts' => PHP_INT_MIN,
1745
				'tzid' => '',
1746
			),
1747
			array(
1748
				'ts' => strtotime('1919-12-14T16:26:08+0000'),
1749
				'tzid' => 'Asia/Yakutsk',
1750
			),
1751
			array(
1752
				'ts' => strtotime('2014-10-25T16:00:00+0000'),
1753
				'tzid' => 'Etc/GMT-8',
1754
			),
1755
			array(
1756
				'ts' => strtotime('2016-03-26T18:00:00+0000'),
1757
				'tzid' => 'Asia/Yakutsk',
1758
			),
1759
		),
1760
1761
		// Diverged from Asia/Magadan in version 2014f.
1762
		'Asia/Srednekolymsk' => array(
1763
			array(
1764
				'ts' => PHP_INT_MIN,
1765
				'tzid' => '',
1766
			),
1767
			array(
1768
				'ts' => strtotime('1924-05-01T13:45:08+0000'),
1769
				'tzid' => 'Etc/GMT-10',
1770
			),
1771
			array(
1772
				'ts' => strtotime('1930-06-20T14:00:00+0000'),
1773
				'tzid' => 'Asia/Magadan',
1774
			),
1775
			array(
1776
				'ts' => strtotime('2014-10-25T14:00:00+0000'),
1777
				'tzid' => 'Etc/GMT-11',
1778
			),
1779
		),
1780
1781
		// Diverged from Pacific/Port_Moresby in version 2014i.
1782
		'Pacific/Bougainville' => array(
1783
			array(
1784
				'ts' => PHP_INT_MIN,
1785
				'tzid' => '',
1786
			),
1787
			// Pacific/Yap is an unused link to Pacific/Port_Moresby.
1788
			array(
1789
				'ts' => strtotime('1879-12-31T14:11:20+0000'),
1790
				'tzid' => 'Pacific/Yap',
1791
			),
1792
			// Apparently this was different for a while in World War II.
1793
			array(
1794
				'ts' => strtotime('1942-06-30T14:00:00+0000'),
1795
				'tzid' => 'Singapore',
1796
			),
1797
			array(
1798
				'ts' => strtotime('1945-08-20T15:00:00+0000'),
1799
				'tzid' => 'Pacific/Yap',
1800
			),
1801
			// For dates after divergence, it is the same as Pacific/Kosrae.
1802
			// If this ever ceases to be true, add another entry.
1803
			array(
1804
				'ts' => strtotime('2014-12-27T16:00:00+0000'),
1805
				'tzid' => 'Pacific/Kosrae',
1806
			),
1807
		),
1808
1809
		// Added in version 2015g.
1810
		'America/Fort_Nelson' => array(
1811
			array(
1812
				'ts' => PHP_INT_MIN,
1813
				'tzid' => '',
1814
			),
1815
			array(
1816
				'ts' => strtotime('1884-01-01T08:12:28+0000'),
1817
				'tzid' => 'Canada/Pacific',
1818
			),
1819
			array(
1820
				'ts' => strtotime('1946-01-01T08:00:00+0000'),
1821
				'tzid' => 'Etc/GMT+8',
1822
			),
1823
			array(
1824
				'ts' => strtotime('1947-01-01T08:00:00+0000'),
1825
				'tzid' => 'Canada/Pacific',
1826
			),
1827
			array(
1828
				'ts' => strtotime('2015-03-08T10:00:00+0000'),
1829
				'tzid' => 'MST',
1830
			),
1831
		),
1832
1833
		// Created in version 2016b.
1834
		'Europe/Astrakhan' => array(
1835
			array(
1836
				'ts' => PHP_INT_MIN,
1837
				'tzid' => '',
1838
			),
1839
			array(
1840
				'ts' => strtotime('1935-01-26T20:00:00+0000'),
1841
				'tzid' => 'Europe/Samara',
1842
			),
1843
			array(
1844
				'ts' => strtotime('1989-03-25T22:00:00+0000'),
1845
				'tzid' => 'Europe/Volgograd',
1846
			),
1847
			array(
1848
				'ts' => strtotime('2016-03-26T23:00:00+0000'),
1849
				'tzid' => 'Europe/Samara',
1850
			),
1851
		),
1852
1853
		// Created in version 2016b.
1854
		'Europe/Ulyanovsk' => array(
1855
			array(
1856
				'ts' => PHP_INT_MIN,
1857
				'tzid' => '',
1858
			),
1859
			array(
1860
				'ts' => strtotime('1935-01-26T20:00:00+0000'),
1861
				'tzid' => 'Europe/Samara',
1862
			),
1863
			array(
1864
				'ts' => strtotime('1989-03-25T22:00:00+0000'),
1865
				'tzid' => 'W-SU',
1866
			),
1867
			array(
1868
				'ts' => strtotime('2016-03-26T23:00:00+0000'),
1869
				'tzid' => 'Europe/Samara',
1870
			),
1871
		),
1872
1873
		// Created in version 2016b.
1874
		'Asia/Barnaul' => array(
1875
			array(
1876
				'ts' => PHP_INT_MIN,
1877
				'tzid' => '',
1878
			),
1879
			array(
1880
				'ts' => strtotime('1919-12-09T18:25:00+0000'),
1881
				'tzid' => 'Etc/GMT-6',
1882
			),
1883
			array(
1884
				'ts' => strtotime('1930-06-20T18:00:00+0000'),
1885
				'tzid' => 'Asia/Novokuznetsk',
1886
			),
1887
			array(
1888
				'ts' => strtotime('1995-05-27T16:00:00+0000'),
1889
				'tzid' => 'Asia/Novosibirsk',
1890
			),
1891
			array(
1892
				'ts' => strtotime('2016-03-26T20:00:00+0000'),
1893
				'tzid' => 'Asia/Novokuznetsk',
1894
			),
1895
		),
1896
1897
		// Created in version 2016b.
1898
		'Asia/Tomsk' => array(
1899
			array(
1900
				'ts' => PHP_INT_MIN,
1901
				'tzid' => '',
1902
			),
1903
			array(
1904
				'ts' => strtotime('1919-12-21T18:20:09+0000'),
1905
				'tzid' => 'Asia/Novosibirsk',
1906
			),
1907
			array(
1908
				'ts' => strtotime('1930-06-20T18:00:00+0000'),
1909
				'tzid' => 'Asia/Novokuznetsk',
1910
			),
1911
			array(
1912
				'ts' => strtotime('2002-04-30T19:00:00+0000'),
1913
				'tzid' => 'Asia/Novosibirsk',
1914
			),
1915
			array(
1916
				'ts' => strtotime('2016-05-28T20:00:00+0000'),
1917
				'tzid' => 'Asia/Novokuznetsk',
1918
			),
1919
		),
1920
1921
		// Created in version 2016d.
1922
		'Europe/Kirov' => array(
1923
			array(
1924
				'ts' => PHP_INT_MIN,
1925
				'tzid' => '',
1926
			),
1927
			array(
1928
				'ts' => strtotime('1935-01-26T20:00:00+0000'),
1929
				'tzid' => 'Europe/Samara',
1930
			),
1931
			array(
1932
				'ts' => strtotime('1989-03-25T22:00:00+0000'),
1933
				'tzid' => 'Europe/Volgograd',
1934
			),
1935
			array(
1936
				'ts' => strtotime('1992-03-28T22:00:00+0000'),
1937
				'tzid' => 'W-SU',
1938
			),
1939
		),
1940
1941
		// Diverged from Asia/Nicosia in version 2016i.
1942
		'Asia/Famagusta' => array(
1943
			array(
1944
				'ts' => PHP_INT_MIN,
1945
				'tzid' => '',
1946
			),
1947
			// Europe/Nicosia is an otherwise unused link to Asia/Nicosia.
1948
			array(
1949
				'ts' => strtotime('1921-11-13T21:46:32+0000'),
1950
				'tzid' => 'Europe/Nicosia',
1951
			),
1952
			// Became same as Europe/Istanbul.
1953
			// Turkey is an otherwise unused link to Europe/Istanbul.
1954
			array(
1955
				'ts' => strtotime('2016-09-07T21:00:00+0000'),
1956
				'tzid' => 'Turkey',
1957
			),
1958
			// Became same as Asia/Nicosia again.
1959
			array(
1960
				'ts' => strtotime('2017-10-29T01:00:00+0000'),
1961
				'tzid' => 'Europe/Nicosia',
1962
			),
1963
		),
1964
1965
		// Created in version 2016j.
1966
		'Asia/Atyrau' => array(
1967
			array(
1968
				'ts' => PHP_INT_MIN,
1969
				'tzid' => '',
1970
			),
1971
			array(
1972
				'ts' => strtotime('1924-05-01T20:32:16+0000'),
1973
				'tzid' => 'Etc/GMT-3',
1974
			),
1975
			array(
1976
				'ts' => strtotime('1930-06-20T21:00:00+0000'),
1977
				'tzid' => 'Asia/Aqtau',
1978
			),
1979
			array(
1980
				'ts' => strtotime('1981-09-30T19:00:00+0000'),
1981
				'tzid' => 'Asia/Aqtobe',
1982
			),
1983
			array(
1984
				'tz' => strtotime('1999-03-27T21:00:00+0000'),
1985
				'tzid' => 'Asia/Aqtau'
1986
			),
1987
		),
1988
1989
		// Diverged from Europe/Volgograd in version 2016j.
1990
		'Europe/Saratov' => array(
1991
			array(
1992
				'ts' => PHP_INT_MIN,
1993
				'tzid' => '',
1994
			),
1995
			array(
1996
				'ts' => strtotime('1935-01-26T20:00:00+0000'),
1997
				'tzid' => 'Europe/Samara',
1998
			),
1999
			array(
2000
				'ts' => strtotime('1988-03-26T22:00:00+0000'),
2001
				'tzid' => 'Europe/Volgograd',
2002
			),
2003
			array(
2004
				'ts' => strtotime('2016-12-03T23:00:00+0000'),
2005
				'tzid' => 'Europe/Samara',
2006
			),
2007
		),
2008
2009
		// Diverged from America/Santiago in version 2017a.
2010
		'America/Punta_Arenas' => array(
2011
			array(
2012
				'ts' => PHP_INT_MIN,
2013
				'tzid' => '',
2014
			),
2015
			// Chile/Continental is an otherwise unused link to America/Santiago.
2016
			array(
2017
				'ts' => strtotime('1890-01-01T04:43:40+0000'),
2018
				'tzid' => 'Chile/Continental',
2019
			),
2020
			array(
2021
				'ts' => strtotime('1942-08-01T05:00:00+0000'),
2022
				'tzid' => 'Etc/GMT+4',
2023
			),
2024
			array(
2025
				'ts' => strtotime('1946-08-29T04:00:00+0000'),
2026
				'tzid' => 'Chile/Continental',
2027
			),
2028
			// America/Mendoza is an otherwise unused link to America/Argentina/Mendoza.
2029
			array(
2030
				'ts' => strtotime('2016-12-04T03:00:00+0000'),
2031
				'tzid' => 'America/Mendoza',
2032
			),
2033
		),
2034
2035
		// Diverged from Asia/Qyzylorda in version 2018h.
2036
		'Asia/Qostanay' => array(
2037
			array(
2038
				'ts' => PHP_INT_MIN,
2039
				'tzid' => '',
2040
			),
2041
			array(
2042
				'ts' => strtotime('1924-05-01T19:45:32+0000'),
2043
				'tzid' => 'Asia/Qyzylorda',
2044
			),
2045
			array(
2046
				'ts' => strtotime('1930-06-20T20:00:00+0000'),
2047
				'tzid' => 'Asia/Aqtobe',
2048
			),
2049
			array(
2050
				'ts' => strtotime('2004-10-30T21:00:00+0000'),
2051
				'tzid' => 'Asia/Almaty',
2052
			),
2053
		),
2054
2055
		// Diverged from America/Ojinaga in version 2022g.
2056
		'America/Ciudad_Juarez' => array(
2057
			array(
2058
				'ts' => PHP_INT_MIN,
2059
				'tzid' => '',
2060
			),
2061
			array(
2062
				'ts' => strtotime('1922-01-01T07:00:00+0000'),
2063
				'tzid' => 'America/Ojinaga',
2064
			),
2065
			array(
2066
				'ts' => strtotime('2022-11-30T06:00:00+0000'),
2067
				'tzid' => 'America/Denver',
2068
			),
2069
		),
2070
2071
		// Diverged from America/Santiago in version 2025b.
2072
		// From 2025-03-20 onward, becomes the same as America/Punta_Arenas.
2073
		'America/Coyhaique' => array(
2074
			array(
2075
				'ts' => PHP_INT_MIN,
2076
				'tzid' => '',
2077
			),
2078
			array(
2079
				'ts' => strtotime('1890-01-01T04:48:16+0000'),
2080
				// Chile/Continental is an otherwise unused link to America/Santiago.
2081
				'tzid' => 'Chile/Continental',
2082
			),
2083
			array(
2084
				'ts' => strtotime('1942-08-01T05:00:00+0000'),
2085
				'tzid' => 'Etc/GMT+4',
2086
			),
2087
			array(
2088
				'ts' => strtotime('1946-08-29T04:00:00+0000'),
2089
				'tzid' => 'Chile/Continental',
2090
			),
2091
			array(
2092
				'ts' => strtotime('2025-03-20T03:00:00+0000'),
2093
				// America/Mendoza is an otherwise unused link to America/Argentina/Mendoza.
2094
				'tzid' => 'America/Mendoza',
2095
			),
2096
		),
2097
	);
2098
2099
	$missing = array_diff($tzids, timezone_identifiers_list(DateTimeZone::ALL_WITH_BC));
2100
2101
	call_integration_hook('integrate_timezone_fallbacks', array(&$fallbacks, &$missing, $tzids, $when));
2102
2103
	$replacements = array();
2104
2105
	foreach ($tzids as $tzid)
2106
	{
2107
		// Not missing.
2108
		if (!in_array($tzid, $missing))
2109
			$replacements[$tzid] = $tzid;
2110
2111
		// Missing and we have no fallback.
2112
		elseif (empty($fallbacks[$tzid]))
2113
			$replacements[$tzid] = '';
2114
2115
		// Missing, but we have a fallback.
2116
		else
2117
		{
2118
			usort(
2119
				$fallbacks[$tzid],
2120
				function ($a, $b)
2121
				{
2122
					return $a['ts'] > $b['ts'];
2123
				}
2124
			);
2125
2126
			foreach ($fallbacks[$tzid] as $alt)
2127
			{
2128
				if ($when < $alt['ts'])
2129
					break;
2130
2131
				$replacements[$tzid] = $alt['tzid'];
2132
			}
2133
2134
			// Replacement is already in use.
2135
			if (in_array($alt['tzid'], $replacements) || (in_array($alt['tzid'], $tzids) && strpos($alt['tzid'], 'Etc/') === false))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $alt does not seem to be defined for all execution paths leading up to this point.
Loading history...
2136
				$replacements[$tzid] = '';
2137
2138
			if (empty($replacements[$tzid]))
2139
				$replacements[$tzid] = '';
2140
		}
2141
	}
2142
2143
	return $replacements;
2144
}
2145
2146
/**
2147
 * Validates a set of two-character ISO 3166-1 country codes.
2148
 *
2149
 * @param array|string $country_codes Array or CSV string of country codes.
2150
 * @param bool $as_csv If true, return CSV string instead of array.
2151
 * @return array|string Array or CSV string of valid country codes.
2152
 */
2153
function validate_iso_country_codes($country_codes, $as_csv = false)
2154
{
2155
	if (is_string($country_codes))
2156
		$country_codes = explode(',', $country_codes);
2157
	else
2158
		$country_codes = array_map('strval', (array) $country_codes);
2159
2160
	foreach ($country_codes as $key => $country_code)
2161
	{
2162
		$country_code = strtoupper(trim($country_code));
2163
		$country_tzids = strlen($country_code) !== 2 ? null : @timezone_identifiers_list(DateTimeZone::PER_COUNTRY, $country_code);
2164
		$country_codes[$key] = empty($country_tzids) ? null : $country_code;
2165
	}
2166
2167
	$country_codes = array_filter($country_codes);
2168
2169
	if (!empty($as_csv))
2170
		$country_codes = implode(',', $country_codes);
2171
2172
	return $country_codes;
2173
}
2174
2175
?>