Я придумал динамическое самообновляющееся решение, которое не требует никаких таблиц поиска ( select demo ):
function Timezones()
{
$result = array();
$timezones = array();
// only process geographical timezones
foreach (preg_grep('~^(?:A(?:frica|merica|ntarctica|rctic|tlantic|sia|ustralia)|Europe|Indian|Pacific)/~', timezone_identifiers_list()) as $timezone)
{
if (is_object($timezone = new DateTimeZone($timezone)) === true)
{
$id = array();
// get only the two most distant transitions
foreach (array_slice($timezone->getTransitions($_SERVER['REQUEST_TIME']), -2) as $transition)
{
// dark magic
$id[] = sprintf('%b|%+d|%u', $transition['isdst'], $transition['offset'], $transition['ts']);
}
if (count($id) > 1)
{
sort($id, SORT_NUMERIC); // sort by %b (isdst = 0) first, so that we always get the raw offset
}
$timezones[implode('|', $id)][] = $timezone->getName();
}
}
if ((is_array($timezones) === true) && (count($timezones) > 0))
{
uksort($timezones, function($a, $b) // sort offsets by -, 0, +
{
foreach (array('a', 'b') as $key)
{
$$key = explode('|', $$key);
}
return intval($a[1]) - intval($b[1]);
});
foreach ($timezones as $key => $value)
{
$zone = reset($value); // first timezone ID is our internal timezone
$result[$zone] = preg_replace(array('~^.*/([^/]+)$~', '~_~'), array('$1', ' '), $value); // "humanize" city names
if (array_key_exists(1, $offset = explode('|', $key)) === true) // "humanize" the offset
{
$offset = str_replace(' +00:00', '', sprintf('(UTC %+03d:%02u)', $offset[1] / 3600, abs($offset[1]) % 3600 / 60));
}
if (asort($result[$zone]) === true) // sort city names
{
$result[$zone] = trim(sprintf('%s %s', $offset, implode(', ', $result[$zone])));
}
}
}
return $result;
}
Существует множество часовых поясов, которые разделяют точныес теми же смещениями и временами перехода на летнее время (Europe/Dublin
, Europe/Lisbon
и Europe/London
и многие другие), мой алгоритм группирует эти зоны (используя специальную запись в ключах массива dst?|offset|timestamp
) в первом идентификаторе часового пояса этой группы иобъединяет гуманизированные преобразования последнего (обычно городского уровня) сегмента идентификатора часового пояса:
Array
(
[Pacific/Midway] => (UTC -11:00) Midway, Niue, Pago Pago
[America/Adak] => (UTC -10:00) Adak
[Pacific/Fakaofo] => (UTC -10:00) Fakaofo, Honolulu, Johnston, Rarotonga, Tahiti
[Pacific/Marquesas] => (UTC -10:30) Marquesas
[America/Anchorage] => (UTC -09:00) Anchorage, Juneau, Nome, Sitka, Yakutat
[Pacific/Gambier] => (UTC -09:00) Gambier
[America/Dawson] => (UTC -08:00) Dawson, Los Angeles, Tijuana, Vancouver, Whitehorse
[America/Santa_Isabel] => (UTC -08:00) Santa Isabel
[America/Metlakatla] => (UTC -08:00) Metlakatla, Pitcairn
[America/Dawson_Creek] => (UTC -07:00) Dawson Creek, Hermosillo, Phoenix
[America/Chihuahua] => (UTC -07:00) Chihuahua, Mazatlan
[America/Boise] => (UTC -07:00) Boise, Cambridge Bay, Denver, Edmonton, Inuvik, Ojinaga, Shiprock, Yellowknife
[America/Chicago] => (UTC -06:00) Beulah, Center, Chicago, Knox, Matamoros, Menominee, New Salem, Rainy River, Rankin Inlet, Resolute, Tell City, Winnipeg
[America/Belize] => (UTC -06:00) Belize, Costa Rica, El Salvador, Galapagos, Guatemala, Managua, Regina, Swift Current, Tegucigalpa
[Pacific/Easter] => (UTC -06:00) Easter
[America/Bahia_Banderas] => (UTC -06:00) Bahia Banderas, Cancun, Merida, Mexico City, Monterrey
[America/Detroit] => (UTC -05:00) Detroit, Grand Turk, Indianapolis, Iqaluit, Louisville, Marengo, Monticello, Montreal, Nassau, New York, Nipigon, Pangnirtung, Petersburg, Thunder Bay, Toronto, Vevay, Vincennes, Winamac
[America/Atikokan] => (UTC -05:00) Atikokan, Bogota, Cayman, Guayaquil, Jamaica, Lima, Panama, Port-au-Prince
[America/Havana] => (UTC -05:00) Havana
[America/Caracas] => (UTC -05:30) Caracas
[America/Glace_Bay] => (UTC -04:00) Bermuda, Glace Bay, Goose Bay, Halifax, Moncton, Thule
[Atlantic/Stanley] => (UTC -04:00) Stanley
[America/Santiago] => (UTC -04:00) Palmer, Santiago
[America/Anguilla] => (UTC -04:00) Anguilla, Antigua, Aruba, Barbados, Blanc-Sablon, Boa Vista, Curacao, Dominica, Eirunepe, Grenada, Guadeloupe, Guyana, Kralendijk, La Paz, Lower Princes, Manaus, Marigot, Martinique, Montserrat, Port of Spain, Porto Velho, Puerto Rico, Rio Branco, Santo Domingo, St Barthelemy, St Kitts, St Lucia, St Thomas, St Vincent, Tortola
[America/Campo_Grande] => (UTC -04:00) Campo Grande, Cuiaba
[America/Asuncion] => (UTC -04:00) Asuncion
[America/St_Johns] => (UTC -04:30) St Johns
[America/Sao_Paulo] => (UTC -03:00) Sao Paulo
[America/Araguaina] => (UTC -03:00) Araguaina, Bahia, Belem, Buenos Aires, Catamarca, Cayenne, Cordoba, Fortaleza, Jujuy, La Rioja, Maceio, Mendoza, Paramaribo, Recife, Rio Gallegos, Rothera, Salta, San Juan, Santarem, Tucuman, Ushuaia
[America/Montevideo] => (UTC -03:00) Montevideo
[America/Godthab] => (UTC -03:00) Godthab
[America/Argentina/San_Luis] => (UTC -03:00) San Luis
[America/Miquelon] => (UTC -03:00) Miquelon
[America/Noronha] => (UTC -02:00) Noronha, South Georgia
[Atlantic/Cape_Verde] => (UTC -01:00) Cape Verde
[America/Scoresbysund] => (UTC -01:00) Azores, Scoresbysund
[Atlantic/Canary] => (UTC) Canary, Dublin, Faroe, Guernsey, Isle of Man, Jersey, Lisbon, London, Madeira
[Africa/Abidjan] => (UTC) Abidjan, Accra, Bamako, Banjul, Bissau, Casablanca, Conakry, Dakar, Danmarkshavn, El Aaiun, Freetown, Lome, Monrovia, Nouakchott, Ouagadougou, Reykjavik, Sao Tome, St Helena
[Africa/Algiers] => (UTC +01:00) Algiers, Bangui, Brazzaville, Douala, Kinshasa, Lagos, Libreville, Luanda, Malabo, Ndjamena, Niamey, Porto-Novo, Tunis
[Africa/Ceuta] => (UTC +01:00) Amsterdam, Andorra, Belgrade, Berlin, Bratislava, Brussels, Budapest, Ceuta, Copenhagen, Gibraltar, Ljubljana, Longyearbyen, Luxembourg, Madrid, Malta, Monaco, Oslo, Paris, Podgorica, Prague, Rome, San Marino, Sarajevo, Skopje, Stockholm, Tirane, Vaduz, Vatican, Vienna, Warsaw, Zagreb, Zurich
[Africa/Windhoek] => (UTC +01:00) Windhoek
[Asia/Damascus] => (UTC +02:00) Damascus
[Asia/Beirut] => (UTC +02:00) Beirut
[Asia/Jerusalem] => (UTC +02:00) Jerusalem
[Asia/Nicosia] => (UTC +02:00) Athens, Bucharest, Chisinau, Helsinki, Istanbul, Mariehamn, Nicosia, Riga, Sofia, Tallinn, Vilnius
[Africa/Blantyre] => (UTC +02:00) Blantyre, Bujumbura, Cairo, Gaborone, Gaza, Harare, Hebron, Johannesburg, Kigali, Lubumbashi, Lusaka, Maputo, Maseru, Mbabane, Tripoli
[Asia/Amman] => (UTC +02:00) Amman
[Africa/Addis_Ababa] => (UTC +03:00) Addis Ababa, Aden, Antananarivo, Asmara, Baghdad, Bahrain, Comoro, Dar es Salaam, Djibouti, Juba, Kaliningrad, Kampala, Khartoum, Kiev, Kuwait, Mayotte, Minsk, Mogadishu, Nairobi, Qatar, Riyadh, Simferopol, Syowa, Uzhgorod, Zaporozhye
[Asia/Tehran] => (UTC +03:30) Tehran
[Asia/Yerevan] => (UTC +04:00) Yerevan
[Asia/Dubai] => (UTC +04:00) Dubai, Mahe, Mauritius, Moscow, Muscat, Reunion, Samara, Tbilisi, Volgograd
[Asia/Baku] => (UTC +04:00) Baku
[Asia/Kabul] => (UTC +04:30) Kabul
[Antarctica/Mawson] => (UTC +05:00) Aqtau, Aqtobe, Ashgabat, Dushanbe, Karachi, Kerguelen, Maldives, Mawson, Oral, Samarkand, Tashkent
[Asia/Colombo] => (UTC +05:30) Colombo, Kolkata
[Asia/Kathmandu] => (UTC +05:45) Kathmandu
[Antarctica/Vostok] => (UTC +06:00) Almaty, Bishkek, Chagos, Dhaka, Qyzylorda, Thimphu, Vostok, Yekaterinburg
[Asia/Rangoon] => (UTC +06:30) Cocos, Rangoon
[Antarctica/Davis] => (UTC +07:00) Bangkok, Christmas, Davis, Ho Chi Minh, Hovd, Jakarta, Novokuznetsk, Novosibirsk, Omsk, Phnom Penh, Pontianak, Vientiane
[Antarctica/Casey] => (UTC +08:00) Brunei, Casey, Choibalsan, Chongqing, Harbin, Hong Kong, Kashgar, Krasnoyarsk, Kuala Lumpur, Kuching, Macau, Makassar, Manila, Perth, Shanghai, Singapore, Taipei, Ulaanbaatar, Urumqi
[Australia/Eucla] => (UTC +08:45) Eucla
[Asia/Dili] => (UTC +09:00) Dili, Irkutsk, Jayapura, Palau, Pyongyang, Seoul, Tokyo
[Australia/Adelaide] => (UTC +09:30) Adelaide, Broken Hill
[Australia/Darwin] => (UTC +09:30) Darwin
[Antarctica/DumontDUrville] => (UTC +10:00) Brisbane, Chuuk, DumontDUrville, Guam, Lindeman, Port Moresby, Saipan, Yakutsk
[Australia/Currie] => (UTC +10:00) Currie, Hobart, Melbourne, Sydney
[Australia/Lord_Howe] => (UTC +10:30) Lord Howe
[Antarctica/Macquarie] => (UTC +11:00) Efate, Guadalcanal, Kosrae, Macquarie, Noumea, Pohnpei, Sakhalin, Vladivostok
[Pacific/Norfolk] => (UTC +11:30) Norfolk
[Antarctica/McMurdo] => (UTC +12:00) Auckland, McMurdo, South Pole
[Asia/Anadyr] => (UTC +12:00) Anadyr, Fiji, Funafuti, Kamchatka, Kwajalein, Magadan, Majuro, Nauru, Tarawa, Wake, Wallis
[Pacific/Chatham] => (UTC +12:45) Chatham
[Pacific/Enderbury] => (UTC +13:00) Enderbury, Tongatapu
[Pacific/Apia] => (UTC +13:00) Apia
[Pacific/Kiritimati] => (UTC +14:00) Kiritimati
)
Конечно, конкатенация города все еще довольно длинная, но список уникальных (фактических) часовых поясов сократился с 414 (или 415, если мы рассматриваем негеографический UTC) до 75 - что является довольно хорошим IMO и, кажется, отражает список «нормализованных» часовых поясов, который Windows использует (также 75).
У этого автоматического подхода есть две большие проблемы:
- выбранный идентификатор часового пояса для группы городов является первым в алфавитном порядке, это означает, что для (UTC) Канарские, Дублинские, Фарерские, Гернси, Остров Мэн, Джерси, Лиссабон, Лондон, Мадейра значение часового пояса будет
Atlantic/Canary
- хотя в этом не должно быть ничего плохого, оно будетболее разумно выбрать идентификатор часового пояса, связанный с большим городом (например, Europe/London
) - объединение городов, безусловно, является самой большой проблемой, их слишком много - один из способов решить эту проблемуиспользовать
array_slice($cities, 0, $maxCities)
перед взрывом, но это не будет учитывать размер города, и для предела 4 Канарские острова, Дублин, Фарерские острова, Гернси, Остров Мэн, Джерси, Лиссабон, Лондон, Мадейра станет Канарскими, Дублинскими, Фарерскими, Гернси вместо более логичного Windows-эквивалента Дублин, Эдинбург, Лиссабон, Лондон .
Это не должно быть очень полезно, но я подумал, что поделюсь - возможно, кто-то еще может улучшить его.