Допустим, платёж прошёл 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_totalFROM paymentsGROUP BY 1ORDER 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_totalFROM paymentsGROUP BY 1ORDER 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_totalFROM paymentsWHERE 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, расхождения останутся и после настройки часового пояса. Для сверки нужны одинаковые событие, часовой пояс и границы периода. Ну и желательно вывести эти условия рядом с датой выгрузки. Как-то так…
ссылка на оригинал статьи https://habr.com/ru/articles/1106548/