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

В JavaScript работа с временными интервалами в рамках международной локализации опирается на стандартизированные единицы времени, поддерживаемые спецификациями ECMAScript Internationalization API. Основной инструмент для отображения относительного времени — Intl.RelativeTimeFormat, который использует строго ограниченный набор единиц и правил их языкового преобразования.

Стандартизированные единицы времени

В API интернационализации поддерживаются следующие базовые единицы времени:

  • second — секунда
  • minute — минута
  • hour — час
  • day — день
  • week — неделя
  • month — месяц
  • quarter — квартал
  • year — год

Каждая единица представляет собой абстрактный временной интервал, который не привязан к конкретной длительности в миллисекундах. Например, месяц в календарном смысле может иметь 28, 29, 30 или 31 день, что делает прямую арифметику невозможной без контекста календаря.

Intl.RelativeTimeFormat и семантика единиц

Intl.RelativeTimeFormat предназначен для форматирования выражений вида «через 5 минут», «2 дня назад», «через 1 год». Он принимает числовое значение и одну из поддерживаемых единиц времени.

Базовая структура:

new Intl.RelativeTimeFormat(locales, options)

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

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

rtf.format(-1, 'day');   // "вчера"
rtf.format(1, 'day');    // "завтра"
rtf.format(-5, 'minute'); // "5 минут назад"

Поведение параметра unit

Параметр unit строго ограничен списком поддерживаемых значений. Попытка использовать произвольные строки (например, "millisecond" или "decade") приводит к ошибке или игнорированию в зависимости от окружения.

Важная особенность заключается в том, что единицы интерпретируются лингвистически, а не математически. Это означает, что API не выполняет преобразование между единицами автоматически.

Пример:

rtf.format(60, 'second'); // "через 60 секунд"
rtf.format(1, 'minute');  // "через 1 минуту"

Несмотря на эквивалентность, результат зависит от выбранной единицы.

Логика преобразования значений

Intl.RelativeTimeFormat не нормализует значения между единицами. Разработчик обязан самостоятельно определять, в какой единице представлять интервал.

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

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

function formatSeconds(value) {
  if (value < 60) {
    return rtf.format(value, 'second');
  }
  if (value < 3600) {
    return rtf.format(Math.floor(value / 60), 'minute');
  }
  if (value < 86400) {
    return rtf.format(Math.floor(value / 3600), 'hour');
  }
  return rtf.format(Math.floor(value / 86400), 'day');
}

Такой подход отражает ключевую концепцию: API занимается локализацией, а не вычислением временных интервалов.

Особенности языковой флексии

Единицы времени в Intl.RelativeTimeFormat подчиняются правилам множественного числа конкретного языка. Это означает, что одна и та же числовая величина может иметь разные грамматические формы.

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

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

rtf.format(-1, 'minute'); // "1 минуту назад"
rtf.format(-2, 'minute'); // "2 минуты назад"
rtf.format(-5, 'minute'); // "5 минут назад"

Система автоматически применяет правила CLDR (Unicode Common Locale Data Repository), исключая необходимость ручной обработки склонений.

Опция numeric и её влияние

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

const rtfNumeric = new Intl.RelativeTimeFormat('ru', { numeric: 'always' });
const rtfAuto = new Intl.RelativeTimeFormat('ru', { numeric: 'auto' });

Примеры:

rtfNumeric.format(-1, 'day'); // "1 день назад"
rtfAuto.format(-1, 'day');    // "вчера"

Для языков с богатыми лексическими формами это существенно влияет на читаемость результата.

Границы применимости единиц

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

  • second — кратковременные события, таймеры, интервалы UI
  • minute — пользовательские действия, логирование
  • hour — расписания, события в течение суток
  • day — календарные изменения, публикации
  • week — аналитика, периодические отчёты
  • month — биллинги, подписки
  • quarter — финансовая отчётность
  • year — долгосрочные события

Несмотря на условность, API не накладывает ограничений на диапазон значений внутри каждой единицы.

Календарная неоднозначность

Некоторые единицы времени не имеют фиксированной длины:

  • месяц
  • год
  • неделя (в разных культурах начинается в разные дни)

Это приводит к тому, что Intl.RelativeTimeFormat не оперирует абсолютной длительностью, а лишь обозначает относительную величину.

Пример:

rtf.format(-1, 'month'); // "1 месяц назад"
rtf.format(1, 'month');  // "через 1 месяц"

Фактическое количество дней не вычисляется.

Intl.DurationFormat и расширение модели единиц

Современные реализации JavaScript постепенно вводят Intl.DurationFormat, который дополняет модель единиц времени более точным представлением длительностей.

Принципиальное отличие заключается в том, что используется набор полей, а не одна единица:

  • hours
  • minutes
  • seconds
  • milliseconds (в некоторых реализациях)
  • days, months, years

Пример:

const df = new Intl.DurationFormat('ru', { style: 'long' });

df.format({ hours: 2, minutes: 30 });
// "2 часа 30 минут"

В отличие от RelativeTimeFormat, здесь отсутствует понятие «до/после», акцент делается на длительности.

Сопоставление RelativeTimeFormat и DurationFormat

Характеристика RelativeTimeFormat DurationFormat
Назначение Относительное время Длительность
Модель (value, unit) объект с полями
Направление есть (прошлое/будущее) отсутствует
Локализация да да
Примеры “5 минут назад” “5 минут 10 секунд”

Эти два API решают разные задачи и не являются взаимозаменяемыми.

Ограничения единиц и совместимость

Не все среды поддерживают полный набор единиц или новые API. Наиболее стабильно реализованы:

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

Менее универсальны:

  • week
  • quarter

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

Практическая модель выбора единиц

Выбор единицы времени обычно строится по принципу масштабирования:

  1. Определение абсолютного интервала (в миллисекундах)
  2. Последовательная нормализация
  3. Выбор наиболее подходящей единицы
  4. Передача в Intl.RelativeTimeFormat

Пример стратегии:

function selectUnit(ms) {
  const seconds = Math.abs(ms / 1000);

  if (seconds < 60) return ['second', seconds];
  if (seconds < 3600) return ['minute', seconds / 60];
  if (seconds < 86400) return ['hour', seconds / 3600];
  if (seconds < 2592000) return ['day', seconds / 86400];
  if (seconds < 31536000) return ['month', seconds / 2592000];

  return ['year', seconds / 31536000];
}

Такой подход обеспечивает согласованность отображения независимо от локали.

Поведение в разных локалях

Разные языки по-разному группируют единицы времени:

  • В английском часто используются более прямые конструкции: “in 5 minutes”, “5 minutes ago”
  • В русском активно применяются падежные формы
  • В некоторых азиатских языках структура может быть более лаконичной и контекстной

API абстрагирует эти различия, оставляя только числовое значение и единицу.

Кэширование форматтеров

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

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

function format(value, unit) {
  return rtf.format(value, unit);
}

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

Итоговая модель единиц времени в Intl

Единицы времени в Internationalization API представляют собой фиксированный набор семантических категорий, предназначенных для лингвистического отображения временных интервалов. Их использование отделяет вычислительную часть (нормализацию и выбор масштаба) от языкового представления, что обеспечивает стабильность локализации при изменении языка, региона и культурных правил форматирования времени.