Пошук уроків, статей та іншого контенту
Навчитеся форматувати тривалості й календарні одиниці за допомогою Intl.DurationFormat та пов’язаних локалізаційних правил.
Тривалість описує проміжок часу:
2 години 15 хвилин;
3 дні;
1 рік, 2 місяці та 4 дні;
01:45:20.
Це відрізняється від календарної дати. Дата відповідає на запитання «коли?», а тривалість — «як довго?».
Для локалізації тривалостей у JavaScript використовується Intl.DurationFormat:
const formatter = new Intl.DurationFormat("uk", {
style: "long",
});
console.log(
formatter.format({
hours: 2,
minutes: 15,
seconds: 30,
}),
);Можливий результат:
2 години 15 секундКонкретне форматування залежить від реалізації та правил локалі. Форматер самостійно:
вибирає правильну форму одиниці;
розставляє розділові знаки;
додає сполучники;
форматує числа відповідно до локалі;
визначає порядок одиниць.
Intl.DurationFormatКонструктор приймає локаль або список локалей та об’єкт параметрів:
const formatter = new Intl.DurationFormat(locales, options);Наприклад:
const duration = {
days: 3,
hours: 4,
minutes: 25,
};
const formatter = new Intl.DurationFormat("uk-UA", {
style: "long",
});
console.log(formatter.format(duration));Об’єкт тривалості може містити такі поля:
years;
months;
weeks;
days;
hours;
minutes;
seconds;
milliseconds;
microseconds;
nanoseconds.
Усі поля необов’язкові. Невказані одиниці не відображаються.
const formatter = new Intl.DurationFormat("uk", {
style: "short",
});
console.log(
formatter.format({
hours: 1,
minutes: 5,
}),
);Тривалість потрібно передавати як звичайний об’єкт. Значення в ньому є числами, а не рядками з одиницями вимірювання.
Параметр style визначає загальний вигляд результату.
longПовні назви одиниць:
const formatter = new Intl.DurationFormat("uk", {
style: "long",
});
console.log(
formatter.format({
days: 2,
hours: 6,
minutes: 30,
}),
);Приклад результату:
2 дні, 6 годин і 30 хвилинЦей стиль підходить для:
описів у довідці;
повідомлень для користувача;
доступних текстових інтерфейсів;
підписів до статистики.
shortСкорочені назви:
const formatter = new Intl.DurationFormat("uk", {
style: "short",
});
console.log(
formatter.format({
hours: 2,
minutes: 15,
seconds: 8,
}),
);Приклад результату:
2 год, 15 хв, 8 сСтиль зручний для компактних інформаційних блоків, карток та списків.
narrowНайкоротший запис:
const formatter = new Intl.DurationFormat("en", {
style: "narrow",
});
console.log(
formatter.format({
hours: 2,
minutes: 15,
seconds: 8,
}),
);Приклад результату:
2h 15m 8sТакий стиль варто використовувати лише тоді, коли контекст очевидний. У вузькій колонці або на шкалі часу він може бути корисним, але для звичайного тексту менш зрозумілий.
digitalЦифровий формат призначений передусім для годин, хвилин і секунд:
const formatter = new Intl.DurationFormat("en", {
style: "digital",
});
console.log(
formatter.format({
hours: 2,
minutes: 5,
seconds: 8,
}),
);Приклад результату:
2:05:08Цей стиль підходить для:
таймерів;
тривалості відео;
аудіоплеєрів;
секундомірів;
спортивних результатів.
Цифровий формат не слід використовувати для складних календарних тривалостей на кшталт «1 рік 2 місяці 3 дні».
Окрім загального style, можна налаштовувати окремі одиниці:
const formatter = new Intl.DurationFormat("uk", {
style: "long",
hours: "numeric",
minutes: "2-digit",
seconds: "numeric",
});
console.log(
formatter.format({
hours: 2,
minutes: 5,
seconds: 8,
}),
);Доступні значення для одиниць залежать від типу форматування, але зазвичай використовуються:
"long" — повна назва;
"short" — скорочена назва;
"narrow" — компактне позначення;
"numeric" — числове представлення;
"2-digit" — щонайменше дві цифри.
Загальний стиль задає базові правила, а параметри окремих одиниць дають змогу зробити винятки.
const formatter = new Intl.DurationFormat("en", {
style: "short",
hours: "long",
minutes: "numeric",
});
console.log(
formatter.format({
hours: 1,
minutes: 5,
}),
);Під час поєднання параметрів потрібно перевіряти результат у цільових локалях. Скорочення, порядок слів і розділові знаки не є універсальними.
Одна й та сама тривалість у різних локалях виглядає по-різному:
const duration = {
years: 1,
months: 2,
days: 3,
};
for (const locale of ["uk", "en", "pl", "de"]) {
const formatter = new Intl.DurationFormat(locale, {
style: "long",
});
console.log(`${locale}: ${formatter.format(duration)}`);
}Локалізація враховує не лише переклад назв. Вона також визначає:
відмінювання;
форми однини та множини;
порядок одиниць;
спосіб поєднання компонентів;
локальні цифри;
розділові знаки;
напрямок письма.
Наприклад, в українській мові форми можуть змінюватися залежно від числа:
1 день;
2 дні;
5 днів.
Тому не варто будувати текст вручну:
// Небажано: граматичні правила не враховано
const text = `${days} день ${hours} годин`;Краще передати значення форматеру:
const formatter = new Intl.DurationFormat("uk", {
style: "long",
});
const text = formatter.format({
days,
hours,
});Для тривалостей із дробовою частиною секунд можна вказати fractionalDigits:
const formatter = new Intl.DurationFormat("en", {
style: "long",
fractionalDigits: 3,
});
console.log(
formatter.format({
minutes: 2,
seconds: 4,
milliseconds: 125,
}),
);fractionalDigits визначає кількість цифр дробової частини секунди. Це корисно для:
вимірювання продуктивності;
тривалості HTTP-запитів;
аудіо- та відеообробки;
наукових або технічних даних.
Не слід показувати надмірну точність у звичайному інтерфейсі. Значення на кшталт 2.000000000 секунд рідко допомагає користувачеві.
formatToPartsМетод formatToParts() повертає масив складових форматованого результату:
const formatter = new Intl.DurationFormat("uk", {
style: "long",
});
const parts = formatter.formatToParts({
hours: 2,
minutes: 15,
});
console.log(parts);Частини можуть містити:
числові значення;
назви одиниць;
літерали;
розділові знаки;
сполучники.
Це дає змогу стилізувати окремі частини, не втрачаючи локалізаційних правил.
Наприклад, можна виділити числа жирним шрифтом у HTML:
function formatDurationAsHtml(duration, locale = "uk") {
const formatter = new Intl.DurationFormat(locale, {
style: "long",
});
return formatter
.formatToParts(duration)
.map((part) => {
if (part.type === "integer") {
return `<strong>${part.value}</strong>`;
}
return part.value;
})
.join("");
}
console.log(
formatDurationAsHtml({
hours: 2,
minutes: 15,
}),
);Результат може мати вигляд:
<strong>2</strong> години і <strong>15</strong> хвилинУ реальному застосунку значення частин потрібно екранувати перед вставленням у HTML, якщо вони походять із ненадійного джерела.
resolvedOptions()Метод resolvedOptions() показує параметри, які фактично використовує форматер:
const formatter = new Intl.DurationFormat("uk-UA", {
style: "short",
});
console.log(formatter.resolvedOptions());Це може бути корисно для:
діагностики локалей;
перевірки вибраної системи числення;
тестування конфігурації;
аналізу поведінки в різних середовищах.
Не варто покладатися на конкретний вигляд об’єкта більше, ніж це визначено API. Для відображення користувачеві використовуйте format(), а не ручне складання тексту з resolvedOptions().
Intl.DurationFormat форматує вже готові компоненти. Він не визначає, скільки днів містить місяць, і не обчислює тривалість між двома датами.
const formatter = new Intl.DurationFormat("uk", {
style: "long",
});
console.log(
formatter.format({
months: 1,
}),
);Це означає «1 місяць», але не означає автоматичне перетворення на 28, 29, 30 або 31 день.
Місяць і рік є календарними одиницями. Їхня тривалість залежить від:
конкретної початкової дати;
календаря;
високосного року;
часового поясу;
переходів на літній або зимовий час.
Наприклад, «додати один місяць до дати» і «додати 30 днів» — не завжди однакова операція.
Для різних завдань використовуються різні API:
Intl.DurationFormat — відображення тривалості;
Intl.DateTimeFormat — форматування календарної дати й часу;
Intl.RelativeTimeFormat — фрази на кшталт «через 3 дні» або «2 години тому»;
Date — базова робота з моментами часу;
Temporal API, якщо доступний у середовищі, — моделювання дат, часу, часових зон і тривалостей.
Наприклад, це не тривалість:
const formatter = new Intl.RelativeTimeFormat("uk", {
numeric: "auto",
});
console.log(formatter.format(-1, "day"));Результат означає «учора», тобто відносне розташування моменту щодо іншого моменту.
А це тривалість:
const formatter = new Intl.DurationFormat("uk", {
style: "long",
});
console.log(
formatter.format({
days: 1,
}),
);Результат означає «1 день», без твердження про те, коли саме цей проміжок починається або закінчується.
Нові можливості Intl можуть бути відсутні в окремих старих браузерах або середовищах виконання. Перед використанням можна перевірити наявність конструктора:
function formatDuration(duration, locale = "uk") {
if (typeof Intl.DurationFormat !== "function") {
throw new Error(
"Це середовище не підтримує Intl.DurationFormat",
);
}
const formatter = new Intl.DurationFormat(locale, {
style: "long",
});
return formatter.format(duration);
}
console.log(
formatDuration({
hours: 1,
minutes: 30,
}),
);Якщо підтримка є критичною для застосунку, потрібно передбачити стратегію сумісності:
транспіляція не додає локалізаційні дані автоматично;
поліфіл має містити відповідні дані локалей;
ручний fallback повинен бути обмеженим і зрозумілим;
тестувати потрібно не лише англійську локаль.
Fallback може повертати простий технічний запис:
function formatDurationWithFallback(duration, locale = "uk") {
if (typeof Intl.DurationFormat === "function") {
return new Intl.DurationFormat(locale, {
style: "long",
}).format(duration);
}
const parts = [];
if (duration.hours != null) {
parts.push(`${duration.hours} h`);
}
if (duration.minutes != null) {
parts.push(`${duration.minutes} min`);
}
if (duration.seconds != null) {
parts.push(`${duration.seconds} s`);
}
return parts.join(" ");
}Такий fallback не замінює повну локалізацію. Він лише дає прийнятний результат у середовищі без Intl.DurationFormat.
Нижче наведено повний приклад форматування тривалості для кількох представлень:
const duration = {
days: 2,
hours: 4,
minutes: 7,
seconds: 9,
};
function printDuration(locale, style) {
if (typeof Intl.DurationFormat !== "function") {
console.log(
"Це середовище не підтримує Intl.DurationFormat",
);
return;
}
const formatter = new Intl.DurationFormat(locale, {
style,
});
console.log(`${locale}, ${style}:`);
console.log(formatter.format(duration));
}
printDuration("uk-UA", "long");
printDuration("uk-UA", "short");
printDuration("en-US", "long");
printDuration("en-US", "digital");Для стилю digital зазвичай використовують лише часові компоненти:
const timerDuration = {
hours: 4,
minutes: 7,
seconds: 9,
};
if (typeof Intl.DurationFormat === "function") {
const timerFormatter = new Intl.DurationFormat("en-US", {
style: "digital",
});
console.log(timerFormatter.format(timerDuration));
}// Небажано: форма слова не залежить лише від простого числа
const result = `${count} день`;Використовуйте Intl.DurationFormat, оскільки він враховує правила локалі.
// Небезпечно: календарний місяць не завжди має 30 днів
const days = months * 30;Таке перетворення допустиме лише для спеціальної прикладної моделі, де місяць заздалегідь визначено як умовні 30 днів.
Date для тривалостейDate представляє момент часу, а не довільний проміжок. Спроба зберігати тривалість як дату може призвести до помилок через:
часові пояси;
переходи між літнім і зимовим часом;
обмеження календарних років;
неправильне трактування нульової дати.
Не слід припускати, що форматер перетворить:
{
minutes: 90
}на:
1 година 30 хвилинПередані компоненти потрібно попередньо нормалізувати власною логікою, якщо цього вимагає модель даних.
1 місяць та 30 днів можуть мати різний зміст. Не змішуйте їх без чітко визначених правил домену.
Формат, протестований лише для en-US, може бути непридатним для української, польської або арабської локалі. Перевіряйте:
однину та множину;
порядок компонентів;
цифровий стиль;
довгі й короткі назви;
напрямок письма.
Потрібно заздалегідь вирішити, як відображати:
{
hours: 0,
minutes: 5,
seconds: 0
}Чи має результат містити нульові години та секунди, залежить від вимог інтерфейсу. Не покладайте цю бізнес-логіку на локалізаційний форматер.
Intl.DurationFormat призначений для локалізації проміжків часу.
Тривалість передається об’єктом із полями years, months, days, hours, minutes, seconds та іншими одиницями.
Стилі long, short, narrow і digital підходять для різних типів інтерфейсів.
Форматер автоматично враховує граматику, множину, порядок одиниць і розділові знаки.
formatToParts() дає змогу стилізувати окремі частини результату.
Intl.DurationFormat не обчислює різницю між датами й не перетворює місяці на дні.
Для дат, відносного часу та тривалостей потрібно використовувати різні API.
У production-коді слід перевіряти підтримку Intl.DurationFormat і тестувати кілька локалей.