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

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

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

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

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

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

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

Ну что у меня пока ...

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


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

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


Обновление

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


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

Я назначил маленькую награду за этот вопрос!

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

Ответы [ 8 ]

24 голосов
/ 28 апреля 2011

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

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

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

function isValidTimezoneId($timezoneId) {
    try{
        new DateTimeZone($timezoneId);
    }catch(Exception $e){
        return FALSE;
    }
    return TRUE;
} 
16 голосов
/ 04 мая 2011

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

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 также удаляет одиночное приведение, когда результат истинен.

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

5 голосов
/ 03 мая 2011

Когда я попробовал это в системе 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.

1 голос
/ 27 ноября 2013

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

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;
}
1 голос
/ 09 мая 2011
  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,
    // ...
);
1 голос
/ 09 мая 2011

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

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

1 голос
/ 09 мая 2011

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

Edit: Хорошо, статический / динамический.

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

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

0 голосов
/ 14 мая 2016

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

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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...