Created
January 28, 2020 20:52
-
-
Save heathdutton/2e229b53ff7d7b737de686056e60eb66 to your computer and use it in GitHub Desktop.
Dynamic collection of supported PHP Timezones. Grouped by country, with dynamic abbreviations, DST indicators, and future-proofing. For making useful and clean select2 (or similar) dropdown selectors. Better than all the hard-coded solutions I could find.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Class TimeZoneOptionCollection | |
* | |
* Dynamic collection of supported PHP Timezones: | |
* - Grouped by country | |
* - Commonly used abbreviations | |
* - DST indicators | |
* - Future-proof (leans on PHP for timezones). | |
* | |
* Laravel example: {{ Form::select('timezone', (new TimeZoneOptionCollection())->toArray(), 'America/New_York', ['class' => 'select2']) }} | |
* | |
*/ | |
class TimeZoneOptionCollection | |
{ | |
/** | |
* ISO 3166-2 Official updated 2020 | |
* https://datahub.io/core/country-list/r/data.csv | |
* | |
* An even more future-proof version would fetch/cache these, but this class is meant to be self-sufficient. | |
* If a country is not supported, timezones will still be listed and available for selection. | |
* | |
* @var array | |
*/ | |
protected static $countries = [ | |
'Afghanistan' => 'AF', | |
'Åland Islands' => 'AX', | |
'Albania' => 'AL', | |
'Algeria' => 'DZ', | |
'American Samoa' => 'AS', | |
'Andorra' => 'AD', | |
'Angola' => 'AO', | |
'Anguilla' => 'AI', | |
'Antarctica' => 'AQ', | |
'Antigua and Barbuda' => 'AG', | |
'Argentina' => 'AR', | |
'Armenia' => 'AM', | |
'Aruba' => 'AW', | |
'Australia' => 'AU', | |
'Austria' => 'AT', | |
'Azerbaijan' => 'AZ', | |
'Bahamas' => 'BS', | |
'Bahrain' => 'BH', | |
'Bangladesh' => 'BD', | |
'Barbados' => 'BB', | |
'Belarus' => 'BY', | |
'Belgium' => 'BE', | |
'Belize' => 'BZ', | |
'Benin' => 'BJ', | |
'Bermuda' => 'BM', | |
'Bhutan' => 'BT', | |
'Bolivia, Plurinational State of' => 'BO', | |
'Bonaire, Sint Eustatius and Saba' => 'BQ', | |
'Bosnia and Herzegovina' => 'BA', | |
'Botswana' => 'BW', | |
'Bouvet Island' => 'BV', | |
'Brazil' => 'BR', | |
'British Indian Ocean Territory' => 'IO', | |
'Brunei Darussalam' => 'BN', | |
'Bulgaria' => 'BG', | |
'Burkina Faso' => 'BF', | |
'Burundi' => 'BI', | |
'Cambodia' => 'KH', | |
'Cameroon' => 'CM', | |
'Canada' => 'CA', | |
'Cape Verde' => 'CV', | |
'Cayman Islands' => 'KY', | |
'Central African Republic' => 'CF', | |
'Chad' => 'TD', | |
'Chile' => 'CL', | |
'China' => 'CN', | |
'Christmas Island' => 'CX', | |
'Cocos (Keeling) Islands' => 'CC', | |
'Colombia' => 'CO', | |
'Comoros' => 'KM', | |
'Congo' => 'CG', | |
'Congo, the Democratic Republic of the' => 'CD', | |
'Cook Islands' => 'CK', | |
'Costa Rica' => 'CR', | |
'Croatia' => 'HR', | |
'Cuba' => 'CU', | |
'Curaçao' => 'CW', | |
'Cyprus' => 'CY', | |
'Czech Republic' => 'CZ', | |
'Côte d\'Ivoire' => 'CI', | |
'Denmark' => 'DK', | |
'Djibouti' => 'DJ', | |
'Dominica' => 'DM', | |
'Dominican Republic' => 'DO', | |
'Ecuador' => 'EC', | |
'Egypt' => 'EG', | |
'El Salvador' => 'SV', | |
'Equatorial Guinea' => 'GQ', | |
'Eritrea' => 'ER', | |
'Estonia' => 'EE', | |
'Ethiopia' => 'ET', | |
'Falkland Islands (Malvinas)' => 'FK', | |
'Faroe Islands' => 'FO', | |
'Fiji' => 'FJ', | |
'Finland' => 'FI', | |
'France' => 'FR', | |
'French Guiana' => 'GF', | |
'French Polynesia' => 'PF', | |
'French Southern Territories' => 'TF', | |
'Gabon' => 'GA', | |
'Gambia' => 'GM', | |
'Georgia' => 'GE', | |
'Germany' => 'DE', | |
'Ghana' => 'GH', | |
'Gibraltar' => 'GI', | |
'Greece' => 'GR', | |
'Greenland' => 'GL', | |
'Grenada' => 'GD', | |
'Guadeloupe' => 'GP', | |
'Guam' => 'GU', | |
'Guatemala' => 'GT', | |
'Guernsey' => 'GG', | |
'Guinea' => 'GN', | |
'Guinea-Bissau' => 'GW', | |
'Guyana' => 'GY', | |
'Haiti' => 'HT', | |
'Heard Island and McDonald Islands' => 'HM', | |
'Holy See (Vatican City State)' => 'VA', | |
'Honduras' => 'HN', | |
'Hong Kong' => 'HK', | |
'Hungary' => 'HU', | |
'Iceland' => 'IS', | |
'India' => 'IN', | |
'Indonesia' => 'ID', | |
'Iran, Islamic Republic of' => 'IR', | |
'Iraq' => 'IQ', | |
'Ireland' => 'IE', | |
'Isle of Man' => 'IM', | |
'Israel' => 'IL', | |
'Italy' => 'IT', | |
'Jamaica' => 'JM', | |
'Japan' => 'JP', | |
'Jersey' => 'JE', | |
'Jordan' => 'JO', | |
'Kazakhstan' => 'KZ', | |
'Kenya' => 'KE', | |
'Kiribati' => 'KI', | |
'Korea, Democratic People\'s Republic of' => 'KP', | |
'Korea, Republic of' => 'KR', | |
'Kuwait' => 'KW', | |
'Kyrgyzstan' => 'KG', | |
'Lao People\'s Democratic Republic' => 'LA', | |
'Latvia' => 'LV', | |
'Lebanon' => 'LB', | |
'Lesotho' => 'LS', | |
'Liberia' => 'LR', | |
'Libya' => 'LY', | |
'Liechtenstein' => 'LI', | |
'Lithuania' => 'LT', | |
'Luxembourg' => 'LU', | |
'Macao' => 'MO', | |
'Macedonia, the Former Yugoslav Republic of' => 'MK', | |
'Madagascar' => 'MG', | |
'Malawi' => 'MW', | |
'Malaysia' => 'MY', | |
'Maldives' => 'MV', | |
'Mali' => 'ML', | |
'Malta' => 'MT', | |
'Marshall Islands' => 'MH', | |
'Martinique' => 'MQ', | |
'Mauritania' => 'MR', | |
'Mauritius' => 'MU', | |
'Mayotte' => 'YT', | |
'Mexico' => 'MX', | |
'Micronesia, Federated States of' => 'FM', | |
'Moldova, Republic of' => 'MD', | |
'Monaco' => 'MC', | |
'Mongolia' => 'MN', | |
'Montenegro' => 'ME', | |
'Montserrat' => 'MS', | |
'Morocco' => 'MA', | |
'Mozambique' => 'MZ', | |
'Myanmar' => 'MM', | |
'Namibia' => 'NA', | |
'Nauru' => 'NR', | |
'Nepal' => 'NP', | |
'Netherlands' => 'NL', | |
'New Caledonia' => 'NC', | |
'New Zealand' => 'NZ', | |
'Nicaragua' => 'NI', | |
'Niger' => 'NE', | |
'Nigeria' => 'NG', | |
'Niue' => 'NU', | |
'Norfolk Island' => 'NF', | |
'Northern Mariana Islands' => 'MP', | |
'Norway' => 'NO', | |
'Oman' => 'OM', | |
'Pakistan' => 'PK', | |
'Palau' => 'PW', | |
'Palestine, State of' => 'PS', | |
'Panama' => 'PA', | |
'Papua New Guinea' => 'PG', | |
'Paraguay' => 'PY', | |
'Peru' => 'PE', | |
'Philippines' => 'PH', | |
'Pitcairn' => 'PN', | |
'Poland' => 'PL', | |
'Portugal' => 'PT', | |
'Puerto Rico' => 'PR', | |
'Qatar' => 'QA', | |
'Romania' => 'RO', | |
'Russian Federation' => 'RU', | |
'Rwanda' => 'RW', | |
'Réunion' => 'RE', | |
'Saint Barthélemy' => 'BL', | |
'Saint Helena, Ascension and Tristan da Cunha' => 'SH', | |
'Saint Kitts and Nevis' => 'KN', | |
'Saint Lucia' => 'LC', | |
'Saint Martin (French part)' => 'MF', | |
'Saint Pierre and Miquelon' => 'PM', | |
'Saint Vincent and the Grenadines' => 'VC', | |
'Samoa' => 'WS', | |
'San Marino' => 'SM', | |
'Sao Tome and Principe' => 'ST', | |
'Saudi Arabia' => 'SA', | |
'Senegal' => 'SN', | |
'Serbia' => 'RS', | |
'Seychelles' => 'SC', | |
'Sierra Leone' => 'SL', | |
'Singapore' => 'SG', | |
'Sint Maarten (Dutch part)' => 'SX', | |
'Slovakia' => 'SK', | |
'Slovenia' => 'SI', | |
'Solomon Islands' => 'SB', | |
'Somalia' => 'SO', | |
'South Africa' => 'ZA', | |
'South Georgia and the South Sandwich Islands' => 'GS', | |
'South Sudan' => 'SS', | |
'Spain' => 'ES', | |
'Sri Lanka' => 'LK', | |
'Sudan' => 'SD', | |
'Suriname' => 'SR', | |
'Svalbard and Jan Mayen' => 'SJ', | |
'Swaziland' => 'SZ', | |
'Sweden' => 'SE', | |
'Switzerland' => 'CH', | |
'Syrian Arab Republic' => 'SY', | |
'Taiwan, Province of China' => 'TW', | |
'Tajikistan' => 'TJ', | |
'Tanzania, United Republic of' => 'TZ', | |
'Thailand' => 'TH', | |
'Timor-Leste' => 'TL', | |
'Togo' => 'TG', | |
'Tokelau' => 'TK', | |
'Tonga' => 'TO', | |
'Trinidad and Tobago' => 'TT', | |
'Tunisia' => 'TN', | |
'Turkey' => 'TR', | |
'Turkmenistan' => 'TM', | |
'Turks and Caicos Islands' => 'TC', | |
'Tuvalu' => 'TV', | |
'Uganda' => 'UG', | |
'Ukraine' => 'UA', | |
'United Arab Emirates' => 'AE', | |
'United Kingdom' => 'GB', | |
'United States Minor Outlying Islands' => 'UM', | |
'United States' => 'US', | |
'Uruguay' => 'UY', | |
'Uzbekistan' => 'UZ', | |
'Vanuatu' => 'VU', | |
'Venezuela, Bolivarian Republic of' => 'VE', | |
'Viet Nam' => 'VN', | |
'Virgin Islands, British' => 'VG', | |
'Virgin Islands, U.S.' => 'VI', | |
'Wallis and Futuna' => 'WF', | |
'Western Sahara' => 'EH', | |
'Yemen' => 'YE', | |
'Zambia' => 'ZM', | |
'Zimbabwe' => 'ZW', | |
]; | |
/** | |
* The items contained in the collection. | |
* | |
* @var array | |
*/ | |
protected $items = []; | |
/** | |
* TimeZoneOptionCollection constructor. | |
* | |
* @param array $items | |
* | |
* @throws Exception | |
*/ | |
public function __construct($items = []) | |
{ | |
if ($items) { | |
$this->items = $items; | |
} else { | |
$unmapped = array_flip(DateTimeZone::listIdentifiers()); | |
foreach (self::$countries as $country => $iso) { | |
$identifiers = \DateTimeZone::listIdentifiers(\DateTimeZone::PER_COUNTRY, $iso); | |
if ($identifiers) { | |
$country = ucfirst(implode(' ', array_reverse(explode(', ', $country)))); | |
$items[$country] = []; | |
foreach ($identifiers as $identifier) { | |
$items[$country][$identifier] = self::identifierToString($identifier); | |
unset($unmapped[$identifier]); | |
} | |
self::ksort($items); | |
} | |
} | |
// Include any unmapped identifiers that do not have a country (future-proofing) | |
// UTC is not a timezone, but is expected as an option here. | |
foreach ($unmapped as $identifier => $int) { | |
$items[$identifier] = self::identifierToString($identifier); | |
} | |
self::ksort($items); | |
$this->items = $items; | |
} | |
} | |
/** | |
* @param $identifier | |
* | |
* @return string | |
* @throws Exception | |
*/ | |
private static function identifierToString($identifier) | |
{ | |
$abbrArr = []; | |
$time = new \DateTime(null, new \DateTimeZone($identifier)); | |
$abbr = $time->format('T'); | |
$identArr = explode('/', str_replace('_', ' ', $identifier)); | |
$city = end($identArr); | |
$region = reset($identArr); | |
$areaArr = array_diff($identArr, [$city, $region]); | |
// Include region when specified. | |
if ($areaArr) { | |
$city = $city.', '.reset($areaArr); | |
} | |
// Append common acronym abbreviation if applicable. | |
if ($abbr && !is_numeric($abbr) && $abbr !== $identifier) { | |
$abbrArr[] = $abbr; | |
} | |
// Append Daylight Savings Time indicator. | |
if ($time->format('I')) { | |
$abbrArr[] = 'DST'; | |
} | |
// Include current GMT offset in simplified notation while still allowing partial-hour timezones. | |
$abbrArr[] = 'GMT'.str_replace([':00', '-0', '+0', '+0'], ['', '-', '+', ''], $time->format('P')); | |
return $city.' ('.implode('/', $abbrArr).')'; | |
} | |
/** | |
* @param $array | |
* @param string $in | |
* @param string $out | |
* | |
* @return bool | |
*/ | |
private static function ksort($array, $in = 'UTF-8', $out = 'ASCII//TRANSLIT//IGNORE') | |
{ | |
return uksort($array, function ($a, $b) use ($in, $out) { | |
$a = @iconv($in, $out, $a); | |
$b = @iconv($in, $out, $b); | |
return strnatcasecmp($a, $b); | |
}); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function get($key) | |
{ | |
if (null === $key) { | |
return null; | |
} | |
return array_get($this->items, $key); | |
} | |
/** | |
* @param array|string $keys | |
* | |
* @return array | |
*/ | |
public function except($keys) | |
{ | |
$keys = is_array($keys) ? $keys : func_get_args(); | |
return array_except($this->items, $keys); | |
} | |
/** | |
* @param array|string $keys | |
* | |
* @return array | |
*/ | |
public function only($keys) | |
{ | |
$keys = is_array($keys) ? $keys : func_get_args(); | |
return array_only($this->items, $keys); | |
} | |
/** | |
* @return array | |
*/ | |
public function keys() | |
{ | |
return array_keys($this->items); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function toArray() | |
{ | |
return $this->items; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment