Работа с временными интервалами в FormatJS строится вокруг
стандартного API ECMAScript Internationalization API (Intl)
и его расширений, которые используются в экосистеме
formatjs для унифицированного форматирования дат,
относительного времени и диапазонов дат с учётом локализации.
Относительное время используется для выражений вида «5 минут назад»,
«через 2 часа», «3 дня назад». В основе лежит
Intl.RelativeTimeFormat.
Форматирование строится на двух параметрах:
const rtf = new Intl.RelativeTimeFormat('ru', {
numeric: 'auto'
});
rtf.format(-5, 'minute'); // "5 минут назад"
rtf.format(2, 'hour'); // "через 2 часа"
secondminutehourdayweekmonthquarteryearКаждая единица интерпретируется относительно текущего момента или базовой точки отсчёта.
Семантика знака строго определяет направление времени:
rtf.format(-1, 'day'); // "вчера"
rtf.format(1, 'day'); // "завтра"
При использовании numeric: "auto" возможна замена
числовых форм на лексические («вчера», «сегодня», «завтра»), если локаль
это поддерживает.
В экосистеме FormatJS используется пакет react-intl,
который предоставляет обёртки над
Intl.RelativeTimeFormat.
import { FormattedRelativeTime } from 'react-intl';
<FormattedRelativeTime
value={-10}
unit="minute"
/>
Типичный паттерн включает динамический расчёт разницы:
const now = Date.now();
const diff = (timestamp - now) / 1000;
let value;
let unit;
if (Math.abs(diff) < 60) {
value = Math.round(diff);
unit = 'second';
} else if (Math.abs(diff) < 3600) {
value = Math.round(diff / 60);
unit = 'minute';
} else {
value = Math.round(diff / 3600);
unit = 'hour';
}
Для корректного отображения временных интервалов используется стратегия нормализации:
function getRelativeTime(diffSeconds) {
const abs = Math.abs(diffSeconds);
if (abs < 60) return ['second', diffSeconds];
if (abs < 3600) return ['minute', diffSeconds / 60];
if (abs < 86400) return ['hour', diffSeconds / 3600];
if (abs < 604800) return ['day', diffSeconds / 86400];
if (abs < 2629800) return ['week', diffSeconds / 604800];
if (abs < 31557600) return ['month', diffSeconds / 2629800];
return ['year', diffSeconds / 31557600];
}
Помимо относительного времени, важную роль играет отображение временных интервалов между двумя датами.
Современный API позволяет форматировать диапазоны дат:
const formatter = new Intl.DateTimeFormat('ru', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
formatter.formatRange(
new Date(2024, 0, 1),
new Date(2024, 0, 10)
);
Результат зависит от локали и может быть представлен как:
1–10 янв. 2024
В formatjs используется аналогичный механизм через
formatDateTimeRange.
import { useIntl } from 'react-intl';
const intl = useIntl();
intl.formatDateTimeRange(
new Date(2024, 5, 1),
new Date(2024, 5, 7),
{
year: 'numeric',
month: 'long',
day: 'numeric'
}
);
| Подход | Назначение | Пример |
|---|---|---|
| RelativeTimeFormat | Относительно текущего момента | «5 минут назад» |
| DateTimeRange | Интервал между датами | «1–7 июня 2024» |
Форматирование зависит от локали:
new Intl.RelativeTimeFormat('en').format(-3, 'day'); // "3 days ago"
new Intl.RelativeTimeFormat('ru').format(-3, 'day'); // "3 дня назад"
new Intl.RelativeTimeFormat('ar').format(-3, 'day'); // арабская форма
Создание Intl.RelativeTimeFormat и
Intl.DateTimeFormat является ресурсоёмким. В
высоконагруженных приложениях применяется кэширование:
const cache = new Map();
function getRtf(locale) {
if (!cache.has(locale)) {
cache.set(locale, new Intl.RelativeTimeFormat(locale, {
numeric: 'auto'
}));
}
return cache.get(locale);
}
При работе с временными интервалами важно учитывать:
Math.round(diff / 60); // минуты
Math.floor(diff / 60); // строгая обрезка
Выбор метода влияет на стабильность отображения UI.
rtf.format(0, 'second'); // "сейчас"
При значениях выше года возможны:
При изменении единицы (например, 59 → 60 секунд) возможны скачки текста:
59 секунд назад → 1 минута назад
import { useIntl } from 'react-intl';
function TimeLabel({ timestamp }) {
const intl = useIntl();
const diff = (timestamp - Date.now()) / 1000;
const [unit, value] = getRelativeTime(diff);
return intl.formatRelativeTime(value, unit);
}
Для динамических интерфейсов используется периодическое обновление:
setInterval(() => {
setNow(Date.now());
}, 1000);
Частота обновления зависит от единицы отображения:
Разные языки обрабатывают интервалы по-разному:
FormatJS делегирует обработку правил движку Intl,
обеспечивая консистентность.
В интерфейсах часто используется гибридный подход:
// "5 минут назад"
intl.formatRelativeTime(-5, 'minute');
// "12:00 — 12:05"
intl.formatDateTimeRange(start, end, {
hour: '2-digit',
minute: '2-digit'
});
Создание единого слоя:
export function formatTimeDiff(intl, timestamp) {
const diff = (timestamp - Date.now()) / 1000;
const [unit, value] = getRelativeTime(diff);
return intl.formatRelativeTime(value, unit);
}
Слой форматирования отделяется от UI, что позволяет: