Форматирование временных интервалов

Форматирование временных интервалов отличается от форматирования одиночных значений даты и времени тем, что требуется корректно отобразить не одну точку во времени, а диапазон между двумя моментами. В зависимости от локали, продолжительности интервала и совпадения компонентов даты (год, месяц, день, время) результат может существенно меняться.

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

Модель интервалов в CLDR

Базой для интервалов в Globalize выступает CLDR (Unicode Common Locale Data Repository), где для каждой локали определены правила форматирования диапазонов дат и времени.

Ключевой элемент — intervalFormats, описывающий, как должны выглядеть интервалы для разных частей даты:

  • годовые интервалы
  • месячные интервалы
  • дневные интервалы
  • временные интервалы внутри одного дня

Каждый тип интервала описывается набором шаблонов, зависящих от совпадения частей даты.

Пример логики CLDR:

  • одинаковый год → год может быть опущен во второй части
  • одинаковый месяц → месяц не дублируется
  • разные годы → обе даты отображаются полностью

Такое поведение позволяет получить компактное, но однозначное представление интервала.

Подготовка Globalize и CLDR-данных

Работа с форматированием интервалов в Globalize невозможна без загрузки соответствующих CLDR-ресурсов.

Обычно подключаются следующие группы данных:

  • cldr-core
  • cldr-dates-full
  • cldr-numbers-full
  • данные нужной локали (например, ru, en, de)

Инициализация базовой среды:

import Globalize from "globalize";

import likelySubtags from "cldr-core/supplemental/likelySubtags.json";
import timeData from "cldr-core/supplemental/timeData.json";
import weekData from "cldr-core/supplemental/weekData.json";

import dateFields from "cldr-dates-full/main/ru/dateFields.json";
import timeZoneNames from "cldr-dates-full/main/ru/timeZoneNames.json";
import caGregorian from "cldr-dates-full/main/ru/ca-gregorian.json";

Globalize.load(
  likelySubtags,
  timeData,
  weekData,
  dateFields,
  timeZoneNames,
  caGregorian
);

Globalize.locale("ru");

Без загрузки ca-gregorian и связанных структур работа с датами и интервалами будет неполной, поскольку именно там содержатся шаблоны форматирования.

Базовая идея форматирования интервала

В основе лежит функция форматирования диапазона между двумя датами:

  • начало интервала
  • конец интервала
  • правила локали

Обобщённая модель:

Globalize.formatDateRange(startDate, endDate, options);

Где options определяет стиль форматирования (short, medium, long, full) и тип вывода.

Пример интервала в пределах одного дня

const start = new Date(2026, 4, 27, 10, 0);
const end = new Date(2026, 4, 27, 14, 0);

Globalize.formatDateRange(start, end, {
  datetime: "short"
});

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

  • «27.05.2026, 10:00–14:00»

Здесь дата выводится один раз, а диапазон применяется только к времени.

Сокращение повторяющихся компонентов

Одной из ключевых особенностей является автоматическое устранение дублирования.

Совпадение дня

Если начало и конец интервала находятся в одном дне, форматирование сводится к диапазону времени:

const start = new Date(2026, 4, 27, 9, 30);
const end = new Date(2026, 4, 27, 18, 45);

Результат:

  • «27.05.2026, 09:30–18:45»

Совпадение месяца

Если даты различаются днями, но находятся в одном месяце:

const start = new Date(2026, 4, 10);
const end = new Date(2026, 4, 27);

Результат:

  • «10–27 мая 2026 г.»

Здесь месяц и год выводятся один раз, а диапазон применяется только к дню.

Полный интервал с разными годами

const start = new Date(2025, 11, 30);
const end = new Date(2026, 0, 2);

Результат сохраняет обе границы:

  • «30 дек. 2025 г. – 2 янв. 2026 г.»

Skeleton-based форматирование интервалов

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

Skeleton определяет:

  • наличие дня
  • наличие месяца
  • наличие года
  • формат времени (24/12 часа)
  • точность (минуты, секунды)

Globalize использует skeleton как вход для выбора подходящего шаблона интервала.

Пример логики:

  • skeleton: yMd → год + месяц + день
  • skeleton: Hm → часы + минуты

На основе skeleton выбирается правило из intervalFormats.

Влияние локали на интервал

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

Русская локаль

  • сокращение месяцев словами
  • использование точки как разделителя даты
  • порядок день–месяц–год

Английская локаль

  • порядок месяц–день–год
  • сокращения месяцев с точкой или без
  • использование AM/PM в 12-часовом формате

Пример:

Globalize.locale("en");

Globalize.formatDateRange(start, end, { datetime: "medium" });

Результат может выглядеть как:

  • «May 10–27, 2026»

Работа с временными зонами

При форматировании интервалов важно учитывать временные зоны, поскольку два момента времени могут отображаться по-разному в зависимости от контекста.

Globalize опирается на CLDR timeZoneNames и системные настройки среды выполнения.

Особенности:

  • интервал может пересекать переход на летнее время
  • смещение может меняться внутри диапазона
  • отображаемое время может нормализоваться или сохранять локальное представление

Пример интервала с потенциальным DST-переходом:

const start = new Date("2026-03-28T23:00:00Z");
const end = new Date("2026-03-29T03:00:00Z");

Форматирование учитывает локальные правила перехода времени.

Поведение при идентичности дат

Если начало и конец интервала совпадают, система может:

  • вывести одиночную дату без диапазона
  • либо отобразить минимальный интервал в зависимости от формата

Пример:

const start = new Date(2026, 4, 27, 10, 0);
const end = new Date(2026, 4, 27, 10, 0);

Результат обычно сводится к:

  • «27.05.2026, 10:00»

Оптимизация и кэширование форматтеров

Форматирование интервалов является относительно дорогой операцией из-за:

  • загрузки CLDR правил
  • вычисления skeleton matching
  • построения цепочек форматирования

Поэтому Globalize использует кэширование форматтеров:

  • повторное использование правил для одинаковых options
  • предварительная компиляция форматов
  • минимизация пересоздания внутренних структур

Рекомендуется:

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

Граничные случаи интервалов

Переход через границу месяца

const start = new Date(2026, 0, 28);
const end = new Date(2026, 1, 2);

Форматирование раскрывает месяц полностью для обеих границ.

Переход через границу года

const start = new Date(2025, 11, 31);
const end = new Date(2026, 0, 1);

Год отображается на обеих сторонах, так как сокращение невозможно без потери смысла.

Короткие временные интервалы

При интервалах в пределах минут или секунд система сохраняет максимальную точность:

const start = new Date(2026, 4, 27, 10, 0, 0);
const end = new Date(2026, 4, 27, 10, 0, 45);

Результат:

  • «10:00:00–10:00:45»

Согласованность с одиночным форматированием дат

Форматирование интервалов тесно связано с обычным форматированием дат:

  • используются те же CLDR-шаблоны
  • применяются одинаковые локальные правила
  • различие заключается только в логике объединения двух значений

Это обеспечивает визуальную согласованность между:

  • одиночными датами
  • временными диапазонами
  • календарными представлениями