#php #validation #datetime #time #timezone
#php #проверка #дата и время #время #Часовой пояс
Вопрос:
Я попытаюсь объяснить, в чем здесь проблема.
Согласно списку поддерживаемых часовых поясов из руководства по PHP, я могу видеть все действительные идентификаторы TZ в PHP.
Мой первый вопрос заключается в том, как получить этот список из кода, но это не то, что мне действительно нужно.
Моя конечная цель — написать функцию, isValidTimezoneId()
которая возвращает TRUE, если допустим часовой пояс, в противном случае она должна возвращать FALSE.
function isValidTimezoneId($timezoneId) {
# ...function body...
return ?; # TRUE or FALSE
}
Итак, когда я передаю идентификатор TZ с помощью $timezoneId
(string) в функции, мне нужен логический результат.
Ну, что у меня есть на данный момент…
1) Решение с использованием @ operator
Первое решение, которое у меня есть, примерно такое:
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']) amp;amp; $item['timezone_id'] != '')
$tzlist[] = $item['timezone_id'];
$tzlist = array_unique($tzlist);
asort($tzlist);
return array_values($tzlist);
}
This function returns 563 elements (in Example #2
I’ve got just 407).
I’ve tried to find differences between those two arrays:
$a1 = timezone_identifiers_list();
$a2 = createTZlist();
print_r(array_values(array_diff($a2, $a1)));
Result is:
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
)
This list contains all valid TZ identifiers that Example #2
failed to match.
Есть еще четыре идентификатора 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()
, если это возможно)? Или, если вы знаете другой способ, как проверить, действителен ли параметр часового пояса, пожалуйста, дайте мне знать (я повторяю, @
оператор не может быть частью решения).
P.S. Если вам нужны более подробные сведения и примеры, дайте мне знать. Я думаю, вы этого не делаете.
Я использую PHP 5.3.5
(думаю, это не важно).
Обновить
Любая часть кода, которая выдает исключение для недопустимой строки часового пояса (скрытая с помощью @
или перехваченная с помощью try..catch
блока), не является решением, которое я ищу.
Другое обновление
Я назначил небольшую награду за этот вопрос!
Теперь я ищу самый простой способ, как извлечь список всех идентификаторов часовых поясов в PHP array.
Ответ №1:
Почему бы не использовать @ operator? Этот код работает довольно хорошо, и вы не меняете часовой пояс по умолчанию:
function isValidTimezoneId($timezoneId) {
@$tz=timezone_open($timezoneId);
return $tz!==FALSE;
}
Если вы не хотите @, вы можете сделать:
function isValidTimezoneId($timezoneId) {
try{
new DateTimeZone($timezoneId);
}catch(Exception $e){
return FALSE;
}
return TRUE;
}
Комментарии:
1. Решение try-catch является лучшим имхо. Хорошее OO-решение.
2. @hectorct — 1 за хорошую попытку. Я действительно ценю это. Но… Я не хочу ничего, что вызывает исключение! Это очень важная часть этой задачи. Оба примера хороши, но оба они могут вызвать исключение для недопустимого часового пояса (даже если он скрыт или перехвачен). Это именно то, чего я хочу избежать. Мне нравятся оба способа, большое спасибо, чувак.
3. @Wh1t3h4Ck5 Извините, но могу я спросить, почему? Просто интересно. Почему вы ищете такие сложные решения, когда это может быть достигнуто простым использованием?
4. @Wh1t3h4Ck5 Спасибо. Я провел тест, и ваш код в 30 раз быстрее, чем оба моих. Вау! Я не знал, что попытка создать объект Timezone была такой дорогостоящей.
5. @hectorct, я его еще не тестировал. Но вы, вероятно, правы. Классы в основном основаны на собственных функциях PHP, и вы никогда не знаете, сколько кода они содержат в своем конструкторе / методах. Каждая отдельная строка имеет значение с точки зрения скорости. Ну, мы говорим здесь о микро- или наносекундах, но в огромном коде, и особенно в циклах, это время увеличивается.
Ответ №2:
Ваше решение работает нормально, поэтому, если вы ищете скорость, я бы более внимательно посмотрел на то, что вы делаете со своими массивами. Я рассчитал время нескольких тысяч попыток, чтобы получить разумное среднее время, и вот результаты:
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 мс на моей тестовой машине, что почти вдвое меньше времени, указанного в вашем примере. Забавный эксперимент по микрооптимизации!
Комментарии:
1. 1 очень интересный подход. Скажите мне еще одну вещь. Какую ОС вы использовали для получения этих микровременей? Это был онлайн-сервер или localhost? Какая версия PHP? Использовали ли вы microtime() (с параметром TRUE или без него)?
2. PHP 5.2.6 запускается из командной строки, используя незанятый экземпляр EC2 m1.small. Я запускал каждую пробную версию 2000 раз (чередующимися блоками по 100, чтобы усреднить любую другую активность на сервере, влияющую на нее), используя microtime (true) для получения микросекунд.
3. спасибо, я просто тестирую все 7 примеров (упомянутых в вопрос ответы), используя 3 разные платформы (среднее микротайм 1 000 000 попыток).
4. Этот ответ — это в основном то, чего я хотел достичь, даже если это еще не идеальное решение. Я, наконец, закончил тесты. В ближайшие пару дней я обновлю свой вопрос с результатами, которые у меня есть. Еще раз спасибо, чувак.
5. я знаю, что он старый, но небольшая синтаксическая ошибка в вашем решении, последняя строка должна вернуть !!$valid (с $)
Ответ №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
.
Комментарии:
1. 1, спасибо, выглядит интересно. Самый быстрый способ — это, конечно,
@date_default_timezone_set($timezoneId)
, я знаю это, потому что этот метод не вызывает конструктор класса like@timezone_open($timezoneId)
(псевдонимDateTimeZone::__construct()
). На самом деле, я еще не тестировал это, но hectorct сказал в первом ответе, что это в 30 раз медленнее, чем в моем примере # 1. Однако позже я собираюсь проанализировать и протестировать ваш код на разных платформах. Посмотрим, какие результаты я получу.2. @Wh1T3h4Ck5: Как я упоминал выше, более вероятно, что
timezone_open
метод медленнее для успешного результата, потому что он фактически анализирует данные часового пояса, в то время как другие методы этого не делают. Еслиdate_default_timezone_set
работает быстрее, чемtimezone_open
, я бы предположил, что он тоже просто проверяет идентификатор и не утруждает себя анализом данных часового пояса до тех пор, пока это не потребуется.3. вы правы… Я думаю, что идеальным решением будет, если я смогу найти, как
date_default_timezone_set()
работает (его основной код), чтобы у меня было быстрое решение без установки часового пояса. Эта строка:timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)
выглядит как то, что мне нужно (меньше кода, чем в моем примере # 3, возвращает тот же результат). В данный момент меня нет дома, поэтому я сравню результаты позже и дам вам знать, это то, что я ищу. Я почти уверен, что это так.
Ответ №4:
Если вы используете Linux, большая часть, если не вся информация о часовых поясах, хранящаяся в /usr/share/zoneinfo/
. Вы можете просмотреть их с помощью is_file()
и связанных функций.
Вы также можете проанализировать предыдущие файлы с помощью zdump
кодов for или получить исходники для этих файлов и grep / вырезать необходимую информацию. Опять же, вы не обязаны использовать встроенные функции для выполнения задачи. Нет никакого обоснования, почему кто-то заставляет вас использовать только встроенные функции даты.
Комментарии:
1. Я на разных платформах. Я ищу окончательное решение для каждой платформы. Я не хочу использовать способ, зависящий от платформы. Тем не менее, это хороший ответ. 1, потому что на этот вопрос нет неправильных ответов. Просмотр файлов замедляется, особенно в циклах. Моя главная цель — сформировать скорость. Спасибо за ваши усилия, я ценю это.
2. Никто не может помешать вам просмотреть этот файл один раз и сохранить результаты в любом формате, который вы предпочитаете для дальнейшего использования. Здесь все в ваших руках.
Ответ №5:
-
Смотрите в источниках 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,
// ...
);
Комментарии:
1. 1) Да, это статично. У меня нет исходных файлов в дистрибутиве PHP для Windows (только DLL-ы). На серверах Linux у меня нет доступа к исходному коду. 2) У меня уже есть это решение, и я предполагаю, что оно не идеальное, но на данный момент у меня нет ничего лучше. 3) Абсолютно согласен с вами. Размещение часовых поясов в массиве — это не то, что мне нужно, потому что я не хочу обновлять этот список для каждого дистрибутива PHP. Я работаю над окончательным решением для каждого выпуска PHP и не могу использовать что-то, что зависит от версии PHP. Тем не менее, хороший ответ 1.
Ответ №6:
Я бы исследовал, что изменяет идеальный массив, и использовал базовый механизм кэширования (например, сохранял массив в файле, который вы включаете и обновляете при необходимости). В настоящее время вы оптимизируете построение массива, который является статическим для 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, файл должен автоматически восстанавливаться вашим кодом.
Комментарии:
1. 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;
}
Комментарии:
1.
1.
я не могу выбрать версию PHP, используемую моими клиентами.2.
Сначала прочитайте вопрос,3.
особенно внимательно прочитайте часть под заголовком , что мне здесь действительно нужно? и строка в примере # 1,4.
этот код выглядит не более простым, чем тот, который показан в примере # 2. В любом случае, спасибо за вашу попытку.
Ответ №8:
Просто дополнение к отличному ответу Кэла. Я думаю, что следующее может быть еще быстрее…
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;
}