Подтвердить что ты не робот

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

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

Согласно списку поддерживаемых часовых поясов из руководства 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 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 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(), если это возможно)? Или, если вы знаете другой способ проверки параметра часового пояса, действительного, сообщите мне (я повторяю, оператор @ не может быть частью решения).


Постскриптум Если вам нужны подробности и примеры, дайте мне знать. Думаю, что нет.

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


Update

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


Другое обновление

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

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

4b9b3361

Ответ 1

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

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);
}

Тест 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];
}

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

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

Ответ 2

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

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

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

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

Ответ 3

Когда я попробовал это на 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 с timezonedb расширение PECL установлено, пример # 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.

Ответ 4

Я бы изучил, что изменяет идеальный массив и использует базовый механизм кеширования (например, хранить массив в файле, который вы включаете и обновляете, когда это необходимо). Вы в настоящее время оптимизируете создание массива, который является статичным для 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, файл должен автоматически восстанавливаться вашим кодом.

Ответ 5

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

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

Ответ 6

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

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

  • Как вы можете видеть, все эти значения представляют собой только один раз заполненный список для текущей версии 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,
    // ...
);

Ответ 7

В случае 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;
}

Ответ 8

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

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;
}