Перейти к содержанию
Innovialabs

Даты и часовые пояса в коде: UTC, ISO 8601 и типичные ошибки

Практика4 мин

Коротко

Храните момент времени в UTC, передавайте его строкой ISO 8601 со смещением, а в локальное время переводите только при показе пользователю по IANA-зоне вроде Asia/Tashkent.

Короткий ответ

Почти все баги с датами возникают из-за того, что код путает два разных понятия:

  • момент времени (instant) — точка на временной шкале, одинаковая для всего мира: «заказ создан»;
  • календарная дата или локальное время — то, что видит человек на часах в своём городе: «день рождения 5 марта», «встреча в 10:00 по Ташкенту».

Правило простое: моменты храните в UTC, передавайте в формате ISO 8601 с явным смещением, а в локальное время переводите только на последнем шаге — при показе пользователю. Календарные даты без времени храните как дату, а не как полночь в UTC.

ISO 8601: единый формат обмена

ISO 8601 — стандарт записи дат, который понимают все языки и базы данных:

ЗначениеПример
Дата2026-03-05
Момент в UTC2026-03-05T07:30:00Z
Момент со смещением2026-03-05T12:30:00+05:00

Буква Z означает UTC. Строка без Z и без смещения (2026-03-05T12:30:00) — это локальное время неизвестно какой зоны. В API такие строки лучше не принимать вовсе.

Смещение — не часовой пояс

+05:00 — это смещение, а Asia/Tashkent — IANA-зона: она хранит историю правил, включая переходы на летнее время (DST) и изменения законов. В Ташкенте сейчас нет перехода на летнее время, но у ваших пользователей в Европе или США он есть. Если вы сохранили только смещение, вы не сможете правильно вычислить «тот же час через неделю» после перевода часов.

Поэтому для повторяющихся событий и расписаний храните локальное время плюс имя зоны, а для фактов, которые уже произошли, — момент в UTC.

Типичные ошибки

  • Сдвиг на один день. Дату рождения сохранили как 2026-03-05T00:00:00Z, а пользователь в зоне с отрицательным смещением видит 4 марта. Решение: тип «дата» без времени.
  • Парсинг строки в JavaScript. new Date("2026-03-05") разбирается как полночь UTC, а new Date("2026-03-05T00:00") — как полночь в зоне браузера. Разница в один символ меняет результат.
  • Месяцы с нуля. В Date из JS январь — это 0.
  • Арифметика «плюс 24 часа» вместо «плюс 1 день». В день перевода часов сутки длятся 23 или 25 часов.
  • Время сервера. Сервер в другой зоне, чем разработчик, — и отчёт «за сегодня» начинается не в полночь. Держите сервер и базу в UTC.
  • Сравнение строк с разными смещениями. 12:30+05:00 и 07:30Z — один и тот же момент, но как строки они не равны.

Как это делать в коде

Показ в нужной зоне в JavaScript — встроенный Intl:

const d = new Date("2026-03-05T07:30:00Z");
new Intl.DateTimeFormat("ru-RU", {
  timeZone: "Asia/Tashkent",
  dateStyle: "medium",
  timeStyle: "short",
}).format(d); // 5 мар. 2026 г., 12:30

Для арифметики и форматирования удобны date-fns и пакет date-fns-tz (formatInTimeZone, fromZonedTime). В Python есть стандартный модуль zoneinfo:

from datetime import datetime, timezone
from zoneinfo import ZoneInfo

now = datetime.now(timezone.utc)               # момент в UTC
local = now.astimezone(ZoneInfo("Asia/Tashkent"))
print(local.isoformat())

Избегайте «наивных» datetime без tzinfo: их нельзя корректно сравнивать с осознанными (aware) значениями.

Чек-лист для API и баз данных

  1. В базе моменты храните в типе с зоной (в PostgreSQL — timestamptz), а сессию подключения держите в UTC.
  2. Календарные даты — в типе date, а не timestamp.
  3. API принимает и отдаёт ISO 8601 с Z или смещением; строки без смещения отклоняйте.
  4. Для расписаний храните локальное время и IANA-имя зоны отдельными полями.
  5. Зону пользователя берите из профиля или браузера (Intl.DateTimeFormat().resolvedOptions().timeZone), а не угадывайте по IP.
  6. Границы периодов («за день», «за месяц») вычисляйте в зоне пользователя, затем переводите в UTC для запроса.
  7. Пишите тесты на даты рядом с переходом на летнее время и на конец месяца.
  8. Не храните даты как Unix-время в секундах в одном месте и в миллисекундах в другом — договоритесь об одном варианте.

Частые вопросы

Пользователи, серверы и внешние сервисы со временем окажутся в других зонах. UTC в хранилище ничего не стоит сейчас, а миграция данных потом обходится дорого.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.