UTC (Coordinated Universal Time) используется как базовая временная
шкала для вычислений, хранения и передачи дат в большинстве серверных и
распределённых систем. В JavaScript работа с UTC часто вызывает
сложности из-за смешения локального времени, системной таймзоны и
внутреннего представления Date как количества миллисекунд
от эпохи Unix.
Библиотека date-fns предоставляет набор функций для работы с датами, но важно понимать ключевое ограничение: ядро библиотеки не реализует полноценную работу с таймзонами. Для операций с UTC и конвертаций используется отдельное расширение — date-fns-tz.
Встроенный объект Date хранит момент времени в виде
количества миллисекунд с 1 января 1970 года UTC. Это означает:
getHours(), getDate() и подобные
возвращают локальное времяgetUTCHours(), getUTCDate()
работают в UTCconst d = new Date("2026-01-01T12:00:00Z");
d.getHours(); // локальное время
d.getUTCHours(); // UTC-время
Основная проблема заключается в том, что большинство операций в приложениях требуют явного контроля над таймзоной, а стандартный API JavaScript не предоставляет удобных инструментов для конвертаций.
date-fns работает исключительно с объектами Date и не
интерпретирует их как принадлежащие конкретной таймзоне. Все функции
библиотеки:
Date или строкуDateПример:
import { format } from "date-fns";
const date = new Date("2026-01-01T12:00:00Z");
format(date, "yyyy-MM-dd HH:mm:ss");
Результат зависит от локальной таймзоны окружения, что делает UTC-логику неявной.
При использовании format важно учитывать, что результат
всегда интерпретируется в локальной зоне:
import { format } from "date-fns";
const date = new Date("2026-01-01T00:00:00Z");
format(date, "yyyy-MM-dd HH:mm");
// зависит от локальной таймзоны
Это означает, что одна и та же дата может отображаться по-разному на сервере и клиенте.
В рамках базового date-fns можно работать с UTC только через
встроенные методы Date.
const date = new Date("2026-01-01T15:30:00Z");
date.getUTCFullYear(); // 2026
date.getUTCMonth(); // 0
date.getUTCDate(); // 1
date.getUTCHours(); // 15
function formatUTC(date) {
const yyyy = date.getUTCFullYear();
const mm = String(date.getUTCMonth() + 1).padStart(2, "0");
const dd = String(date.getUTCDate()).padStart(2, "0");
return `${yyyy}-${mm}-${dd}`;
}
Такой подход используется редко, поскольку лишает преимуществ библиотеки форматирования.
Основные ограничения:
Эти ограничения приводят к необходимости использования расширения date-fns-tz.
Модуль date-fns-tz добавляет функции:
utcToZonedTimezonedTimeToUtcformatInTimeZoneОн позволяет корректно интерпретировать даты в UTC и локальных зонах.
import { zonedTimeToUtc } from "date-fns-tz";
const date = "2026-01-01 12:00:00";
const timeZone = "Europe/Almaty";
const utcDate = zonedTimeToUtc(date, timeZone);
Логика:
Dateimport { utcToZonedTime } from "date-fns-tz";
const utcDate = new Date("2026-01-01T12:00:00Z");
const zoned = utcToZonedTime(utcDate, "Asia/Tokyo");
Теперь объект zoned содержит те же миллисекунды, но
может быть форматирован как локальное время указанной зоны.
Ключевой инструмент:
import { formatInTimeZone } from "date-fns-tz";
const date = new Date("2026-01-01T12:00:00Z");
const result = formatInTimeZone(
date,
"UTC",
"yyyy-MM-dd HH:mm:ssXXX"
);
Особенности:
"UTC" используется как фиксированная зонаПрактика в backend-системах:
Пример нормализации:
import { zonedTimeToUtc } from "date-fns-tz";
function saveEvent(inputDate, timeZone) {
return zonedTimeToUtc(inputDate, timeZone);
}
Это исключает ошибки, связанные с изменением системной таймзоны.
ISO 8601 является стандартом передачи времени в UTC:
const iso = "2026-01-01T10:00:00Z";
const date = new Date(iso);
При использовании date-fns:
import { parseISO } from "date-fns";
const date = parseISO("2026-01-01T10:00:00Z");
parseISO:
Z как UTCСравнение в JavaScript всегда основано на timestamp:
const a = new Date("2026-01-01T10:00:00Z");
const b = new Date("2026-01-01T12:00:00Z");
a < b; // true
При использовании date-fns:
import { isBefore } from "date-fns";
isBefore(a, b); // true
UTC-аспект здесь скрыт, поскольку сравнение происходит по миллисекундам.
Некоторые функции могут давать неожиданные результаты при локальном времени:
import { startOfDay } from "date-fns";
const d = new Date("2026-01-01T23:30:00Z");
startOfDay(d);
startOfDay использует локальную зону, а не UTC. Это
приводит к смещению границы суток.
Для UTC-логики используется:
import { startOfDay } from "date-fns";
import { utcToZonedTime } from "date-fns-tz";
const utc = new Date("2026-01-01T00:00:00Z");
const zoned = utcToZonedTime(utc, "UTC");
startOfDay(zoned);
zonedTimeToUtcformatInTimeZoneformat без учёта зоныformat(date, "yyyy-MM-dd HH:mm");
Результат зависит от сервера.
new Date().getHours(); // локально
new Date().getUTCHours(); // UTC
new Date("2026-01-01T10:00:00"); // локальная зона
С точки зрения date-fns и date-fns-tz оптимальная схема выглядит следующим образом:
zonedTimeToUtcDateformatInTimeZoneПри расчётах периодов важно учитывать зону:
import { startOfDay, endOfDay } from "date-fns";
const date = new Date("2026-01-01T10:00:00Z");
startOfDay(date);
endOfDay(date);
Для строгого UTC:
import { formatInTimeZone } from "date-fns-tz";
const start = formatInTimeZone(date, "UTC", "yyyy-MM-dd'T'00:00:00XXX");
date-fns использует неизменяемый подход:
DateЭто позволяет безопасно комбинировать цепочки:
import { addDays } from "date-fns";
const result = addDays(new Date("2026-01-01T00:00:00Z"), 5);
DateformatInTimeZonezonedTimeToUtc,
utcToZonedTime