В проекте нужно хранить только календарную дату. Чем рискован выбор TIMESTAMP вместо DATE?
Если значение описывает только календарную дату, следует использовать DATE. TIMESTAMP дополнительно допускает время и, в вариантах с часовым поясом, связывает значение с правилами преобразования часовых поясов; это может привести к ошибкам при сравнении, фильтрации и отображении даты.
В SQL разные типы даты и времени появились для разделения разных предметных смыслов: календарной даты, времени суток и момента или локальной даты со временем. Такое разделение предотвращает смешивание, например, дня рождения и момента проведения платежа.
Проблема возникла из-за того, что внешне похожие значения требуют разной точности и разных правил интерпретации. Тип данных должен отражать смысл значения, а не только удобный формат его отображения.
Календарная дата не содержит час, минуту, секунду или часовой пояс. Если хранить её как TIMESTAMP, в значение может случайно попасть время по умолчанию, а преобразование между часовыми поясами способно изменить отображаемый день.
Это особенно опасно для дат рождения, сроков действия документов, бухгалтерских периодов и праздничных дней. Запросы на равенство и диапазоны также могут стать менее очевидными: условие по дате придётся согласовывать со временем и точностью дробных секунд.
DATE хранит компоненты календарной даты: год, месяц и день. Он не выражает момент на временной шкале, поэтому к нему не следует приписывать часовой пояс или скрытое время.
TIMESTAMP WITHOUT TIME ZONE хранит дату и время без часового пояса. Это не делает его подходящей заменой DATE: он всё равно допускает временную часть, а приложение или СУБД могут по-разному трактовать её при преобразованиях.
TIMESTAMP WITH TIME ZONE, если такой вариант поддерживается СУБД, предназначен для значений, связанных со временем и часовым поясом или моментом. Его отображение может зависеть от часового пояса сеанса, поэтому одна и та же временная точка способна показываться разными локальными датами.
Минимальный пример различия смыслов:
В первой колонке значение 1990-05-20 не содержит времени. Во второй колонке 2025-05-20 09:30:00 описывает дату вместе со временем, поэтому сравнение только по календарному дню требует отдельной логики, зависящей от СУБД.
Использование DATE не решает все проблемы автоматически: приложение всё равно должно валидировать формат ввода и не выполнять неявные преобразования в timestamp. Для момента события обычно выбирают timestamp-тип, а для локального расписания без привязки к моменту — тип даты и времени без часового пояса, если он соответствует модели данных.
Сервис хранит дату окончания подписки, например 31 декабря. Один вариант — сохранять её как timestamp в полночь. Плюс такого подхода — единообразие с другими временными полями; минусы — скрытое время, риск ошибок на границе суток и возможное изменение даты при преобразовании часового пояса.
Второй вариант — хранить дату как DATE, а момент фактического продления — отдельным timestamp-полем. Он требует явно преобразовать дату в момент, когда это действительно необходимо, зато сохраняет исходный бизнес-смысл и упрощает проверки периода.
Практически выбирают второй вариант: дата окончания остаётся DATE, а операции, требующие временной точки, используют отдельное поле с документированным часовым поясом. В результате фильтрация по календарным дням не зависит от часового пояса сервера или пользователя.
Вопрос: Можно ли считать TIMESTAMP WITHOUT TIME ZONE моментом, одинаковым для всех часовых поясов?
Ответ: Нет. Такой тип хранит дату и время без сведения о часовом поясе и сам по себе не определяет однозначную точку на временной шкале. Чтобы получить момент, нужно дополнительно знать, в каком часовом поясе задано локальное время; без этого интерпретация остаётся неполной.
Вопрос: Почему хранение даты окончания как полуночи может дать ошибку при проверке срока действия?
Ответ: Если срок должен действовать весь календарный день, timestamp полуночи обычно означает начало этого дня. Проверка по условию строго меньше этой полуночи следующего дня может быть корректной, но проверка до полуночи текущего дня преждевременно исключит сам день окончания. У DATE граница выражается календарно и не требует искусственного выбора времени 00:00:00.
Вопрос: Достаточно ли заменить тип столбца с TIMESTAMP на DATE, чтобы устранить ошибки часовых поясов?
Ответ: Нет. При преобразовании нужно определить, какая дата должна сохраниться: календарная дата исходного значения или дата после перевода момента в конкретный часовой пояс. Для timestamp с часовым поясом один момент может соответствовать разным локальным датам, поэтому безопасная миграция требует явно зафиксировать целевой часовой пояс и проверить пограничные значения.