PHP, MySQL и часовые пояса - PullRequest
       3

PHP, MySQL и часовые пояса

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

Я пытаюсь интегрировать систему часовых поясов в свое приложение, я действительно очень старался избегать создания приложений с поддержкой часовых поясов до сих пор - но это обязательное требование сейчас, поэтому у меня нет выбора.Часовые пояса это просто идет по моей голове.Я прочитал несколько тем на PHP.net, а также на других сайтах, включая, помимо прочего, SO.Но я никогда не мог овладеть этим.

Так что мне было интересно, может ли кто-нибудь помочь мне здесь :( То, что я хочу сделать, является предпочтительным вариантом в моем приложении, чтобы позволить пользователям выбирать своисобственные часовые пояса из меню выбора, но приложение также должно иметь возможность устанавливать / выбирать DST соответственно для каждого пользователя.

Пожалуйста, я уверен, что это поможет другим, кто все еще стремится освоитьчасовые пояса, поэтому, пожалуйста, предоставьте как можно более подробное объяснение, даже если вы считаете меня полным дураком / нубом.


Изменить для награды:

Я добавляю награду к этому вопросу, потому что мне действительно нужен хороший канонический вопрос о часовых поясах при написании приложений PHP / MySQL (таким образом, я также добавляю тег MySQL). Я нашел вещи из многих мест, но этобыло бы хорошо, если бы все было вместе. Ответ Чарльза великолепен, но я все еще чувствую, что ему чего-то не хватает. Вот некоторые вещи, о которых я подумал:

  • Как сохранить времяs в базе данных из объекта PHP DateTime
  • Должны ли они храниться в DATETIME или TIMESTAMP?Каковы преимущества или предостережения для каждого?
  • Нужно ли нам когда-нибудь беспокоиться о часовых поясах с MySQL DATE?
  • Как вставить значения с помощью NOW().Нужно ли конвертировать их до или после вставки?
  • Нужно ли устанавливать часовой пояс, используемый MySQL?Если так, то как?Это должно быть сделано постоянно или при каждом запросе HTTP?Должен ли он быть установлен в UTC или это может быть что-то еще?Или достаточно времени сервера?
  • Как извлечь значения из MySQL и преобразовать их в DateTime объект.Достаточно ли будет указать это прямо в DateTime::__construct() или нам нужно использовать DateTime::createFromFormat()?
  • Когда переводить в местное время и почему.Есть ли время, когда мы хотели бы преобразовать его до того, как будет возвращено пользователю (например, для сравнения с другим объектом DateTime или статическим значением)?
  • Есть ли когда-либовремя нам нужно беспокоиться о летнее время (DST)?Почему или почему нет?
  • Что делать, если кто-то вставил ранее данные (например, с помощью NOW()), не беспокоясь о часовом поясе, чтобы убедиться, что все остается согласованным?
  • Что-нибудь еще, что выподумайте о том, что кто-то должен искать

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

Ответы [ 3 ]

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

Этот ответ был обновлен с учетом щедрости.Оригинальный неотредактированный ответ находится ниже линии.

Почти все вопросы, добавленные владельцем щедрости, касаются того, как должны взаимодействовать даты и времени MySQL и PHP в контексте часовых поясов.

MySQL по-прежнему имеет жалкую поддержку часового пояса , что означает, что интеллект должен быть на стороне PHP.

  • Установите свой MySQL подключение часового пояса к UTC, как указано в ссылке выше.Это приведет к тому, что все даты и время, обрабатываемые MySQL, включая NOW(), будут обрабатываться разумно.
  • Всегда использовать DATETIME, никогда не использовать TIMESTAMP, если только вам явно не требуется специальное поведениев TIMESTAMP.Это менее болезненно, чем раньше.
    • Это ok для сохранения времени эпохи Unix в виде целого числа, если у вас есть , например, длянаследственные цели.Эпоха - UTC.
    • Предпочитаемый формат даты и времени MySQL создается с использованием строки формата даты PHP Y-m-d H:i:s
  • Преобразование всех PHP даты в UTCпри их сохранении в MySQL, что довольно просто, как описано ниже
  • Даты, возвращаемые из MySQL, можно безопасно передавать конструктору PHP DateTime.Обязательно укажите часовой пояс UTC!
  • Конвертируйте PHP DateTime в локальный часовой пояс пользователя в echo , не раньше.К счастью, сравнение DateTime и математика по сравнению с другими DateTimes будет учитывать часовой пояс, в котором находится каждый из них.
  • Вы по-прежнему согласны с прихотью базы данных DST, предоставляемой с PHP.Следите за обновлениями PHP и ОС!Держите MySQL в блаженном состоянии UTC, чтобы убрать одно потенциальное раздражение по летнему времени.

Обращается к большинству точек.

Последняя вещь - это doozy:

  • Что делать, если кто-то уже вставил данные (например, с помощью NOW()), не беспокоясь о часовом поясе, чтобы убедиться, что все остается согласованным?

Это настоящее раздражение.Один из других ответов указал на MySQL CONVERT_TZ, хотя я лично сделал бы это, переключаясь между часовым поясом сервера и UTC во время выбора и обновления, потому что я такой хардкорный.


приложение также должно иметь возможность устанавливать / выбирать DST соответственно для каждого пользователя.

Вам не нужно, а не должно делают это в современную эпоху.

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

Вы можете комбинировать DateTimeZone с DateTime для некоторых простых, но мощных функций.Вы можете просто сохранить и использовать все ваших временных меток в UTC по умолчанию и преобразовать их в отображаемый часовой пояс пользователя.

// UTC default
    date_default_timezone_set('UTC');
// Note the lack of time zone specified with this timestamp.
    $nowish = new DateTime('2011-04-23 21:44:00');
    echo $nowish->format('Y-m-d H:i:s'); // 2011-04-23 21:44:00
// Let's pretend we're on the US west coast.  
// This will be PDT right now, UTC-7
    $la = new DateTimeZone('America/Los_Angeles');
// Update the DateTime's timezone...
    $nowish->setTimeZone($la);
// and show the result
    echo $nowish->format('Y-m-d H:i:s'); // 2011-04-23 14:44:00

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

Вы можете использовать аналогичный метод для отображения меню выбора.Вы можете непрерывно переназначать часовой пояс для одного объекта DateTime.Например, этот код перечислит зоны и их текущее время, на данный момент:

$dt = new DateTime('now', new DateTimeZone('UTC')); 
foreach(DateTimeZone::listIdentifiers() as $tz) {
    $dt->setTimeZone(new DateTimeZone($tz));
    echo $tz, ': ', $dt->format('Y-m-d H:i:s'), "\n";
}

Вы можете значительно упростить процесс выбора с помощью некоторой магии на стороне клиента.Javascript имеет пятнистый, но функциональный класс дат со стандартным методом для получения смещения UTC в минутах .Вы можете использовать это, чтобы сузить список вероятных часовых поясов при слепом предположении, что часы пользователя правильные.

Давайте сравним этот метод с тем, чтобы сделать это самостоятельно.На самом деле вам нужно будет выполнять математику даты каждый раз, когда вы манипулируете датой-временем, в дополнение к тому, что вам не нужно выбирать пользователя, о котором он не будет беспокоиться.Это не просто неоптимально, это безумный бат-гуано.Заставление пользователей указывать, когда они хотят, чтобы поддержка DST вызывала проблемы и путаницу.

Кроме того, если вы хотите использовать для этого современные фреймворки PHP DateTime и DateTimeZone, вам необходимо использовать устаревшие Etc/GMT... строки часовых поясов вместо именованных часовых поясов.Эти имена зон могут быть удалены из будущих версий PHP, поэтому было бы неразумно делать это.Я говорю все это из опыта.

tl; dr : Используйте современный набор инструментов, избавьте себя от ужасов даты математики.Предоставьте пользователю список с именем часовых поясов. Сохраните ваши даты в UTC , на которые DST никак не повлияет.Преобразуйте дату и время в выбранный пользователем часовой пояс на дисплее , не ранее.


В соответствии с запросом, приведен цикл по доступным часовым поясам, отображающий их смещение по Гринвичу в минутах.Я выбрал минуты здесь, чтобы продемонстрировать прискорбный факт: не все смещения в целых часах!Некоторые фактически переключаются на полчаса вперед во время летнего времени, а не на целый час.Результирующее смещение в минутах должно совпадать с Javascript Date.getTimezoneOffset.

$utc = new DateTimeZone('UTC');
$dt = new DateTime('now', $utc); 
foreach(DateTimeZone::listIdentifiers() as $tz) {
    $local = new DateTimeZone($tz);
    $dt->setTimeZone($local);
    $offset = $local->getOffset($dt); // Yeah, really.
    echo $tz, ': ', 
         $dt->format('Y-m-d H:i:s'),
         ', offset = ',
         ($offset / 60),
         " minutes\n";
}
4 голосов
/ 06 марта 2013
  • Как сохранить время в базе данных из PHP DateTime объекта

    Стандарт SQL-92 указывает, что временные литералы должны передаваться в SQL с использованием подходящего ключевого слова типа данных (например, TIMESTAMP для значений даты / времени), за которым следует строковое представление значения ( содержит необязательное смещение часового пояса, если не по умолчанию).

    К сожалению, MySQL не соответствует этой части стандарта SQL. Как указано в Литералы даты и времени :

    Стандартный SQL позволяет указывать временные литералы с использованием ключевого слова типа и строки.

    [ <strong><em>deletia</em></strong> ]

    MySQL распознает эти конструкции, а также соответствующий синтаксис ODBC:

    [ <strong><em>deletia</em></strong> ]

    Однако MySQL игнорирует ключевое слово type, и каждая из предыдущих конструкций создает строковое значение '<strong><em>str</em></strong>' с типом VARCHAR.

    В документации описываются буквальные форматы, которые поддерживает MySQL, и, в частности, явные смещения часовых поясов отсутствуют. Существует запрос функции , чтобы исправить это, которому уже более семи лет и который вряд ли появится в ближайшее время.

    Вместо этого необходимо установить переменную time_zone сеанса до обмена значениями даты / времени между сервером и клиентом. Поэтому, используя PDO :

    1. Подключение к MySQL:

      $dbh = new PDO("mysql:dbname=$dbname", $username, $password);
      $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
      
    2. Установите сеанс time_zone на сеанс объекта DateTime:

      $qry = $dbh->prepare('SET SESSION time_zone = ?');
      $qry->execute([$datetime->format('P')]);
      
    3. Создайте подходящий литерал из объекта DateTime и передайте MySQL как обычно (т. Е. В качестве параметра для подготовленного оператора).

      Как описано в документации, существует ряд возможных литеральных форматов, которые можно использовать. Тем не менее, я бы предложил использовать строку в формате 'YYYY-MM-DD hh:mm:ss.ffffff' (обратите внимание, что дробные секунды будут игнорироваться в версиях MySQL до 5.6), так как она наиболее близка к стандарту SQL; действительно, можно поставить префикс литерала с ключевым словом TIMESTAMP, чтобы гарантировать переносимость SQL:

      $qry = $dbh->prepare('
        UPDATE my_table
        SET    the_time = TIMESTAMP ?
        WHERE  ...
      ');
      $qry->execute([$datetime->format('Y-m-d H:i:s.u')]);
      
  • Должны ли они храниться в DATETIME или TIMESTAMP? Каковы преимущества или предостережения для каждого?

    PHP DateTime объекты всегда должны храниться в столбцах типа TIMESTAMP.

    Самым фундаментальным отличием является то, что TIMESTAMP хранит информацию о часовом поясе (путем сохранения значения в UTC и преобразования в / из, как требуется для переменной time_zone выше), тогда как DATETIME - нет. Таким образом, TIMESTAMP полезно для представления определенного момента времени (аналогично PHP DateTime объектам), тогда как DATETIME полезно для представления времени, которое видно на календаре / часах (как на фотографии).

    Как указано в Типы DATE, DATETIME и TIMESTAMP :

    Тип DATETIME используется для значений, которые содержат как дату, так и время. MySQL извлекает и отображает значения DATETIME в формате 'YYYY-MM-DD HH:MM:SS'. Поддерживаемый диапазон: от '1000-01-01 00:00:00' до '9999-12-31 23:59:59'.

    Тип данных TIMESTAMP используется для значений, которые содержат как дату, так и время. TIMESTAMP имеет диапазон от '1970-01-01 00:00:01' UTC до '2038-01-19 03:14:07' UTC.

    MySQL преобразует значения TIMESTAMP из текущего часового пояса в UTC для хранения и обратно из UTC в текущий часовой пояс для извлечения. (Это не происходит для других типов, таких как DATETIME.) По умолчанию текущим часовым поясом для каждого соединения является время сервера. Часовой пояс может быть установлен для каждого соединения отдельно. Пока настройка часового пояса остается постоянной, вы получаете то же значение, которое храните. Если вы сохраните значение TIMESTAMP, а затем измените часовой пояс и получите значение, полученное значение будет отличаться от значения, которое вы сохранили. Это происходит потому, что один и тот же часовой пояс не использовался для преобразования в обоих направлениях. Текущий часовой пояс доступен как значение системной переменной time_zone. Для получения дополнительной информации см. Раздел 10.6, «Поддержка часового пояса MySQL Server» .

    Тип данных TIMESTAMP предлагает автоматическую инициализацию и обновление до текущей даты и времени. Для получения дополнительной информации см. Раздел 11.3.5, «Автоматическая инициализация и обновление для TIMESTAMP» .

    Обратите внимание на последний абзац, который часто привлекает новичков в MySQL.

    Также стоит добавить, что, как указано в Требования к типу данных для хранения * значения 1127 *, DATETIME требуют 8 байт для хранения, тогда как значения TIMESTAMP требуют только 4 байта (базовый формат хранения данных может можно найти в Представление типа данных даты и времени ).

  • Нужно ли нам когда-нибудь беспокоиться о часовых поясах с MySQL DATE?

    Это имеет смысл только для времени , чтобы быть чувствительным к часовому поясу. По определению, дата сама по себе универсально одинакова независимо от часового пояса, и поэтому нет необходимости «беспокоиться о часовых поясах» при использовании типа данных MySQL DATE.

    Следствием этого является то, что, если кто-то имеет значение, чувствительное к часовому поясу, он также должен хранить свое время, например, в столбце TIMESTAMP: использование столбца DATE приводит к необратимой потере важной информации.

  • Как вставить значения, используя NOW(). Нужно ли конвертировать их до или после вставки?

    Как указано в NOW():

    Возвращает текущую дату и время в виде значения в формате 'YYYY-MM-DD HH:MM:SS' или YYYYMMDDHHMMSS.uuuuuu, в зависимости от того, используется ли функция в строковом или числовом контексте. Значение выражается в текущем часовом поясе.

    Поскольку « значение выражено в текущем часовом поясе », и те же самые « текущий часовой пояс » будут использоваться при оценке значений даты / времени, нет необходимости беспокоиться о часовом поясе при использовании функции MySQL NOW() (или любого из ее псевдонимов). Поэтому для вставки записи:

    INSERT INTO my_table (the_time) VALUES (NOW());
    

    Обратите внимание, что, как упоминалось выше, автоматическая инициализация MySQL столбцов TIMESTAMP делает излишним большинство попыток использовать NOW() во время вставки / обновления записи.

  • Необходимо ли устанавливать часовой пояс, используемый MySQL? Если так, то как? Это должно быть сделано постоянно или при каждом запросе HTTP? Должен ли он быть установлен в UTC или это может быть что-то еще? Или времени сервера достаточно?

    Это уже упоминалось выше. Можно установить переменную MySQL time_zone глобально, если это необходимо, и, таким образом, избежать необходимости устанавливать ее при каждом соединении. См. Поддержка часового пояса сервера MySQL для получения дополнительной информации.

  • Как извлечь значения из MySQL и преобразовать их в DateTime объект. Достаточно ли поместить его прямо в DateTime::__construct() или нам нужно использовать DateTime::createFromFormat()?

    Как описано в Составные форматы , один из форматов даты / времени, распознаваемых синтаксическим анализатором, который PHP использует в DateTime::__construct(), является выходным форматом MySQL.

    Однако, поскольку выходной формат MySQL не включает часовой пояс, необходимо обязательно предоставить конструктору DateTime эту информацию через его необязательный второй аргумент:

    $qry = $dbh->prepare('SET SESSION time_zone = ?');
    $qry->execute([$timezone->getName()]);
    $qry = $dbh->query('SELECT the_time FROM my_table');
    $datetime = new DateTime($qry->fetchColumn(), $timezone);
    

    В качестве альтернативы, MySQL может преобразовать время в метку времени UNIX и построить объект DateTime из этого:

    $qry = $dbh->query('SELECT UNIX_TIMESTAMP(the_time) FROM my_table');
    $datetime = new DateTime($qry->fetchColumn());
    
  • Когда переводить в местное время и почему. Есть ли когда-нибудь время, когда мы захотим преобразовать его до , когда оно будет возвращено пользователю (например, для сравнения с другим объектом DateTime или статическим значением)?

    Я не уверен, что вы подразумеваете под "местным временем" (местное, для которого? СУБД? Веб-сервер? Веб-клиент?), Но сравнения между DateTime объектами будут обрабатывать преобразования часовых поясов по мере необходимости (PHP хранит значения внутренне в UTC и преобразует только для вывода).

  • Есть ли время, когда нам нужно беспокоиться о переходе на летнее время (DST)? Почему или почему нет?

    Вообще говоря, если вы следуете методике, приведенной выше, единственное беспокойство о летнем времени заключается в обеспечении того, чтобы значения отображались пользователю в ожидаемом часовом поясе.

  • Что следует делать, если они ранее вставили данные (например, с помощью NOW()), не беспокоясь о часовом поясе, чтобы убедиться, что все остается согласованным?

    Как упоминалось выше, использование NOW() никогда не должно вызывать проблем.

    Если литеральные значения были вставлены в столбец TIMESTAMP, в то время как переменная time_zone сеанса была установлена ​​в неверное значение, необходимо будет соответствующим образом обновить эти значения. Функция MySQL CONVERT_TZ() может оказаться полезной:

    UPDATE my_table SET the_time = CONVERT_TZ(the_time, '+00:00', '+10:00');
    
0 голосов
/ 07 марта 2013

Как хранить времена в базе данных из объекта PHP DateTime Должны ли они храниться в DATETIME или TIMESTAMP?Каковы преимущества или предостережения для каждого?

* ОБНОВЛЕНИЕ, уточните мой первый абзац * Вы также можете сохранить метку времени как INT.Преимущество состоит в том, что вы знаете, в каком часовом поясе вы сохранили свое значение, поскольку отметка времени - это текущее время, измеренное в секундах с начала эпохи Unix (1 января 1970 г., 00:00:00 по Гринвичу). см. Phpdoc: http://php.net/manual/en/function.time.php Используя 64-битную операционную систему, вам не нужно беспокоиться о выпуске 2038 года: http://en.wikipedia.org/wiki/Year_2038_problem

Отметка времени намного проще в использовании для сравнения времени даты и более интересногоиспользовать в объектах и ​​массивах.Например, вы можете легко использовать их в качестве ключей для ваших массивов.

Нужно ли нам когда-либо беспокоиться о часовых поясах с MySQL DATE?

В MySQL CURRENT_TIMESTAMP (), Функции CURRENT_TIME (), CURRENT_DATE () и FROM_UNIXTIME () возвращают значения в текущем часовом поясе соединения, которое доступно как значение системной переменной time_zone.Кроме того, UNIX_TIMESTAMP () предполагает, что его аргумент является значением даты и времени в текущем часовом поясе.http://dev.mysql.com/doc/refman/5.0/en/date-and-time-functions.html

Как вставить значения, используя NOW ().Нужно ли их каким-либо образом преобразовывать до или после вставки?

Если вы используете метку времени, вы можете положиться на функцию PHP, это просто целое число.

Если вы используетеДата, функция curdate позволяет вам иметь текущую дату.http://dev.mysql.com/doc/refman/5.0/en/date-and-time-functions.html#function_curdate

Нужно ли устанавливать часовой пояс, используемый MySQL?Если так, то как?Это должно быть сделано постоянно или при каждом запросе HTTP?Должен ли он быть установлен в UTC или это может быть что-то еще?Или достаточно времени сервера?

см. http://dev.mysql.com/doc/refman/5.0/en/time-zone-support.html

Как извлечь значения из MySQL и преобразовать их в объект DateTime.Будет ли достаточно поместить его прямо в DateTime :: __ construct () или нам нужно использовать DateTime :: createFromFormat ()?

Опять же, если вы используете метку времени, это будет проще.Вы знаете временную зону с отметкой времени, знаете, когда переводить местное время и почему.DST легко управлять с помощью метки времени, см. Функции вокруг метки времени: http://php.net/manual/en/function.mktime.php

Есть ли время, когда мы хотели бы преобразовать его, прежде чем оно будет возвращено пользователю (например, для сравнения сдругой объект DateTime или статическое значение)?

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

Есть ли время, когда нам нужно беспокоиться о переходе на летнее время (DST)?Почему или почему нет?Что делать, если они ранее вставили данные (например, с помощью NOW ()), не беспокоясь о часовом поясе, чтобы убедиться, что все остается согласованным?

Да, вам следует беспокоиться о том, нужно ли создаватьназначение или встреча в заявке.Я разработал два приложения, одно для клинического назначения и одно для семинаров, которые поддерживают более 70000 учетных записей и огромное количество записей.Я придерживаюсь метки времени, это супер легко индексировать, манипулировать, сравнивать.Часть для печати доступна только в представлении.

Существуют преимущества использования datetime в вашей базе данных.Если вам нужно проанализировать данные из таблицы в sql direct, их будет намного легче читать, они более «удобочитаемы».

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...