Поля даты и времени в MySQL - как мне сослаться на «дополнительный» час? - PullRequest
81 голосов
/ 29 октября 2009

Я использую часовой пояс Америки / Нью-Йорка. Осенью мы «отступаем» на час - фактически «набираем» один час в 2 часа ночи. В точке перехода происходит следующее:

это 01:59:00 -04: 00
затем через 1 минуту становится:
01:00:00 -05: 00

Так что, если вы просто скажете «1:30 утра», это будет неоднозначно относительно того, имеете ли вы в виду первый раз, когда 1:30 катится или второй. Я пытаюсь сохранить данные планирования в базе данных MySQL и не могу определить, как правильно сохранить время.

Вот проблема:
«2009-11-01 00:30:00» хранится как 2009-11-01 00:30:00 -04: 00
«2009-11-01 01:30:00» хранится как 2009-11-01 01:30:00 -05: 00

Это нормально и вполне ожидаемо. Но как мне сохранить что-нибудь в 01:30:00 -04: 00 ? Документация не показывает никакой поддержки для определения смещения и, соответственно, когда я пытался указать смещение, оно должным образом игнорировалось.

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

Большое спасибо за любые предложения.

Ответы [ 7 ]

65 голосов
/ 30 октября 2009

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

Вопреки тому, что я сказал в одном из моих предыдущих комментариев, поля DATETIME и TIMESTAMP do ведут себя по-разному. Поля TIMESTAMP (как указывают документы) берут все, что вы отправляете, в формате «ГГГГ-ММ-ДД чч: мм: сс» и преобразует его из текущего часового пояса в время UTC. Обратное происходит прозрачно всякий раз, когда вы получаете данные. Поля DATETIME не делают это преобразование. Они берут все, что вы им отправляете, и просто храните это напрямую.

Ни типы полей DATETIME, ни TIMESTAMP не могут точно хранить данные в часовом поясе, который соблюдает DST . Если вы храните «01-11-2009, 01:30:00», в полях нет никакой возможности определить, какую версию 1:30 вы хотели - версию -04: 00 или -05: 00.

Хорошо, поэтому мы должны хранить наши данные в часовом поясе не в летнее время (например, в формате UTC). Поля TIMESTAMP не могут обрабатывать эти данные точно по причинам, которые я объясню: если ваша система настроена на часовой пояс DST, то то, что вы поместили в TIMESTAMP, может быть не тем, что вы получите обратно. Даже если вы отправите ему данные, которые вы уже преобразовали в UTC, он все равно будет предполагать, что данные находятся в вашем местном часовом поясе, и выполнит еще одно преобразование в UTC. Эта вынужденная обратная поездка по местному-UTC-обратно-локальному времени с применением TIMESTAMP вызывает потери, когда ваш местный часовой пояс соблюдает летнее время (поскольку «2009-11-01 01:30:00» отображается в 2 различных возможных времени).

С DATETIME вы можете хранить свои данные в любом часовом поясе и быть уверенным, что получите все, что отправите (вы не будете вынуждены переходить с потерями туда и обратно, на что навязывают вам поля TIMESTAMP). Таким образом, решение состоит в том, чтобы использовать поле DATETIME и перед сохранением в поле преобразовать из системного часового пояса в любую зону без DST, в которой вы хотите его сохранить (я думаю, что UTC, вероятно, лучший вариант). Это позволяет вам встроить логику преобразования в ваш язык сценариев, чтобы вы могли явно сохранить UTC-эквивалент "2009-11-01 01:30:00 -04: 00" или "" 2009-11-01 01:30: 00 -05: 00 ".

Еще одна важная вещь, которую стоит отметить, - это то, что математические функции даты / времени в MySQL не работают должным образом вокруг границ DST, если вы храните даты в DST TZ. Так что все больше причин экономить на UTC.

Короче говоря, теперь я делаю это:

При извлечении данных из базы данных:

Явно интерпретирует данные из базы данных как UTC за пределами MySQL, чтобы получить точную метку времени Unix. Для этого я использую функцию PHP strtotime () или ее класс DateTime. Это не может быть надежно сделано внутри MySQL с помощью функций MySQL CONVERT_TZ () или UNIX_TIMESTAMP (), поскольку CONVERT_TZ будет выводить только значение «YYYY-MM-DD hh: mm: ss», которое страдает от проблем неоднозначности, а UNIX_TIMESTAMP () предполагает его вход находится в системном часовом поясе, а не в часовом поясе, в котором данные были фактически сохранены (UTC).

При сохранении данных в базе данных:

Преобразуйте вашу дату в точное время UTC, которое вы хотите за пределами MySQL. Например: с помощью класса PHP DateTime вы можете указать «2009-11-01 1:30:00 EST» отдельно от «2009-11-01 1:30:00 EDT», затем преобразовать его в UTC и сохранить правильное время UTC в поле DATETIME.

Уф. Большое спасибо за каждый вклад и помощь. Надеюсь, это спасет кого-то еще от головной боли в будущем.

Кстати, я вижу это на MySQL 5.0.22 и 5.0.27

41 голосов
/ 30 октября 2009

Типы дат MySQL, честно говоря, не работают и не могут правильно хранить все раза, если в вашей системе не установлен часовой пояс с постоянным смещением, например UTC или GMT-5. (Я использую MySQL 5.0.45)

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

Часовой пояс моей системы America/New_York. Давайте попробуем сохранить 1257051600 (вс, 01 ноября 2009 06:00:00 +0100).

Здесь используется собственный синтаксис INTERVAL:

SELECT UNIX_TIMESTAMP('2009-11-01 00:00:00' + INTERVAL 3599 SECOND); # 1257051599
SELECT UNIX_TIMESTAMP('2009-11-01 00:00:00' + INTERVAL 3600 SECOND); # 1257055200

SELECT UNIX_TIMESTAMP('2009-11-01 01:00:00' - INTERVAL 1 SECOND); # 1257051599
SELECT UNIX_TIMESTAMP('2009-11-01 01:00:00' - INTERVAL 0 SECOND); # 1257055200

Даже FROM_UNIXTIME() не вернет точное время.

SELECT UNIX_TIMESTAMP(FROM_UNIXTIME(1257051599)); # 1257051599
SELECT UNIX_TIMESTAMP(FROM_UNIXTIME(1257051600)); # 1257055200

Как ни странно, DATETIME будет все еще сохранять и возвращать (только в строковой форме!) Времена в течение "потерянного" часа, когда начинается DST (например, 2009-03-08 02:59:59). Но использование этих дат в любой функции MySQL рискованно:

SELECT UNIX_TIMESTAMP('2009-03-08 01:59:59'); # 1236495599
SELECT UNIX_TIMESTAMP('2009-03-08 02:00:00'); # 1236495600
# ...
SELECT UNIX_TIMESTAMP('2009-03-08 02:59:59'); # 1236495600
SELECT UNIX_TIMESTAMP('2009-03-08 03:00:00'); # 1236495600

Вывод: если вам нужно хранить и извлекать каждый раз в году, у вас есть несколько нежелательных вариантов:

  1. Установите системный часовой пояс на GMT + некоторое постоянное смещение. Например. UTC
  2. Храните даты как INT (как обнаружил Аарон, TIMESTAMP даже не надежен)

  3. Представим, что тип DATETIME имеет некоторый постоянный часовой пояс смещения. Например. Если вы находитесь в America/New_York, преобразуйте дату в GMT-5 за пределами MySQL , а затем сохраните как DATETIME (это оказывается необходимым: см. Ответ Аарона). Затем вы должны быть очень осторожны, используя функции даты / времени MySQL, потому что некоторые предполагают, что ваши значения относятся к системному часовому поясу, другие (особенно арифметические функции времени) «независимы от часового пояса» (они могут вести себя так, как если бы время было UTC). 1040 *

Аарон и я подозреваем, что автоматически генерирующиеся столбцы TIMESTAMP также не работают. И 2009-11-01 01:30 -0400, и 2009-11-01 01:30 -0500 будут сохранены как неоднозначные 2009-11-01 01:30.

11 голосов
/ 30 марта 2010

Я думаю, что ссылка от micahwittman имеет лучшее практическое решение этих ограничений MySQL: установите часовой пояс сеанса в UTC при подключении:

SET SESSION time_zone = '+0:00'

Тогда вы просто отправляете метки времени Unix, и все должно быть в порядке.

4 голосов
/ 15 февраля 2013

Эта тема меня бесит, поскольку мы используем TIMESTAMP столбцы с On UPDATE CURRENT_TIMESTAMP (то есть: recordTimestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP) для отслеживания измененных записей и ETL в хранилище данных.

Если кому-то интересно, в этом случае TIMESTAMP ведет себя правильно, и вы можете различить две одинаковые даты, конвертируя TIMESTAMP в метку времени unix:

select TestFact.*, UNIX_TIMESTAMP(recordTimestamp) from TestFact;

id  recordTimestamp         UNIX_TIMESTAMP(recordTimestamp)
1   2012-11-04 01:00:10.0   1352005210
2   2012-11-04 01:00:10.0   1352008810
3 голосов
/ 30 октября 2009

Но как мне сохранить что-нибудь до 01:30:00? -04: 00

?

Вы можете конвертировать в UTC как:

SELECT CONVERT_TZ('2009-11-29 01:30:00','-04:00','+00:00');


Еще лучше сохранить даты в виде поля TIMESTAMP . Это всегда хранится в UTC, и UTC не знает о летнем / зимнем времени.

Вы можете конвертировать из UTC в местное время, используя CONVERT_TZ :

SELECT CONVERT_TZ(UTC_TIMESTAMP(),'+00:00','SYSTEM');

Где '+00: 00' - это UTC, часовой пояс from, а 'SYSTEM' - местный часовой пояс ОС, в которой работает MySQL.

2 голосов
/ 01 апреля 2019

Mysql по своей природе решает эту проблему, используя таблицу time_zone_name из mysql db. Используйте CONVERT_TZ во время CRUD для обновления даты и времени, не беспокоясь о переходе на летнее время.

SELECT
  CONVERT_TZ('2019-04-01 00:00:00','Europe/London','UTC') AS time1,
  CONVERT_TZ('2019-03-01 00:00:00','Europe/London','UTC') AS time2;
1 голос
/ 09 июля 2015

Я работал над регистрацией количества посещений страниц и отображением их на графике (используя плагин Flot jQuery). Я заполнил таблицу тестовыми данными, и все выглядело хорошо, но я заметил, что в конце графика у точек был один выходной в соответствии с метками на оси X. После проверки я заметил, что счетчик просмотров за день 2015-10-25 дважды извлекался из базы данных и передавался во Flot, поэтому каждый день после этой даты перемещался на один день вправо.
После того, как я некоторое время искал ошибку в своем коде, я понял, что именно тогда наступает время перехода на летнее время. Тогда я пришел на эту страницу SO ...
... но предложенные решения были излишними для того, что мне было нужно, или у них были другие недостатки. Меня не очень беспокоит, что я не могу отличить неоднозначные временные метки. Мне просто нужно считать и отображать записи по дням.

Сначала я получаю диапазон дат:

SELECT 
    DATE(MIN(created_timestamp)) AS min_date, 
    DATE(MAX(created_timestamp)) AS max_date 
FROM page_display_log
WHERE item_id = :item_id

Затем, в цикле for, начиная с min_date, заканчивая max_date, с шагом в один день (60*60*24) я получаю значения:

for( $day = $min_date_timestamp; $day <= $max_date_timestamp; $day += 60 * 60 * 24 ) {
    $query = "
        SELECT COUNT(*) AS count_per_day
        FROM page_display_log
        WHERE 
            item_id = :item_id AND
            ( 
                created_timestamp BETWEEN 
                '" . date( "Y-m-d 00:00:00", $day ) . "' AND
                '" . date( "Y-m-d 23:59:59", $day ) . "'
            )
    ";
    //execute query and do stuff with the result
}

Мое окончательное и быстрое решение до моя проблема заключалась в следующем:

$min_date_timestamp += 60 * 60 * 2; // To avoid DST problems
for( $day = $min_date_timestamp; $day <= $max_da.....

Так что я не смотрю петлю в начале дня, а через два часа . День все тот же, и я все еще получаю правильные значения, поскольку я явно запрашиваю в базе данных записи между 00:00:00 и 23:59:59 дня, независимо от фактического времени метки времени. И когда время прыгает на один час, я все еще в правильном дне.

Примечание: я знаю, что это 5-летняя ветка, и я знаю, что это не ответ на вопрос ОП, но это может помочь таким людям, как я, которые столкнулись с этой страницей, в поисках решения описанной мной проблемы.

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