Я использую часовой пояс Америки / Нью-Йорка. Осенью мы «отступаем» на час - фактически «набираем» один час в 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)
Это связано с тем, что вы не можете сохранить любое время в течение часа до окончания летнего времени . Независимо от того, как вы вводите даты, каждая функция даты будет обрабатывать это время, как если бы они были в течение часа после переключения.
Часовой пояс моей системы
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
Вывод: если вам нужно сохранять и извлекать каждый раз в году, у вас есть несколько нежелательных вариантов:
Сохраняйте даты как 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 01:30
.источник
select '2009-11-01 00:00:00' + INTERVAL 3600 SECOND;
имеет значение'2009-11-01 01:00:00'
. Затем UNIX_TIMESTAMP просто пытается скрыть это до UTC в контексте часового пояса сеанса - он не пытается выполнить добавление в контексте правил DST этого часового пояса.America/New_York
, я не вижу возможности сохранить 1257051600. А вы?Я понял это для моих целей. Я резюмирую то, что я узнал (извините, эти заметки многословны; они предназначены для моего будущего обращения, как и все остальное).
Вопреки тому , что я сказал в одном из моих предыдущих комментариев, DATETIME и TIMESTAMP поля действительно ведут себя по- разному. Поля TIMESTAMP (как указано в документации) принимают все, что вы им отправляете, в формате «ГГГГ-ММ-ДД чч: мм: сс» и преобразуют его из текущего часового пояса во время UTC. Обратное происходит прозрачно всякий раз, когда вы извлекаете данные. Поля DATETIME не выполняют это преобразование. Они берут все, что вы им отправляете, и просто хранят это напрямую.
Ни типы полей DATETIME, ни TIMESTAMP не могут точно хранить данные в часовом поясе, который соблюдает DST . Если вы сохраняете «2009-11-01 01:30:00», в полях не будет возможности отличить, какая версия 1:30 вам нужна - версия -04: 00 или -05: 00.
Хорошо, поэтому мы должны хранить наши данные в часовом поясе без летнего времени (например, в UTC). Поля TIMESTAMP не могут точно обрабатывать эти данные по причинам, которые я объясню: если ваша система настроена на часовой пояс DST, то то, что вы вводите в TIMESTAMP, может быть не тем, что вы получите обратно. Даже если вы отправите ему данные, которые вы уже преобразовали в UTC, он все равно примет данные в вашем местном часовом поясе и выполнит еще одно преобразование в UTC. Этот принудительный TIMESTAMP-обратный переход от локального к UTC-обратно к локальному происходит с потерями, когда в вашем часовом поясе наблюдается летнее время (поскольку «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 не работают должным образом в пределах границ летнего времени, если вы храните свои даты в 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
источник
Я думаю, что ссылка micahwittman предлагает лучшее практическое решение этих ограничений MySQL: установите часовой пояс сеанса в UTC при подключении:
SET SESSION time_zone = '+0:00'
Затем вы просто отправляете ему временные метки Unix, и все должно быть в порядке.
источник
Этот тред сделал меня уродом, поскольку мы используем
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
источник
Вы можете преобразовать в 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, часовой пояс от, а "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
шагом one day (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-летняя ветка, и я знаю, что это не ответ на вопрос OP, но это может помочь людям вроде меня, которые столкнулись с этой страницей, в поисках решения описанной мной проблемы.
источник
"SELECT CAST(created_timestamp AS date) day,COUNT(*) WHERE item_id=:item_id AND (created_timestamp BETWEEN '".date("Y-m-d 00:00:00", $min_date_timestamp)."' AND '".date("Y-m-d 23:59:59", $max_date_timestamp)."') GROUP BY day ORDER BY day";