Поля даты и времени MySQL и летнее время — как мне указать «лишний» час?

StackOverflow https://stackoverflow.com/questions/1646171

Вопрос

Я использую часовой пояс Америки/Нью-Йорка.Осенью мы «откатываемся» на час — фактически «выигрываем» один час в 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).Но, похоже, в этом нет необходимости.

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

Это было полезно?

Решение

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

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

Часовой пояс моей системы - Америка / 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 будет по-прежнему сохранять и возвращать (только в строковой форме!) времена в течение " потерянного " час, когда начинается летнее время (например, 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

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

<Ол>
  • Установите системный часовой пояс на GMT + некоторое постоянное смещение. Например. UTC
  • Сохраняйте даты как INT (как обнаружил Аарон, TIMESTAMP даже ненадежен)

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

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

    Другие советы

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

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

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

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

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

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

    В двух словах я сейчас делаю так:

    При получении данных из базы данных:

    Явно интерпретируйте данные из базы данных как UTC вне MySQL, чтобы получить точную временную метку Unix.Для этого я использую функцию PHP strtotime() или ее класс DateTime.Это невозможно надежно сделать внутри MySQL с использованием функций MySQL CONVERT_TZ() или UNIX_TIMESTAMP(), поскольку CONVERT_TZ будет выводить только значение 'ГГГГ-ММ-ДД чч:мм:сс', которое страдает от проблем неоднозначности, а UNIX_TIMESTAMP() предполагает его ввод находится в системном часовом поясе, а не в часовом поясе, в котором данные ФАКТИЧЕСКИ хранились (UTC).

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

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

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

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

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

    SET SESSION time_zone = '+0:00'
    

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

    Этот поток меня разозлил, поскольку мы используем столбцы TIMESTAMP с в UPDATE CURRENT_TIMESTAMP (т.е.: отметка времени recordTimestamp NOT NULL DEFAULT CURRENT_TIMESTAMP в 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
    

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

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

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


    Еще лучше, сохраните даты как ТАЙМ-МЕТКА поле.Оно всегда хранится в формате UTC, а UTC не знает о летнем/зимнем времени.

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

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

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

    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;
    

    Я работал над регистрацией количества посещений страниц и отображением их на графике (используя плагин 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-летняя ветка, и я знаю, что это не ответ на вопрос ОП, но он может помочь таким людям, как я, которые столкнулись с этой страницей, найти решение описанной мной проблемы.

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top