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

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

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

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

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


Изменить для баунти:

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

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

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

4b9b3361

Ответ 1

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

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

У MySQL все еще есть жалкая поддержка часовых поясов, а это значит, что интеллект должен быть PHP-стороной.

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

Это относится к большинству точек.

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

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

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


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

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

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

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

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

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

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


В соответствии с запросом здесь прокрутите доступные часовые пояса, отображая их GMT-смещение в минутах. Я отобрал здесь несколько минут, чтобы продемонстрировать несчастливый факт: не все смещения - целые часы! Некоторые фактически переключаются на полчаса вперед во время ДСТ вместо целого часа. Результирующее смещение в минутах должно соответствовать значению 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";
}

Ответ 2

  • Как сохранить время в базе данных с объекта PHP DateTime

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

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

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

    [ deletia ]

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

    [ deletia ]

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

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

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

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

      $dbh = new PDO("mysql:dbname=$dbname", $username, $password);
      $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
      
    • Задайте сеанс time_zone объекту DateTime:

      $qry = $dbh->prepare('SET SESSION time_zone = ?');
      $qry->execute([$datetime->format('P')]);
      
    • Создайте подходящий литерал из объекта 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" .

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

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

    Можно также добавить, что, как описано в Требования к типу данных типа данных, 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 или статическим значением)?

    Я не уверен, что вы подразумеваете под "местным временем" (локальным для которых? RDBMS? webserver? webclient?), но сравнения между объектами DateTime будут обрабатывать изменения по часовому поясу по мере необходимости (PHP хранит значения внутренне в UTC и только конвертирует для вывода).

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

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

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

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

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

    UPDATE my_table SET the_time = CONVERT_TZ(the_time, '+00:00', '+10:00');
    

Ответ 3

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

* ОБНОВЛЕНИЕ, уточните мой первый параграф * Вы также можете сохранить временную метку как INT. Преимущество заключается в том, что вы знаете, в каком временном часе вы сохранили свое значение, поскольку временная метка - это текущее время, измеренное в секундах от эпохи Unix (1 января 1970 года 00:00:00 по Гринвичу). см. php doc: 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 или статическое значение)?

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

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

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

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

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