В JavaScript объект Date хранит момент времени в виде
количества миллисекунд с начала эпохи Unix (UTC). Вся работа с локальным
отображением времени происходит на уровне среды выполнения и не содержит
информации о конкретном часовом поясе в самом объекте. Это создаёт
ключевую проблему при парсинге строк дат с указанием временных зон:
теряется контекст исходной зоны, если не использовать специализированные
инструменты.
Библиотека date-fns предоставляет набор функций для
работы с датами, но не включает полноценную поддержку часовых поясов.
Для этого используется дополнительный пакет date-fns-tz,
который расширяет базовые возможности и вводит операции преобразования
между UTC и IANA-зонами.
При создании даты через стандартный конструктор:
const date = new Date("2026-01-01T10:00:00");
строка интерпретируется в зависимости от среды выполнения. Если
указана зона Z, то она трактуется как UTC:
const date = new Date("2026-01-01T10:00:00Z");
Результат всегда хранится в UTC-форме, а отображение зависит от локальной зоны системы.
Функция parse из date-fns работает только с
локальной интерпретацией времени:
import { parse } from "date-fns";
const date = parse("2026-01-01 10:00", "yyyy-MM-dd HH:mm", new Date());
Здесь отсутствует возможность указать часовую зону. Любые попытки включить её в строку не приводят к корректному смещению времени.
Ключевая проблема:
Для корректного парсинга используется пакет
date-fns-tz.
zonedTimeToUtc — перевод локального времени в UTC с
учётом зоныtoZonedTime — преобразование UTC в локальное время
указанной зоныformatInTimeZone — форматирование даты в заданной
зонеФункция преобразует локальное время, заданное в конкретной зоне, в UTC:
import { zonedTimeToUtc } from "date-fns-tz";
const utcDate = zonedTimeToUtc(
"2026-01-01 10:00:00",
"Europe/Berlin"
);
Механизм работы:
import { toZonedTime } from "date-fns-tz";
const utcDate = new Date("2026-01-01T09:00:00Z");
const berlinTime = toZonedTime(utcDate, "Europe/Berlin");
Результат — объект Date, который при форматировании
будет отображаться как время в указанной зоне.
Важно учитывать:
Date остаётся UTCНаиболее безопасный способ отображения времени в конкретной зоне:
import { formatInTimeZone } from "date-fns-tz";
const result = formatInTimeZone(
new Date("2026-01-01T09:00:00Z"),
"Europe/Berlin",
"yyyy-MM-dd HH:mm:ss"
);
Поведение:
Корректный вариант:
const date = new Date("2026-01-01T10:00:00Z");
Такой формат однозначен и не требует дополнительных библиотек.
const date = new Date("2026-01-01T10:00:00+03:00");
Механизм:
const date = new Date("2026-01-01T10:00:00");
Интерпретация зависит от локальной среды выполнения, что делает результат неоднозначным при распределённых системах.
import { parse } from "date-fns";
const date = parse(
"2026-01-01 10:00 Europe/Berlin",
"yyyy-MM-dd HH:mm zzz",
new Date()
);
Результат не будет корректно учитывать временную зону, поскольку
date-fns не выполняет IANA-резолвинг.
Структура обработки обычно разделяется на этапы:
Все временные метки приводятся к UTC:
const utc = zonedTimeToUtc("2026-01-01 10:00", "Asia/Tokyo");
UTC-значения безопасны для:
const view = formatInTimeZone(
utc,
"Europe/Berlin",
"dd.MM.yyyy HH:mm"
);
IANA-зоны учитывают переходы на летнее время автоматически.
Пример:
zonedTimeToUtc("2026-07-01 10:00:00", "Europe/Berlin");
zonedTimeToUtc("2026-01-01 10:00:00", "Europe/Berlin");
Разница смещения будет вычислена автоматически:
+03:00
Europe/Berlin
date-fns используется для:
date-fns-tz используется для:
new Date("2026-01-01 10:00");
Результат зависит от окружения.
toZonedTime(zonedTimeToUtc(...))
Приводит к искажению времени из-за повторного применения смещения.
parse("2026-01-01 10:00", ...)
Не учитывает IANA-зону вообще.
Date или ISO string с
Z)zonedTimeToUtcformatInTimeZonedate-fns без зональных преобразований