Конвертация между зонами

В JavaScript объект даты представляет собой момент времени в виде количества миллисекунд, прошедших с Unix-эпохи (UTC). Любая «зона времени» в стандартной модели не изменяет саму дату, а лишь влияет на её интерпретацию при форматировании. Именно этот принцип лежит в основе всех операций конвертации между временными зонами через Intl API.

Внутреннее хранение:

  • Date всегда хранит абсолютное время (UTC timestamp)
  • часовой пояс применяется только при отображении
  • «локальное время» — это результат преобразования UTC в конкретную зону

Следствие: прямой «перевод даты из одной зоны в другую» в виде изменения объекта Date отсутствует. Вместо этого происходит форматирование одного и того же момента времени в разных контекстах.

Форматирование с указанием временной зоны

Основной инструмент — Intl.DateTimeFormat с параметром timeZone.

const date = new Date("2026-05-26T12:00:00Z");

const formatterNY = new Intl.DateTimeFormat("en-US", {
  timeZone: "America/New_York",
  dateStyle: "full",
  timeStyle: "long"
});

const formatterTokyo = new Intl.DateTimeFormat("en-US", {
  timeZone: "Asia/Tokyo",
  dateStyle: "full",
  timeStyle: "long"
});

console.log(formatterNY.format(date));
console.log(formatterTokyo.format(date));

Один и тот же момент времени отображается как разные локальные представления.

Конвертация между зонами через форматирование

Прямого преобразования «из зоны A в зону B» не требуется: выполняется двойное форматирование одного и того же timestamp.

Базовый принцип

  1. есть момент времени (UTC)
  2. интерпретируется в зоне A
  3. тот же момент интерпретируется в зоне B
const date = new Date("2026-05-26T12:00:00Z");

function formatInZone(date, timeZone) {
  return new Intl.DateTimeFormat("en-GB", {
    timeZone,
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
    hour12: false
  }).format(date);
}

const inBerlin = formatInZone(date, "Europe/Berlin");
const inSydney = formatInZone(date, "Australia/Sydney");

Результат — две строки, представляющие один и тот же момент в разных временных зонах.

Получение списка доступных временных зон

const zones = Intl.supportedValuesOf("timeZone");

Это позволяет строить интерфейсы выбора зон или системы сравнения.

Разбор результата через formatToParts

Для более сложной конвертации используется formatToParts, позволяющий извлекать компоненты даты.

const formatter = new Intl.DateTimeFormat("en-US", {
  timeZone: "Asia/Tokyo",
  year: "numeric",
  month: "2-digit",
  day: "2-digit",
  hour: "2-digit",
  minute: "2-digit",
  second: "2-digit",
  hour12: false
});

const parts = formatter.formatToParts(new Date());

console.log(parts);

Структура включает:

  • year
  • month
  • day
  • hour
  • minute
  • second
  • literal

Это используется для построения собственных моделей отображения и ручной реконструкции строк.

«Перевод» времени между зонами как вычисление локального представления

Часто требуется не только отобразить, но и получить строку, соответствующую локальному времени другой зоны.

Пример функции:

function convertToTimeZoneString(date, targetTimeZone) {
  const formatter = new Intl.DateTimeFormat("sv-SE", {
    timeZone: targetTimeZone,
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
    hour12: false
  });

  return formatter.format(date).replace(" ", "T");
}

Использование формата sv-SE полезно, так как он возвращает структурированный ISO-подобный результат.

Обратная проблема: отсутствие прямого парсинга зон

Intl API не предоставляет встроенного механизма:

  • преобразовать локальную дату строки одной зоны в объект времени другой зоны
  • восстановить точный UTC из «человеческой» строки без контекста зоны

Причина: неоднозначность локальных представлений.

Решение строится через:

  • предположение исходной зоны
  • пересчёт через UTC смещение
  • либо сторонние библиотеки (вне Intl)

Работа через UTC как промежуточный слой

Корректная модель конвертации между зонами:

  1. интерпретация входного времени как UTC или как известной зоны
  2. получение timestamp
  3. форматирование в целевую зону
const date = new Date(Date.UTC(2026, 4, 26, 12, 0, 0));

const tz1 = new Intl.DateTimeFormat("en-US", {
  timeZone: "America/Los_Angeles",
  hour: "2-digit",
  minute: "2-digit"
});

const tz2 = new Intl.DateTimeFormat("en-US", {
  timeZone: "Europe/Moscow",
  hour: "2-digit",
  minute: "2-digit"
});

Обе строки основаны на одном UTC значении.

Разница между временными зонами

Разница между зонами не является константой из-за летнего времени.

Пример:

  • Europe/Berlin может быть UTC+1 или UTC+2
  • America/New_York может быть UTC-5 или UTC-4

Следовательно:

  • нельзя хранить фиксированное смещение
  • нельзя вычислять разницу зон без конкретной даты

Получение смещения через форматирование

Хотя прямого API нет, смещение можно вычислить через сравнение частей.

function getOffsetMinutes(date, timeZone) {
  const utc = new Date(date.toLocaleString("en-US", { timeZone: "UTC" }));
  const zoned = new Date(date.toLocaleString("en-US", { timeZone }));

  return (zoned - utc) / 60000;
}

Метод используется как приближённый, но требует осторожности.

Мультизонное отображение одного момента

Типичный сценарий — панель сравнения времени:

const zones = [
  "UTC",
  "Europe/London",
  "Europe/Berlin",
  "Asia/Tokyo",
  "America/New_York"
];

const now = new Date();

const result = zones.map(zone => {
  return {
    zone,
    time: new Intl.DateTimeFormat("en-GB", {
      timeZone: zone,
      year: "numeric",
      month: "2-digit",
      day: "2-digit",
      hour: "2-digit",
      minute: "2-digit",
      second: "2-digit",
      hour12: false
    }).format(now)
  };
});

Нормализация представлений через единый формат

Для хранения и передачи данных используется:

  • ISO 8601 (UTC)
  • Date.toISOString()
const iso = new Date().toISOString();

Любая конвертация между зонами затем выполняется исключительно на этапе отображения.

Формирование универсальной функции преобразования

function convertBetweenZones(date, fromZone, toZone) {
  const intermediate = new Intl.DateTimeFormat("sv-SE", {
    timeZone: fromZone,
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit"
  }).format(date).replace(" ", "T");

  const parsed = new Date(intermediate + "Z");

  return new Intl.DateTimeFormat("sv-SE", {
    timeZone: toZone,
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit"
  }).format(parsed);
}

Модель опирается на промежуточное UTC-представление.

Ограничения модели Intl при конвертации

  • отсутствует полноценный объект «ZonedDateTime»
  • нет обратного преобразования строк в зоны
  • невозможность получить точное локальное время без контекста зоны
  • смещения зависят от даты (DST)

Эти ограничения делают Intl API ориентированным на форматирование, а не на математическую модель временных зон.