#Что означает ошибка и когда она возникает
Приложение Node.js в контейнере может отображать локальное время не так, как ожидают пользователи: по умолчанию многие контейнеры используют UTC. При этом Date хранит момент времени независимо от зоны, toISOString() всегда показывает UTC, а Intl.DateTimeFormat может форматировать его в нужной зоне. Инструкция для расхождения отображаемого времени, а не для «исправления» абсолютных меток UTC.
#Как отличить возможные причины
| Возможная причина | Как узнать | Что делать |
|---|---|---|
| Окружение контейнера использует UTCВ переменных окружения контейнера отсутствует TZ или она не соответствует ожидаемой зоне. | Intl.DateTimeFormat().resolvedOptions().timeZone показывает UTC, хотя пользователи ожидают иной пояс. | Явно задавать IANA timeZone при форматировании или согласованно TZ сервиса. |
| В коде перепутаны UTC и местное времяПриложение ожидает локальный час от new Date().toISOString(), который всегда выдаёт UTC. | Метка заканчивается на Z, время расходится ровно на смещение. | Оставлять хранение UTC, для экрана использовать Intl.DateTimeFormat(timeZone). |
| Приложение и база данных имеют разные правила зоныБаза или внешний сервис возвращает строку без зоны, и приложение воспринимает её как локальную. | Сдвиг только для значений из БД, тогда как Date.now корректен. | Уточнить формат и часовой пояс исходного значения, не прибавлять часы вручную. |
#Пошаговая диагностика и устранение
- Уточните, какое именно время неверноеОткройте страницу приложения или журнал события и сравните одну и ту же дату с ожидаемым временем и UTC. Проверьте, есть ли у временной метки суффикс Z или явное смещение, например +03:00. Не меняйте системные часы хоста, если расхождение относится только к отображению.
- Откройте каталог своего Compose-проектаНа хосте откройте терминал и перейдите в каталог compose.yaml. Выполните
docker compose ps, чтобы узнать название сервиса с Node.js. Для примера далее используется имяapp; в своём проекте подставляйте точное имя изdocker compose config --services. Docker Engine может требовать предоставленного администратором доступа; не используйте sudo автоматически. - Посмотрите текущую зону из самого процесса Node.jsВ терминале проекта выполните
docker compose exec app node -e "console.log(process.env.TZ, Intl.DateTimeFormat().resolvedOptions().timeZone, new Date().toISOString())". Замените app реальным сервисом. Вы увидите значение TZ или undefined, определённую Intl зону и UTC-строку с Z. Даже при правильной зоне ISO останется UTC — это нормально. - Проверьте форматирование с явно указанной зонойДля проверки без изменения контейнера выполните команду из блока ниже с
timeZone: "Europe/Tallinn"(это только пример допустимого имени IANA). Вывод должен показывать локальное время выбранной зоны. Если форматирование правильное, лучше передавать нужную зону пользователя прямо в форматтер, а не менять глобальные часы приложения. - Найдите место, где рассчитывают или форматируют времяВ исходниках Node.js поищите
toISOString(,toLocaleString(,Intl.DateTimeFormat,new Date(и преобразование строк из БД. Если на экран выводят toISOString, замените только отображение на локализованное через Intl.DateTimeFormat с явным timeZone. Сами UTC-метки событий в базе не переписывайте. - Если нужна единая зона сервиса, задайте TZ контролируемоСоздайте резервную копию compose.yaml; в записи
services: app:добавьте в environment параметрTZ: Europe/Tallinnлибо собственный IANA идентификатор. Применяйте это только если по требованиям приложения весь процесс должен использовать одну зону. Выполнитеdocker compose config -qдля проверки; не вставляйте секреты в текст отчёта. - Пересоздайте только нужный сервис в согласованное времяНа тестовом окружении выполните
docker compose up -d appпосле проверки конфигурации: Compose может пересоздать контейнер, это на время прервёт запросы. На рабочем сервере согласуйте время и резервные копии. Если используется Kubernetes или собственный запуск docker run, меняйте переменные соответствующим способом, а не только в Compose. - Проверьте смену летнего/зимнего времени и откатСнова выполните команду диагностики внутри app и проверьте форматирование двух заранее известных дат зимой и летом с указанной зоной IANA. Если форматирование неверно только в старом образе, проверьте версию Node.js/ICU и системную базу зон, не подменяйте смещения вручную. Для отката восстановите compose.yaml и пересоздайте сервис в согласованное окно.
- Команды для отдельных ситуацийВыберите только подходящий вариант. Это не одна последовательность: часть команд диагностирует проблему, а часть меняет настройки или данные.
#Минимальный пример и проверка на тестовом проекте
services:
app:
image: node:24-alpine
environment:
TZ: Europe/Tallinnconst instant = new Date("2026-01-15T12:00:00Z");
const formatted = new Intl.DateTimeFormat("ru-RU", {
timeZone: "Europe/Tallinn", dateStyle: "full", timeStyle: "short"
}).format(instant);
console.log(formatted);
console.log(instant.toISOString());#Проверка результата и действия при неуспехе
Для выбранной даты время в интерфейсе соответствует нужной зоне, UTC-метки сохраняют точный момент, а зимняя и летняя дата отображаются с корректным смещением. Если приложение продолжает выдавать старую зону, проверьте, какой контейнер и процесс фактически обслуживает запрос, и перезапущен ли он после изменения.