date_trunc в PostgreSQL: группировка по дням нужного часового пояса

date_trunc в PostgreSQL: группировка по дням нужного часового пояса
Содержание статьи

Перед группировкой событий по дням определите, чьи это календарные дни. Одно и то же мгновение может относиться к разным датам в UTC и в часовом поясе отчёта. В практикуме Mentoring.digital сначала переводим timestamptz в местное время, затем отбрасываем время внутри суток.

Задайте часовой пояс в выражении

Для timestamptz операция AT TIME ZONE возвращает местное время без часового пояса. После этого date_trunc с точностью day округляет его к началу местного дня. Приведение к date оставляет удобную метку группы.

WITH events(happened_at) AS (
  VALUES
    (TIMESTAMPTZ '2026-01-10 20:59:00+00'),
    (TIMESTAMPTZ '2026-01-10 21:00:00+00'),
    (TIMESTAMPTZ '2026-01-11 08:00:00+00')
)
SELECT
  date_trunc(
    'day', happened_at AT TIME ZONE 'Europe/Moscow'
  )::date AS local_day,
  count(*) AS event_count
FROM events
GROUP BY local_day
ORDER BY local_day;

Для этих учебных значений ожидаются две группы: 10 января с одним событием и 11 января с двумя. Событие в 21:00 UTC уже попадает на полночь следующего московского дня. Проверьте результат в своей тестовой PostgreSQL.

Не полагайтесь на случайную настройку сеанса

Вариант date_trunc('day', happened_at) для timestamptz использует часовой пояс сеанса. Если отчёты запускаются разными клиентами, эта зависимость может оказаться незаметной.

У date_trunc есть и вариант с явным третьим аргументом часового пояса для timestamptz. Он возвращает timestamptz, поэтому отображаемая строка результата всё равно зависит от настройки сеанса. Выбирайте, что нужно дальше: мгновение начала дня или местная дата как подпись группы.

Фильтруйте период по двум местным границам

Для одного местного дня удобно использовать полуоткрытый интервал: начало включительно, следующая полночь не включительно. Так событие на границе не попадает сразу в два соседних отчёта.

WHERE happened_at >=
  (TIMESTAMP '2026-01-11 00:00:00'
   AT TIME ZONE 'Europe/Moscow')
  AND happened_at <
  (TIMESTAMP '2026-01-12 00:00:00'
   AT TIME ZONE 'Europe/Moscow')

Этот фрагмент добавляется к запросу таблицы, в которой есть happened_at. Здесь AT TIME ZONE применяется к timestamp без зоны и, наоборот, получает соответствующее мгновение timestamptz. Учитывайте исходный тип: одно название оператора выполняет разные преобразования.

Проверка на границе дня

Добавьте события ровно перед нижней границей, на ней и ровно на верхней. Затем повторите упражнение с другим поясом отчёта. Для зон с сезонным переводом времени вычисляйте обе местные полуночи отдельно, не подменяйте следующий календарный день безусловными 24 часами.

Если исходный столбец имеет тип timestamp без зоны, сначала установите, что означают записанные значения. SQL не восстановит утраченный контекст автоматически. Такие задачи можно разобрать на занятиях по SQL и проверить с наставником по базам данных.

Похожие статьи по данной теме