Допустим, платёж прошёл 10 ноября 2026 года в 23:30 UTC. В отчёте по UTC его сумма попадёт в 10 ноября. В часовом поясе UTC+7 уже 11 ноября, 06:30. Поэтому два отчёта могут показать разные суммы по дням и одинаковую общую сумму, если в обоих выбран один и тот же набор платежей.
С этим легко столкнуться в PostgreSQL, если группировать платежи через paid_at::date. Когда paid_at имеет тип timestamptz, результат зависит от часового пояса текущей сессии. Один запрос, запущенный из двух подключений с разными настройками, может разнести платежи по разным дням.
Проверим на трёх условных успешных платежах. Все суммы в рублях. Для примера понадобится PostgreSQL и отдельное тестовое подключение. SET TIME ZONE меняет настройку этого подключения, а временная таблица существует до его закрытия. Все команды выполняйте в нём же.
CREATE TEMP TABLE payments ( id integer PRIMARY KEY, paid_at timestamptz NOT NULL, amount numeric(12, 2) NOT NULL ); INSERT INTO payments VALUES (1, '2026-11-10 16:59:00+00', 100), (2, '2026-11-10 17:00:00+00', 200), (3, '2026-11-10 23:30:00+00', 300); SET TIME ZONE 'UTC'; SELECT paid_at::date AS report_day, sum(amount) AS paid_total FROM payments GROUP BY 1 ORDER BY 1;
Получится одна строка за 10 ноября с суммой 600 ₽. Теперь в том же подключении выполните SET TIME ZONE 'Asia/Krasnoyarsk' и повторите SELECT. За 10 ноября останется 100 ₽, за 11 ноября появится 500 ₽. Все три платежа остались в таблице, поменялось правило определения даты.
Для рабочего отчёта я бы явно задавал часовой пояс до группировки.
SELECT (paid_at AT TIME ZONE 'Asia/Krasnoyarsk')::date AS report_day, sum(amount) AS paid_total FROM payments GROUP BY 1 ORDER BY 1;
Этот запрос даст одинаковые даты при обеих настройках сессии. AT TIME ZONE переводит момент платежа в местное время выбранного пояса, а приведение к date берёт его календарную дату.
Платёж | Время UTC | Время UTC+7 | День отчёта UTC+7 |
|---|---|---|---|
1 | 10 ноября, 16:59 | 10 ноября, 23:59 | 10 ноября |
2 | 10 ноября, 17:00 | 11 ноября, 00:00 | 11 ноября |
3 | 10 ноября, 23:30 | 11 ноября, 06:30 | 11 ноября |
То же правило нужно использовать в фильтре. Для 11 ноября по времени Красноярска границы можно задать так.
SELECT sum(amount) AS paid_total FROM payments WHERE paid_at >= timestamp '2026-11-11 00:00:00' AT TIME ZONE 'Asia/Krasnoyarsk' AND paid_at < timestamp '2026-11-12 00:00:00' AT TIME ZONE 'Asia/Krasnoyarsk';
Результат — 500 ₽. Нижняя граница включена, верхняя исключена, поэтому платёж ровно в полночь попадёт в один день. Обе полуночи заданы в часовом поясе отчёта. Это пригодится и для поясов с переводом часов, где календарные сутки могут отличаться от 24 часов.
В примере paid_at — время успешного платежа. Если в одном отчёте взять время платежа, а в другом время получения webhook, расхождения останутся и после настройки часового пояса. Для сверки нужны одинаковые событие, часовой пояс и границы периода. Ну и желательно вывести эти условия рядом с датой выгрузки. Как-то так…
