Относительное форматирование времени

Механизм относительного форматирования времени в Intl реализован через Intl.RelativeTimeFormat. Он предназначен для преобразования числовых временных интервалов в локализованные человекочитаемые строки: «2 часа назад», «через 5 дней», «в прошлом месяце». В отличие от ручной сборки строк, данный API учитывает правила языка, падежи, множественные формы и региональные особенности.

Экземпляр форматтера создаётся один раз и переиспользуется:

const rtf = new Intl.RelativeTimeFormat("ru", {
  numeric: "auto",
  style: "long"
});

Конструктор и параметры конфигурации

Конструктор Intl.RelativeTimeFormat принимает локаль и объект настроек.

new Intl.RelativeTimeFormat(locales, options);

Локаль

Локаль определяет языковые правила форматирования:

new Intl.RelativeTimeFormat("ru");
new Intl.RelativeTimeFormat("en-US");
new Intl.RelativeTimeFormat("kk");

При отсутствии явного указания используется локаль среды выполнения.


Параметр style

style управляет длиной и формой результата.

  • "long" — полная форма: «через 3 минуты»
  • "short" — сокращённая форма: «через 3 мин.»
  • "narrow" — максимально компактная: «+3 мин»
new Intl.RelativeTimeFormat("ru", { style: "short" });

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


Параметр numeric

numeric контролирует, будет ли результат строго числовым.

  • "always" — всегда используется число
  • "auto" — допускаются естественные выражения
new Intl.RelativeTimeFormat("ru", { numeric: "auto" });

При numeric: "auto" возможны языковые замены:

  • -1 day → «вчера»
  • 0 day → «сегодня»
  • +1 day → «завтра»

При numeric: "always" результат остаётся числовым:

  • «1 день назад»
  • «через 1 день»

Единицы измерения времени

Метод format(value, unit) принимает числовое значение и единицу измерения.

Поддерживаемые единицы:

  • "second"
  • "minute"
  • "hour"
  • "day"
  • "week"
  • "month"
  • "quarter"
  • "year"
rtf.format(-5, "minute"); // "5 минут назад"
rtf.format(2, "hour");    // "через 2 часа"

Знак числа определяет направление:

  • отрицательное значение — прошлое
  • положительное — будущее

Форматирование относительного времени

Метод format возвращает строку с учётом локали:

const rtf = new Intl.RelativeTimeFormat("ru");

rtf.format(-1, "day");   // "1 день назад"
rtf.format(3, "day");    // "через 3 дня"
rtf.format(-2, "hour");  // "2 часа назад"

Для английской локали:

const rtfEn = new Intl.RelativeTimeFormat("en");

rtfEn.format(-1, "day"); // "1 day ago"
rtfEn.format(2, "day");  // "in 2 days"

Форматирование частей строки

Метод formatToParts возвращает структурированное представление результата.

const rtf = new Intl.RelativeTimeFormat("ru");

rtf.formatToParts(-2, "hour");

Результат:

[
  { type: "integer", value: "2", unit: "hour" },
  { type: "literal", value: " часа назад" }
]

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


Поведение чисел и языковые формы

Относительное форматирование опирается на CLDR-правила языка. Это означает, что:

  • формы множественного числа корректно подбираются автоматически
  • учитываются исключения (например, славянские языки)
  • нет необходимости вручную реализовывать склонения

Пример для русского языка:

rtf.format(-1, "day"); // "1 день назад"
rtf.format(-2, "day"); // "2 дня назад"
rtf.format(-5, "day"); // "5 дней назад"

Различия локалей

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

const ru = new Intl.RelativeTimeFormat("ru");
const kk = new Intl.RelativeTimeFormat("kk");
const en = new Intl.RelativeTimeFormat("en");
ru.format(-1, "hour"); // "1 час назад"
en.format(-1, "hour"); // "1 hour ago"
kk.format(-1, "hour"); // зависит от реализации локали

Локализация затрагивает не только слова, но и структуру выражения.


Типичные сценарии применения

Относительное форматирование используется в интерфейсах, где важна динамическая временная привязка:

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

Пример вычисления интервала:

const rtf = new Intl.RelativeTimeFormat("ru");

function formatTimeAgo(date) {
  const diff = Date.now() - date.getTime();
  const minutes = Math.round(diff / 60000);

  return rtf.format(-minutes, "minute");
}

Выбор единицы измерения

Корректный выбор unit критичен для читаемости результата. Обычно применяется нормализация:

  • до 60 секунд → seconds
  • до 60 минут → minutes
  • до 24 часов → hours
  • до 7 дней → days
  • далее → weeks/months/years

Пример нормализации:

function getUnit(seconds) {
  if (seconds < 60) return "second";
  if (seconds < 3600) return "minute";
  if (seconds < 86400) return "hour";
  if (seconds < 604800) return "day";
  return "week";
}

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

Создание Intl.RelativeTimeFormat является относительно дорогой операцией. В высоконагруженных сценариях экземпляры обычно кэшируются:

const cache = new Map();

function getFormatter(locale) {
  if (!cache.has(locale)) {
    cache.set(locale, new Intl.RelativeTimeFormat(locale));
  }
  return cache.get(locale);
}

Это снижает накладные расходы при массовом форматировании.


Поведение formatToParts в интерфейсах

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

const parts = rtf.formatToParts(-10, "minute");

Пример использования:

  • число можно выделить жирным
  • единицу времени можно сделать менее заметной
  • направление («назад/вперёд») можно стилизовать отдельно

Ограничения и особенности реализации

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

Поведение numeric: auto

Режим "auto" позволяет избегать избыточной числовой информации:

const rtf = new Intl.RelativeTimeFormat("ru", {
  numeric: "auto"
});

rtf.format(-1, "day"); // "вчера"
rtf.format(0, "day");  // "сегодня"
rtf.format(1, "day");  // "завтра"

При более сложных значениях (например, -2, +3) используется числовая форма.


Интеграция с временными API

Часто используется совместно с Date и временными вычислениями:

const rtf = new Intl.RelativeTimeFormat("ru");

const diffMs = targetDate - new Date();
const diffDays = Math.round(diffMs / 86400000);

rtf.format(diffDays, "day");

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