Как проверить, является ли идентификатор часового пояса действительным из кода?

Я попытаюсь объяснить, в чем проблема.

Согласно списку поддерживаемых часовых поясов из руководства PHP, я могу видеть все допустимые идентификаторы TZ в PHP.

Мой первый вопрос – как получить этот список из кода, но это не то, что мне действительно нужно.

Моя конечная цель – написать функцию isValidTimezoneId() которая возвращает TRUE, если часовой пояс действителен, в противном случае он должен возвращать FALSE .

 function isValidTimezoneId($timezoneId) { # ...function body... return ?; # TRUE or FALSE } 

Итак, когда я передаю идентификатор TZ, используя $timezoneId (string) в функции, мне нужен логический результат.

Ну, что я до сих пор …

1) Решение с использованием оператора @

Первое, что у меня есть, это что-то вроде этого:

 function isValidTimezoneId($timezoneId) { $savedZone = date_default_timezone_get(); # save current zone $res = $savedZone == $timezoneId; # it's TRUE if param matches current zone if (!$res) { # 0r... @date_default_timezone_set($timezoneId); # try to set new timezone $res = date_default_timezone_get() == $timezoneId; # it's true if new timezone set matches param string. } date_default_timezone_set($savedZone); # restore back old timezone return $res; # set result } 

Это работает отлично, но я хочу другое решение (чтобы избежать попытки установить неправильный часовой пояс)

2) Решение с использованием timezone_identifiers_list ()

Затем я пытался получить список допустимых идентификаторов часовых поясов и проверить его против параметра, используя функцию in_array () . Поэтому я попытался использовать timezone_identifiers_list () , но это было не так хорошо, потому что в массиве, возвращенном этой функцией (псевдоним DateTimeZone :: listIdentifiers () ) отсутствовало много таймшонов . На первый взгляд это было именно то, что я искал.

 function isValidTimezoneId($timezoneId) { $zoneList = timezone_identifiers_list(); # list of (all) valid timezones return in_array($timezoneId, $zoneList); # set result } 

Этот код выглядит приятным и легким, но я обнаружил, что массив $zoneList содержит ~ 400 элементов. По моим расчетам, он должен вернуть 550+ элементов. 150+ элементов отсутствуют … Так что это недостаточно хорошо для решения моей проблемы.

3) Решение на основе DateTimeZone :: listAbbreviations ()

Это последний шаг на пути к поиску идеального решения. Используя массив, возвращаемый этим методом, я могу извлечь все идентификаторы часовых поясов, поддерживаемые PHP.

 function createTZlist() { $tza = DateTimeZone::listAbbreviations(); $tzlist = array(); foreach ($tza as $zone) foreach ($zone as $item) if (is_string($item['timezone_id']) && $item['timezone_id'] != '') $tzlist[] = $item['timezone_id']; $tzlist = array_unique($tzlist); asort($tzlist); return array_values($tzlist); } 

Эта функция возвращает 563 элемента (в Example #2 у меня всего 407 ).

Я попытался найти различия между этими двумя массивами:

 $a1 = timezone_identifiers_list(); $a2 = createTZlist(); print_r(array_values(array_diff($a2, $a1))); 

Результат:

 Array ( [0] => Africa/Asmera [1] => Africa/Timbuktu [2] => America/Argentina/ComodRivadavia [3] => America/Atka [4] => America/Buenos_Aires [5] => America/Catamarca [6] => America/Coral_Harbour [7] => America/Cordoba [8] => America/Ensenada [9] => America/Fort_Wayne [10] => America/Indianapolis [11] => America/Jujuy [12] => America/Knox_IN [13] => America/Louisville [14] => America/Mendoza [15] => America/Porto_Acre [16] => America/Rosario [17] => America/Virgin [18] => Asia/Ashkhabad [19] => Asia/Calcutta [20] => Asia/Chungking [21] => Asia/Dacca [22] => Asia/Istanbul [23] => Asia/Katmandu [24] => Asia/Macao [25] => Asia/Saigon [26] => Asia/Tel_Aviv [27] => Asia/Thimbu [28] => Asia/Ujung_Pandang [29] => Asia/Ulan_Bator [30] => Atlantic/Faeroe [31] => Atlantic/Jan_Mayen [32] => Australia/ACT [33] => Australia/Canberra [34] => Australia/LHI [35] => Australia/NSW [36] => Australia/North [37] => Australia/Queensland [38] => Australia/South [39] => Australia/Tasmania [40] => Australia/Victoria [41] => Australia/West [42] => Australia/Yancowinna [43] => Brazil/Acre [44] => Brazil/DeNoronha [45] => Brazil/East [46] => Brazil/West [47] => CET [48] => CST6CDT [49] => Canada/Atlantic [50] => Canada/Central [51] => Canada/East-Saskatchewan [52] => Canada/Eastern [53] => Canada/Mountain [54] => Canada/Newfoundland [55] => Canada/Pacific [56] => Canada/Saskatchewan [57] => Canada/Yukon [58] => Chile/Continental [59] => Chile/EasterIsland [60] => Cuba [61] => EET [62] => EST [63] => EST5EDT [64] => Egypt [65] => Eire [66] => Etc/GMT [67] => Etc/GMT+0 [68] => Etc/GMT+1 [69] => Etc/GMT+10 [70] => Etc/GMT+11 [71] => Etc/GMT+12 [72] => Etc/GMT+2 [73] => Etc/GMT+3 [74] => Etc/GMT+4 [75] => Etc/GMT+5 [76] => Etc/GMT+6 [77] => Etc/GMT+7 [78] => Etc/GMT+8 [79] => Etc/GMT+9 [80] => Etc/GMT-0 [81] => Etc/GMT-1 [82] => Etc/GMT-10 [83] => Etc/GMT-11 [84] => Etc/GMT-12 [85] => Etc/GMT-13 [86] => Etc/GMT-14 [87] => Etc/GMT-2 [88] => Etc/GMT-3 [89] => Etc/GMT-4 [90] => Etc/GMT-5 [91] => Etc/GMT-6 [92] => Etc/GMT-7 [93] => Etc/GMT-8 [94] => Etc/GMT-9 [95] => Etc/GMT0 [96] => Etc/Greenwich [97] => Etc/UCT [98] => Etc/UTC [99] => Etc/Universal [100] => Etc/Zulu [101] => Europe/Belfast [102] => Europe/Nicosia [103] => Europe/Tiraspol [104] => Factory [105] => GB [106] => GB-Eire [107] => GMT [108] => GMT+0 [109] => GMT-0 [110] => GMT0 [111] => Greenwich [112] => HST [113] => Hongkong [114] => Iceland [115] => Iran [116] => Israel [117] => Jamaica [118] => Japan [119] => Kwajalein [120] => Libya [121] => MET [122] => MST [123] => MST7MDT [124] => Mexico/BajaNorte [125] => Mexico/BajaSur [126] => Mexico/General [127] => NZ [128] => NZ-CHAT [129] => Navajo [130] => PRC [131] => PST8PDT [132] => Pacific/Ponape [133] => Pacific/Samoa [134] => Pacific/Truk [135] => Pacific/Yap [136] => Poland [137] => Portugal [138] => ROC [139] => ROK [140] => Singapore [141] => Turkey [142] => UCT [143] => US/Alaska [144] => US/Aleutian [145] => US/Arizona [146] => US/Central [147] => US/East-Indiana [148] => US/Eastern [149] => US/Hawaii [150] => US/Indiana-Starke [151] => US/Michigan [152] => US/Mountain [153] => US/Pacific [154] => US/Pacific-New [155] => US/Samoa [156] => Universal [157] => W-SU [158] => WET [159] => Zulu ) 

Этот список содержит все допустимые идентификаторы TZ, которые не соответствовали Example #2 .

Есть еще четыре идентификатора TZ (часть $a1 ):

 print_r(array_values(array_diff($a1, $a2))); 

Вывод

 Array ( [0] => America/Bahia_Banderas [1] => Antarctica/Macquarie [2] => Pacific/Chuuk [3] => Pacific/Pohnpei ) 

Итак, теперь у меня почти идеальное решение …

 function isValidTimezoneId($timezoneId) { $zoneList = createTZlist(); # list of all valid timezones (last 4 are not included) return in_array($timezoneId, $zoneList); # set result } 

Это мое решение, и я могу его использовать. Конечно, я использую эту функцию как часть класса, поэтому мне не нужно генерировать $zoneList при каждом вызове методов.

Что мне действительно нужно здесь?

Мне интересно, есть ли более легкое (более быстрое) решение получить список всех допустимых идентификаторов часовых DateTimeZone::listAbbreviations() виде массива (я хочу, чтобы избежать извлечения этого списка из DateTimeZone::listAbbreviations() если это возможно)? Или, если вы знаете другой способ проверки параметра часового пояса , пожалуйста, дайте мне знать (повторяю, оператор @ не может быть частью решения).


PS Если вам нужно больше деталей и примеров, дайте мне знать. Наверное, нет.

Я использую PHP 5.3.5 (думаю, это не важно).


Обновить

Любая часть кода, которая выдает исключение из недопустимой строки часового пояса (скрытая с помощью @ или пойманная с try..catch блока try..catch ), не является решением, которое я ищу.


Еще одно обновление

Я поставил небольшую щедрость на этот вопрос!

Теперь я ищу самый простой способ извлечь список всех идентификаторов часовых поясов в массиве PHP.

Ваше решение работает отлично, поэтому, если вы быстро ищете, я бы посмотрел более внимательно на то, что вы делаете с вашими массивами. Я приурочил несколько тысяч проб, чтобы получить разумное среднее время, и это результаты:

 createTZlist : 20,713 microseconds per run createTZlist2 : 13,848 microseconds per run 

Вот более быстрая функция:

 function createTZList2() { $out = array(); $tza = timezone_abbreviations_list(); foreach ($tza as $zone) foreach ($zone as $item) $out[$item['timezone_id']] = 1; unset($out['']); ksort($out); return array_keys($out); } с function createTZList2() { $out = array(); $tza = timezone_abbreviations_list(); foreach ($tza as $zone) foreach ($zone as $item) $out[$item['timezone_id']] = 1; unset($out['']); ksort($out); return array_keys($out); } 

Тест if выполняется быстрее, если вы уменьшите его до значения if ($item['timezone_id']) , но вместо того, чтобы запускать его 489 раз, чтобы поймать один случай, быстрее удалить пустой ключ.

Установка хэш-ключей позволяет нам пропускать вызов array_unique() который стоит дороже. Сортировка ключей, а затем их извлечение на порядок быстрее, чем извлечение их, а затем сортировка извлеченного списка.

Если вы отбрасываете сортировку (которая не нужна, если вы не сравниваете список), она уменьшается до 12 339 микросекунд.

Но на самом деле вам не нужно возвращать ключи в любом случае. Глядя на целостный isValidTimezoneId() , вам было бы лучше:

 function isValidTimezoneId2($tzid){ $valid = array(); $tza = timezone_abbreviations_list(); foreach ($tza as $zone) foreach ($zone as $item) $valid[$item['timezone_id']] = true; unset($valid['']); return !!$valid[$tzid]; } с function isValidTimezoneId2($tzid){ $valid = array(); $tza = timezone_abbreviations_list(); foreach ($tza as $zone) foreach ($zone as $item) $valid[$item['timezone_id']] = true; unset($valid['']); return !!$valid[$tzid]; } 

То есть, предполагая, что вам нужно только один раз выполнить проверку на выполнение, в противном случае вы хотите сохранить $valid после первого запуска. Такой подход позволяет избежать необходимости сортировать или преобразовывать ключи в значения, ключевые поисковые запросы быстрее, чем in_array (), и дополнительный вызов функции отсутствует. Установка значений массива в true вместо 1 также удаляет одиночный приведение, когда результат верен.

Это обеспечивает надежность до менее 12 мс на моей тестовой машине, почти в половине случаев вашего примера. Веселый эксперимент по микрооптимизации!

Почему бы не использовать оператор @? Этот код работает очень хорошо, и вы не меняете часовой пояс по умолчанию:

 function isValidTimezoneId($timezoneId) { @$tz=timezone_open($timezoneId); return $tz!==FALSE; } 

Если вы не хотите @, вы можете сделать:

 function isValidTimezoneId($timezoneId) { try{ new DateTimeZone($timezoneId); }catch(Exception $e){ return FALSE; } return TRUE; } 

Когда я попробовал это на Linux-системе под управлением 5.3.6, ваш пример №2 дал мне 411 зон, а пример №3 дал 496. Следующая незначительная модификация в примере # 2 дает мне 591:

 $zoneList = timezone_identifiers_list(DateTimeZone::ALL_WITH_BC); 

Нет зон, возвращаемых в примере # 3, которые не возвращаются с этим модифицированным примером # 2.

В системе OS X, выполняющей 5.3.3, пример # 2 дает 407, пример №3 дает 564, а модифицированный пример №2 дает 565. Опять же, нет зон, возвращаемых примером № 3, которые не возвращаются с этим модифицированным примером # 2.

В Linux-системе с 5.2.6 с установленным расширением PECL timezonedb пример # 2 дает мне 571 зону, а пример №3 дает мне всего 488. Нет зон, возвращаемых примером № 3, которые не являются примером № 2 в этой системе , Постоянная DateTimeZone :: ALL_WITH_BC, похоже, не существует в 5.2.6; он, вероятно, был добавлен в 5.3.0.

Таким образом, самый простой способ получить список всех часовых поясов в 5.3.x – timezone_identifiers_list(DateTimeZone::ALL_WITH_BC) , а в 5.2.x – timezone_identifiers_list() . Самый простой (если не самый быстрый) способ проверить, является ли конкретная строка допустимым часовым поясом, по-прежнему вероятно @timezone_open($timezoneId) !== false .

Если вы используете Linux, если не всю информацию о часовых поясах, хранящихся в /usr/share/zoneinfo/ . Вы можете пройти через них, используя is_file() и связанные функции.

Вы также можете анализировать прежние файлы с помощью zdump для кодов или источники извлечения для этих файлов и grep / cut out zdump . Опять же, вы не обязаны использовать встроенные функции для выполнения задачи. Не существует обоснования, почему кто-то заставляет вас использовать только встроенные функции даты.

  1. См. В источниках PHP на php_date.c и timezonemap.h, поэтому я могу сказать, что это всегда в статической информации 101.111111% (но для сборки php).

  2. Если вы хотите получить его динамически, используйте timezone_abbreviations_list как DateTimeZone :: listAbbreviations – это карта.

  3. Как вы можете видеть, все эти значения представляют собой только один раз заполненный список для текущей версии PHP.

Настолько гораздо более быстрое решение просто – подготовьте как-то статический файл с полученными идентификаторами один раз на сервере во время установки вашего приложения и используйте его.

Например:

 function isValidTZ($zone) { static $zones = null; if (null === $zones) { include $YOUR_APP_STORAGE . '/tz_list.php'; } // isset is muuuch faster than array_key_exists and also than in_array // so you should work with structure like [key => 1] return isset($zones[$zone]); } 

tz_list.php должен выглядеть следующим образом:

 <?php $zones = array( 'Africa/Abidjan' => 1, 'Africa/Accra' => 1, 'Africa/Addis_Ababa' => 1, // ... ); 

Я бы исследовал, что изменяет идеальный массив и использует базовый механизм кеширования (например, хранить массив в файле, который вы включаете и обновляете, когда это необходимо). В настоящее время вы оптимизируете создание массива, который является статичным для 99.9999% всех запросов.

Изменить: Хорошо, статично / динамично.

 if( !function_exists(timezone_version_get) ) { function timezone_version_get() { return '2009.6'; } } include 'tz_list_' . PHP_VERSION . '_' . timezone_version_get() . '.php'; 

Теперь каждый раз, когда обновляется версия php, файл должен автоматически восстанавливаться вашим кодом.

В случае php <5.3, как насчет этого?

 public static function is_valid_timezone($timezone) { $now_timezone = @date_default_timezone_get(); $result = @date_default_timezone_set($timezone); if( $now_timezone ){ // set back to current timezone date_default_timezone_set($now_timezone); } return $result; } 

Просто добавление к отличному ответу Кэла. Я думаю, что следующее может быть еще быстрее …

 function isValidTimezoneID($tzid) { if (empty($tzid)) { return false; } foreach (timezone_abbreviations_list() as $zone) { foreach ($zone as $item) { if ($item["timezone_id"] == $tzid) { return true; } } } return false; }