Часовые пояса и временные метки
Хранение времени в UTC, преобразование часовых поясов и вопросы о временных метках, которые задают на собеседованиях
«Часовые пояса и временные метки» — бесплатный урок Coding Interview Prep на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Coding Interview Prep, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Coding Interview Prep содержит 4 уроков всего.
Почему часовые пояса ставят кандидатов в тупик
На часовых поясах даже уверенные кандидаты часто теряются, поэтому интервьюеры проверяют эту тему, чтобы понять глубину знаний. Главный вопрос всегда один: «как хранить и сравнивать временные метки в разных регионах?»
Профессиональный ответ — это дисциплина, а не функция: храните всё в UTC, а преобразование выполняйте только на границах системы при отображении. Правильно выберите модель хранения — и большинство запросов станет простым.
- временная метка и временная метка с часовым поясом
- Преобразование между часовыми поясами
- UTC как эталонный источник данных
Временная метка и временная метка с часовым поясом
В PostgreSQL есть два типа временных меток, и их смешение — одна из самых распространенных ошибок на собеседованиях.
timestamp(без часового пояса): значение показаний часов, к которому не привязан часовой пояс. Сохраняется ровно то, что было передано.timestamptz(с часовым поясом): внутри хранится в UTC; при вводе значение преобразуется из часового пояса сеанса, а при выводе преобразуется обратно.
Несмотря на название, timestamptz не хранит часовой пояс; он хранит точный момент времени в UTC. Эта деталь производит хорошее впечатление на интервьюеров.
CREATE TABLE events (
id bigint,
occurred_at timestamptz -- recommended: an absolute instant
);Храните UTC, преобразуйте на границах системы
Главное правило. Сохраняйте моменты времени в UTC (используйте timestamptz), а в местный часовой пояс преобразуйте только при отображении данных пользователю. Это устраняет неоднозначность, связанную с переходами на летнее и зимнее время, и позволяет везде правильно упорядочивать данные по времени.
Если Вас спросят «почему UTC?», ответьте: в UTC нет переходов на летнее и зимнее время, поэтому одно и то же показание местных часов никогда не повторяется и не пропускается, в отличие от местного времени.
-- Display a UTC instant in a user's zone (Postgres)
SELECT occurred_at AT TIME ZONE 'America/New_York' AS local_time
FROM events;Двойное значение AT TIME ZONE
AT TIME ZONE — хитрый оператор и частый источник ошибок, потому что в зависимости от типа входных данных он выполняет два противоположных действия:
- Для
timestamptzон преобразует абсолютный момент в эту часовую зону и возвращает обычныйtimestamp(показания часов в этой зоне). - Для обычного
timestampон интерпретирует эти показания часов как время в этой зоне и возвращаетtimestamptz.
Весь секрет — понимать, в каком направлении выполняется преобразование.
-- timestamptz -> local wall clock (returns timestamp)
SELECT TIMESTAMPTZ '2024-03-01 12:00:00+00'
AT TIME ZONE 'Asia/Tokyo'; -- 2024-03-01 21:00:00
-- plain timestamp interpreted in a zone (returns timestamptz)
SELECT TIMESTAMP '2024-03-01 12:00:00'
AT TIME ZONE 'Asia/Tokyo'; -- 2024-03-01 03:00:00+00Получение текущего момента
Важно понимать, что возвращают функции для получения текущего времени. NOW() и CURRENT_TIMESTAMP возвращают в Postgres значение типа timestamptz. Они возвращают время начала транзакции, а не оператора, что важно при длительных транзакциях.
Чтобы явно получить UTC, выполните преобразование: NOW() AT TIME ZONE 'UTC'. В MySQL функция UTC_TIMESTAMP() сразу возвращает время UTC.
SELECT
NOW() AS tx_start_tz,
NOW() AT TIME ZONE 'UTC' AS utc_walltime;Переходы на летнее время — настоящий враг
Интервьюеры любят пограничные случаи, связанные с DST. При переходе на летнее время локальный час не существует; при возврате на стандартное время час повторяется. Хранение местного времени делает такие значения неоднозначными или недопустимыми.
Хранение UTC полностью устраняет эту проблему: каждый момент уникален и упорядочен монотонно. Указание региона, например 'America/New_York', а не фиксированного смещения вроде -05:00, позволяет базе данных правильно применять правила DST для любой даты.
-- Region name applies DST automatically for the given date
SELECT TIMESTAMPTZ '2024-07-01 12:00:00+00'
AT TIME ZONE 'America/New_York' AS summer, -- EDT (-04)
TIMESTAMPTZ '2024-01-01 12:00:00+00'
AT TIME ZONE 'America/New_York' AS winter; -- EST (-05)Группировка по местному дню в разных часовых зонах
Реалистичная задача: «ежедневно активные пользователи в местном времени каждого пользователя». Если напрямую усечь метку времени UTC, границы полуночи будут неверными для пользователей не из зоны UTC.
Преобразуйте время в часовую зону пользователя до усечения до дня. Преобразование смещает показания часов так, чтобы границы дня совпадали с местными.
SELECT
DATE_TRUNC('day', occurred_at AT TIME ZONE u.tz) AS local_day,
COUNT(DISTINCT e.user_id) AS dau
FROM events e
JOIN users u ON u.id = e.user_id
GROUP BY 1
ORDER BY 1;Безопасное сравнение меток времени
При фильтрации по столбцу timestamptz сравнивайте его с явным моментом времени — в идеале с литералом UTC или с timestamptz со смещением. Сравнение с обычной строкой может привести к её интерпретации в непредсказуемой часовой зоне сеанса.
Так сравнение остаётся однозначным независимо от того, кто выполняет запрос.
SELECT *
FROM events
WHERE occurred_at >= TIMESTAMPTZ '2024-03-01 00:00:00+00'
AND occurred_at < TIMESTAMPTZ '2024-04-01 00:00:00+00';Эпоха и метки времени Unix
Многие системы хранят время как эпоху Unix — число секунд с 1970-01-01 UTC. На собеседовании Вам могут дать числовой столбец и попросить преобразовать его в дату и время.
- Postgres:
TO_TIMESTAMP(epoch_seconds)возвращает значение типаtimestamptz. - Обратное преобразование в эпоху:
EXTRACT(EPOCH FROM occurred_at). - MySQL:
FROM_UNIXTIME()иUNIX_TIMESTAMP().
Значения эпохи по своей сути относятся к UTC, что отчасти объясняет их популярность для хранения.
SELECT
TO_TIMESTAMP(1709294400) AS as_ts, -- from epoch
EXTRACT(EPOCH FROM NOW())::bigint AS as_epoch; -- to epochЗаметки о часовых зонах в разных диалектах
Краткая карта, которая поможет Вам уверенно работать в любой среде:
- Postgres:
timestamptzиAT TIME ZONEобеспечивают наиболее широкие возможности. - MySQL:
TIMESTAMPавтоматически преобразуется через сеансtime_zone;CONVERT_TZ(t, from, to)выполняет явное преобразование. УDATETIMEнет сведений о часовой зоне. - SQL Server:
datetimeoffsetхранит смещение;AT TIME ZONE 'name'выполняет преобразование с использованием названий зон Windows.
-- MySQL explicit conversion
SELECT CONVERT_TZ(event_dt, 'UTC', 'Europe/Istanbul') AS local_dt
FROM events;Более глубокий пример: сеансы, переходящие через полночь
Тонкий вопрос по отчётности: как посчитать сеансы по местному календарному дню, если сеанс может перейти через полночь? Решение основано на том же принципе: преобразуйте время в местное, а затем распределите его по группам.
Храните начало и конец как timestamptz; для отчётности определяйте местный день по преобразованному времени начала. Если сеанс нужно разделить между двумя днями, соедините данные с таблицей последовательных дней — это отличный момент, который стоит упомянуть как дополнительный вопрос.
SELECT
DATE_TRUNC('day', started_at AT TIME ZONE 'Europe/Istanbul') AS local_day,
COUNT(*) AS sessions
FROM sessions
GROUP BY 1
ORDER BY 1;Проверка
Подтвердите рекомендуемую стратегию хранения и объясните почему.
Итоги: часовые зоны и метки времени
Главный принцип, который следует запомнить:
- Храните UTC как
timestamptz; преобразуйте его в именованную часовую зону только для отображения. timestamptzхранит момент времени в UTC, а не часовую зону, несмотря на название.AT TIME ZONEработает в обоих направлениях в зависимости от типа входных данных: преобразует timestamptz в местное показание часов или интерпретирует обычный timestamp как время в указанной зоне.- Используйте названия регионов, например
'America/New_York', чтобы DST применялся автоматически; избегайте фиксированных смещений. - Преобразуйте время в местное до усечения до дня и сравнивайте столбцы с явно заданными моментами времени UTC.
Часто задаваемые вопросы
Урок «Часовые пояса и временные метки» бесплатный?
Да — полный текст урока «Часовые пояса и временные метки» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Coding Interview Prep, подпишись на CoddyKit PRO. Курс Coding Interview Prep содержит 4 уроков всего.
Чему я научусь в уроке «Часовые пояса и временные метки»?
Хранение времени в UTC, преобразование часовых поясов и вопросы о временных метках, которые задают на собеседованиях Ты практикуешь Coding Interview Prep с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Coding Interview Prep?
Предыдущий опыт не требуется. Coding Interview Prep на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Часовые пояса и временные метки»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Coding Interview Prep?
Да. Каждый урок Coding Interview Prep включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Арифметика дат и интервалы
- Округление дат и распределение по интервалам
- Разбор и форматирование строк
- Часовые пояса и временные метки