Как форматировать относительное время, например, 3 дня назад или через 2 часа?
Используйте Intl.RelativeTimeFormat для отображения времени, например, 3 дня назад или через 2 часа, на любом языке с автоматической поддержкой множественного числа и локализации
Введение
Ленты социальных сетей, разделы комментариев и журналы активности отображают временные метки, такие как "5 минут назад", "2 часа назад" или "через 3 дня". Эти относительные временные метки помогают пользователям быстро понять, когда что-то произошло, без необходимости разбираться в абсолютной дате.
Когда вы жестко задаете эти строки на английском языке, вы предполагаете, что все пользователи говорят на английском и следуют правилам английской грамматики. Разные языки по-разному выражают относительное время. Например, на испанском говорят "hace 3 días" вместо "3 days ago". На японском используется "3日前" с совершенно другой структурой. Каждый язык также имеет уникальные правила множественного числа, которые определяют, когда использовать единственное или множественное число.
JavaScript предоставляет API Intl.RelativeTimeFormat для автоматической обработки форматирования относительного времени. Этот урок объясняет, как правильно форматировать относительное время для любого языка с использованием этого встроенного API.
Почему форматирование относительного времени требует интернационализации
Разные языки по-разному выражают относительное время. В английском языке единица времени ставится перед "ago" для прошедшего времени и после "in" для будущего времени. Другие языки используют другой порядок слов, другие предлоги или совершенно другие грамматические структуры.
const rtfEnglish = new Intl.RelativeTimeFormat('en');
console.log(rtfEnglish.format(-3, 'day'));
// "3 days ago"
const rtfSpanish = new Intl.RelativeTimeFormat('es');
console.log(rtfSpanish.format(-3, 'day'));
// "hace 3 días"
const rtfJapanese = new Intl.RelativeTimeFormat('ja');
console.log(rtfJapanese.format(-3, 'day'));
// "3 日前"
Каждый язык создает естественно звучащий вывод, который соответствует его собственным правилам. Вам не нужно знать эти правила или поддерживать файлы переводов. API автоматически обрабатывает все детали форматирования.
Правила множественного числа также значительно различаются между языками. В английском языке различают "1 day" и "2 days". В арабском языке существует шесть различных форм множественного числа в зависимости от количества. В японском используется одна и та же форма независимо от количества. API Intl.RelativeTimeFormat применяет правильные правила множественного числа для каждого языка.
API Intl.RelativeTimeFormat
Конструктор Intl.RelativeTimeFormat создаёт форматировщик, который преобразует числовые значения и единицы времени в локализованные строки. Вы передаёте идентификатор локали в качестве первого аргумента, а затем вызываете метод format() с числовым значением и единицей времени.
const rtf = new Intl.RelativeTimeFormat('en-US');
console.log(rtf.format(-1, 'day'));
// "1 день назад"
console.log(rtf.format(2, 'hour'));
// "через 2 часа"
Метод format() принимает два параметра. Первый — это число, представляющее количество времени. Второй — строка, указывающая единицу времени.
Отрицательные числа обозначают прошедшее время, а положительные числа — будущее время. Эта конвенция делает API интуитивно понятным после того, как вы поймёте принцип использования знаков.
Форматирование прошедшего и будущего времени
Знак значения определяет, относится ли время к прошлому или будущему. Отрицательные значения создают прошедшие времена, а положительные значения — будущие.
const rtf = new Intl.RelativeTimeFormat('en-US');
console.log(rtf.format(-5, 'minute'));
// "5 минут назад"
console.log(rtf.format(5, 'minute'));
// "через 5 минут"
console.log(rtf.format(-2, 'week'));
// "2 недели назад"
console.log(rtf.format(2, 'week'));
// "через 2 недели"
Этот шаблон работает последовательно для всех единиц времени и всех языков. API автоматически выбирает правильную грамматическую структуру в зависимости от того, является ли значение положительным или отрицательным.
Доступные единицы времени
API поддерживает восемь единиц времени, которые покрывают большинство потребностей в форматировании относительного времени. Вы можете использовать как единственное, так и множественное число, и оба варианта работают одинаково.
const rtf = new Intl.RelativeTimeFormat('en-US');
console.log(rtf.format(-30, 'second'));
// "30 секунд назад"
console.log(rtf.format(-15, 'minute'));
// "15 минут назад"
console.log(rtf.format(-6, 'hour'));
// "6 часов назад"
console.log(rtf.format(-3, 'day'));
// "3 дня назад"
console.log(rtf.format(-2, 'week'));
// "2 недели назад"
console.log(rtf.format(-4, 'month'));
// "4 месяца назад"
console.log(rtf.format(-1, 'quarter'));
// "1 квартал назад"
console.log(rtf.format(-2, 'year'));
// "2 года назад"
API принимает как единственные формы, такие как day, так и множественные формы, такие как days. Оба варианта дают одинаковый результат. Единица "квартал" полезна для бизнес-приложений, работающих с финансовыми периодами.
Использование естественного языка с числовым auto
numeric опция управляет тем, использует ли форматтер числа или альтернативы на естественном языке. Значение по умолчанию — always, которое всегда отображает числа.
const rtfAlways = new Intl.RelativeTimeFormat('en-US', {
numeric: 'always'
});
console.log(rtfAlways.format(-1, 'day'));
// "1 день назад"
console.log(rtfAlways.format(0, 'day'));
// "через 0 дней"
console.log(rtfAlways.format(1, 'day'));
// "через 1 день"
Установка numeric в auto обеспечивает более естественные формулировки для определенных значений.
const rtfAuto = new Intl.RelativeTimeFormat('en-US', {
numeric: 'auto'
});
console.log(rtfAuto.format(-1, 'day'));
// "вчера"
console.log(rtfAuto.format(0, 'day'));
// "сегодня"
console.log(rtfAuto.format(1, 'day'));
// "завтра"
Эта опция делает интерфейсы более разговорными. Пользователи видят "вчера" вместо "1 день назад", что звучит более естественно. Опция auto работает для всех единиц времени и всех языков, при этом каждый язык предоставляет свои идиоматические альтернативы.
Выбор стиля форматирования
style опция управляет подробностью вывода. Доступны три стиля: long, short и narrow.
const rtfLong = new Intl.RelativeTimeFormat('en-US', {
style: 'long'
});
console.log(rtfLong.format(-2, 'hour'));
// "2 часа назад"
const rtfShort = new Intl.RelativeTimeFormat('en-US', {
style: 'short'
});
console.log(rtfShort.format(-2, 'hour'));
// "2 ч. назад"
const rtfNarrow = new Intl.RelativeTimeFormat('en-US', {
style: 'narrow'
});
console.log(rtfNarrow.format(-2, 'hour'));
// "2ч назад"
long стиль является значением по умолчанию и подходит для большинства интерфейсов. short стиль экономит место в мобильных макетах или таблицах. narrow стиль обеспечивает наиболее компактный вывод для дизайнов с крайне ограниченным пространством.
Вычисление разницы во времени
Intl.RelativeTimeFormat API форматирует значения, но не вычисляет их. Вы должны сами вычислить разницу во времени, а затем передать результат в форматтер.
Чтобы вычислить разницу во времени, вычтите целевую дату из текущей даты, а затем преобразуйте результат из миллисекунд в нужную единицу.
const rtf = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' });
function formatDaysAgo(date) {
const now = new Date();
const diffInMs = date - now;
const diffInDays = Math.round(diffInMs / (1000 * 60 * 60 * 24));
return rtf.format(diffInDays, 'day');
}
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
console.log(formatDaysAgo(yesterday));
// "вчера"
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
console.log(formatDaysAgo(tomorrow));
// "завтра"
Эта функция вычисляет разницу в днях между целевой датой и текущим моментом. Вычисление делит миллисекунды на количество миллисекунд в дне, а затем округляет до ближайшего целого числа.
Вычитание date - now дает отрицательное значение для прошедших дат и положительное значение для будущих дат. Это соответствует знаковой конвенции, ожидаемой методом format().
Создание универсальной утилитарной функции
Для универсального форматирования относительного времени необходимо выбрать наиболее подходящую единицу времени на основе величины разницы во времени.
const rtf = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' });
const units = {
year: 24 * 60 * 60 * 1000 * 365,
month: 24 * 60 * 60 * 1000 * 365 / 12,
week: 24 * 60 * 60 * 1000 * 7,
day: 24 * 60 * 60 * 1000,
hour: 60 * 60 * 1000,
minute: 60 * 1000,
second: 1000
};
function formatRelativeTime(date) {
const now = new Date();
const diffInMs = date - now;
const absDiff = Math.abs(diffInMs);
for (const [unit, msValue] of Object.entries(units)) {
if (absDiff >= msValue || unit === 'second') {
const value = Math.round(diffInMs / msValue);
return rtf.format(value, unit);
}
}
}
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
console.log(formatRelativeTime(fiveMinutesAgo));
// "5 минут назад"
const threeDaysAgo = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000);
console.log(formatRelativeTime(threeDaysAgo));
// "3 дня назад"
const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000);
console.log(formatRelativeTime(tomorrow));
// "завтра"
Эта функция проходит по единицам времени от самых крупных к самым мелким, выбирая первую единицу, где абсолютная разница превышает значение в миллисекундах для этой единицы. Резервный вариант для секунд гарантирует, что функция всегда возвращает результат.
Определения единиц используют приблизительные значения. Месяцы рассчитываются как 1/12 года, а не с учетом разной длины месяцев. Это приближение хорошо работает для отображения относительного времени, где приблизительные значения более полезны, чем точная точность.
Форматирование для локали пользователя
Вместо жесткого кодирования определенной локали вы можете использовать предпочитаемый язык пользователя из браузера.
const userLocale = navigator.language;
const rtf = new Intl.RelativeTimeFormat(userLocale, { numeric: 'auto' });
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
console.log(rtf.format(-1, 'day'));
// Вывод зависит от локали пользователя
// Для en-US: "yesterday"
// Для es-ES: "ayer"
// Для fr-FR: "hier"
// Для de-DE: "gestern"
Этот подход отображает относительное время в соответствии с языковыми предпочтениями каждого пользователя без необходимости ручного выбора локали. Браузер предоставляет языковые предпочтения, а API применяет соответствующие правила форматирования.
Отображение одного и того же времени на разных языках
Одно и то же значение относительного времени дает разный вывод для разных локалей. Каждый язык следует своим собственным правилам порядка слов, грамматики и склонения.
const threeDaysAgo = -3;
const rtfEnglish = new Intl.RelativeTimeFormat('en-US');
console.log(rtfEnglish.format(threeDaysAgo, 'day'));
// "3 days ago"
const rtfSpanish = new Intl.RelativeTimeFormat('es-ES');
console.log(rtfSpanish.format(threeDaysAgo, 'day'));
// "hace 3 días"
const rtfFrench = new Intl.RelativeTimeFormat('fr-FR');
console.log(rtfFrench.format(threeDaysAgo, 'day'));
// "il y a 3 jours"
const rtfGerman = new Intl.RelativeTimeFormat('de-DE');
console.log(rtfGerman.format(threeDaysAgo, 'day'));
// "vor 3 Tagen"
const rtfJapanese = new Intl.RelativeTimeFormat('ja-JP');
console.log(rtfJapanese.format(threeDaysAgo, 'day'));
// "3 日前"
const rtfArabic = new Intl.RelativeTimeFormat('ar-SA');
console.log(rtfArabic.format(threeDaysAgo, 'day'));
// "قبل 3 أيام"
Каждый язык создает естественный вывод, который носители языка используют в разговоре. API обрабатывает всю сложность различных грамматических структур, систем письма и направлений текста.
Повторное использование форматтеров для повышения производительности
Создание экземпляра Intl.RelativeTimeFormat включает загрузку данных локали и обработку параметров. При форматировании нескольких временных меток создайте форматтер один раз и используйте его повторно.
const rtf = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' });
const timestamps = [
new Date(Date.now() - 5 * 60 * 1000), // 5 минут назад
new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 часа назад
new Date(Date.now() - 24 * 60 * 60 * 1000) // 1 день назад
];
timestamps.forEach(date => {
const diffInMs = date - new Date();
const diffInMinutes = Math.round(diffInMs / (60 * 1000));
console.log(rtf.format(diffInMinutes, 'minute'));
});
Этот подход более эффективен, чем создание нового форматтера для каждой временной метки. Разница в производительности становится значительной при форматировании сотен или тысяч временных меток в лентах активности или потоках комментариев.
Использование относительного времени в интерфейсах
Вы можете использовать форматирование относительного времени везде, где отображаете временные метки для пользователей. Это включает ленты социальных сетей, разделы комментариев, журналы активности, системы уведомлений и любые интерфейсы, где указание, сколько времени прошло с какого-либо события, помогает пользователям понять контекст.
const rtf = new Intl.RelativeTimeFormat(navigator.language, {
numeric: 'auto'
});
function updateTimestamp(element, date) {
const now = new Date();
const diffInMs = date - now;
const diffInMinutes = Math.round(diffInMs / (60 * 1000));
element.textContent = rtf.format(diffInMinutes, 'minute');
}
const commentDate = new Date('2025-10-15T14:30:00');
const timestampElement = document.getElementById('comment-timestamp');
updateTimestamp(timestampElement, commentDate);
Отформатированные строки работают так же, как и любые другие строковые значения. Вы можете вставлять их в текстовый контент, атрибуты или любой другой контекст, где вы отображаете информацию для пользователей.