Как форматировать временные интервалы, такие как 2 часа 30 минут

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

Введение

Когда вы показываете, сколько времени занимает что-либо, важно отображать эту продолжительность так, чтобы пользователи могли её понять. Например, видео показывает длительность 2 часа 30 минут, приложение для тренировок отслеживает продолжительность упражнений, инструмент управления проектами отображает время выполнения задач. Без локализации вы могли бы написать код следующим образом:

const hours = 2;
const minutes = 30;
const timeSpan = `${hours}h ${minutes}m`;

Этот код создаёт строку "2h 30m" для всех пользователей, независимо от их языка. Французские пользователи видят "2h 30m", хотя ожидают "2 h 30 min". Немецкие пользователи видят английские сокращения вместо "2 Std. 30 Min". Испанские пользователи не видят союза "y" между единицами.

JavaScript предоставляет API Intl.DurationFormat для форматирования временных интервалов в соответствии с языком и культурными особенностями пользователя. В этом уроке объясняется, как создавать форматтеры длительности, строить объекты длительности и правильно отображать временные интервалы для любого языка.

Что такое временные интервалы

Временной интервал представляет собой продолжительность времени, а не конкретный момент. Например, 150 минут — это длительность. 15 марта 2025 года в 14:30 — это дата и время.

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

Используйте Intl.DurationFormat для временных интервалов. Используйте Intl.DateTimeFormat для дат и времени. Используйте Intl.RelativeTimeFormat для относительных выражений, таких как "2 часа назад".

Создание форматтера длительности

Конструктор Intl.DurationFormat принимает локаль и объект с опциями. Локаль определяет язык вывода. Опции управляют стилем форматирования и отображением единиц.

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

Вызовите format() с объектом длительности, чтобы получить отформатированную строку. Объект длительности содержит числовые свойства для единиц времени.

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

API автоматически обрабатывает сокращения, союзы, порядок слов и пробелы в зависимости от локали.

Создание объектов длительности

Объект длительности — это простой объект JavaScript с свойствами для единиц времени. Включайте только те единицы, которые вы хотите отображать.

const duration1 = { hours: 2, minutes: 30 };
const duration2 = { minutes: 5, seconds: 45 };
const duration3 = { hours: 1, minutes: 15, seconds: 30 };

API поддерживает следующие единицы времени: years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds.

Вам не нужно включать все единицы. Исключите любые единицы, которые вы не хотите отображать.

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

formatter.format({ hours: 2, minutes: 30 });
// "2 hours and 30 minutes"

formatter.format({ minutes: 30 });
// "30 minutes"

formatter.format({ hours: 2 });
// "2 hours"

Выбор стиля форматирования

Опция style управляет плотностью вывода. Доступны четыре стиля: long, short, narrow и digital.

Стиль long использует полные слова. Используйте его для текста и основных областей контента.

const duration = { hours: 2, minutes: 30 };

new Intl.DurationFormat('en', { style: 'long' }).format(duration);
// "2 hours and 30 minutes"

Стиль short использует общепринятые сокращения. Используйте его, когда пространство ограничено, но важна читаемость.

new Intl.DurationFormat('en', { style: 'short' }).format(duration);
// "2 hr and 30 min"

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

new Intl.DurationFormat('en', { style: 'narrow' }).format(duration);
// "2h 30m"

Стиль digital создает вывод, похожий на таймер, с двоеточиями. Используйте его для медиаплееров и отображения обратного отсчета.

new Intl.DurationFormat('en', { style: 'digital' }).format(duration);
// "2:30:00"

Для стиля digital необходимо включать все единицы от самой крупной до самой мелкой. Если вы форматируете часы и минуты, вы также должны включить секунды.

Форматирование временных интервалов на разных языках

Один и тот же временной интервал форматируется по-разному на каждом языке. API автоматически обрабатывает всю локализацию.

const duration = { hours: 2, minutes: 30 };

new Intl.DurationFormat('en', { style: 'long' }).format(duration);
// "2 hours and 30 minutes"

new Intl.DurationFormat('fr', { style: 'long' }).format(duration);
// "2 heures et 30 minutes"

new Intl.DurationFormat('de', { style: 'long' }).format(duration);
// "2 Stunden und 30 Minuten"

new Intl.DurationFormat('es', { style: 'long' }).format(duration);
// "2 horas y 30 minutos"

new Intl.DurationFormat('ja', { style: 'long' }).format(duration);
// "2時間30分"

Обратите внимание, как каждое языковое окружение использует разные слова и союзы. Французский использует "et", немецкий — "und", испанский — "y", японский не использует союзы. API знает эти правила для каждого языка.

Короткие и узкие стили также корректно локализуются.

new Intl.DurationFormat('fr', { style: 'short' }).format(duration);
// "2 h et 30 min"

new Intl.DurationFormat('de', { style: 'narrow' }).format(duration);
// "2 Std. 30 Min."

Форматирование временных интервалов для локали пользователя

Вместо жесткого указания локали используйте предпочтительный язык пользователя из браузера. Свойство navigator.language возвращает предпочтительный язык пользователя.

const userLocale = navigator.language;
const formatter = new Intl.DurationFormat(userLocale, { style: 'short' });

const duration = { hours: 2, minutes: 30 };
formatter.format(duration);
// Вывод зависит от локали пользователя
// Для en-US: "2 hr and 30 min"
// Для de-DE: "2 Std. und 30 Min."
// Для fr-FR: "2 h et 30 min"

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

Преобразование миллисекунд в объекты временных интервалов

Вычисления времени часто дают результат в миллисекундах. Преобразуйте миллисекунды в объекты длительности, разделив их на соответствующие множители.

const milliseconds = 9000000; // 2 часа 30 минут

const hours = Math.floor(milliseconds / 3600000);
const minutes = Math.floor((milliseconds % 3600000) / 60000);
const seconds = Math.floor((milliseconds % 60000) / 1000);

const duration = { hours, minutes, seconds };

new Intl.DurationFormat('ru', { style: 'long' }).format(duration);
// "2 часа, 30 минут и 0 секунд"

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

const duration = {};
if (hours > 0) duration.hours = hours;
if (minutes > 0) duration.minutes = minutes;
if (seconds > 0) duration.seconds = seconds;

new Intl.DurationFormat('ru', { style: 'long' }).format(duration);
// "2 часа и 30 минут"

Вычисление временных интервалов между двумя датами

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

const startTime = new Date('2025-10-15T10:00:00');
const endTime = new Date('2025-10-15T12:30:00');

const diffMs = endTime - startTime;

const hours = Math.floor(diffMs / 3600000);
const minutes = Math.floor((diffMs % 3600000) / 60000);

const duration = { hours, minutes };

new Intl.DurationFormat('ru', { style: 'short' }).format(duration);
// "2 ч и 30 мин"

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

Форматирование длительности для видеоплееров

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

function formatVideoDuration(totalSeconds) {
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const seconds = Math.floor(totalSeconds % 60);

  const duration = hours > 0
    ? { hours, minutes, seconds }
    : { minutes, seconds };

  const locale = navigator.language;
  return new Intl.DurationFormat(locale, { style: 'digital' }).format(duration);
}

formatVideoDuration(9000); // "2:30:00"
formatVideoDuration(330);  // "5:30"

Этот подход условно включает часы только при необходимости, показывая "5:30" для коротких видео и "2:30:00" для более длинного контента.

Форматирование продолжительности тренировок

Фитнес-приложения отслеживают продолжительность упражнений. Используйте длинный стиль для сводок сеансов и узкий стиль для компактных списков.

function formatWorkoutDuration(startTime, endTime, locale) {
  const diffMs = endTime - startTime;

  const hours = Math.floor(diffMs / 3600000);
  const minutes = Math.floor((diffMs % 3600000) / 60000);

  const duration = hours > 0
    ? { hours, minutes }
    : { minutes };

  return new Intl.DurationFormat(locale, { style: 'long' }).format(duration);
}

const workoutStart = new Date('2025-10-15T07:00:00');
const workoutEnd = new Date('2025-10-15T09:30:00');

formatWorkoutDuration(workoutStart, workoutEnd, 'en');
// "2 hours and 30 minutes"

formatWorkoutDuration(workoutStart, workoutEnd, 'es');
// "2 horas y 30 minutos"

Форматирование продолжительности задач проекта

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

function formatTaskDuration(minutes, locale) {
  const hours = Math.floor(minutes / 60);
  const mins = minutes % 60;

  const duration = {};
  if (hours > 0) duration.hours = hours;
  if (mins > 0) duration.minutes = mins;

  return new Intl.DurationFormat(locale, { style: 'short' }).format(duration);
}

formatTaskDuration(150, 'en');
// "2 hr and 30 min"

formatTaskDuration(45, 'en');
// "45 min"

formatTaskDuration(150, 'de');
// "2 Std. und 30 Min."

Форматирование различных единиц времени

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

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

formatter.format({ days: 3, hours: 2 });
// "3 days and 2 hours"

formatter.format({ minutes: 45, seconds: 30 });
// "45 minutes and 30 seconds"

formatter.format({ hours: 1, minutes: 30, seconds: 45 });
// "1 hour, 30 minutes and 45 seconds"

API обрабатывает соответствующие союзы и разделители для любой комбинации единиц.

Форматирование временных интервалов только в секундах

Когда продолжительность меньше минуты, указывайте только секунды.

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

formatter.format({ seconds: 45 });
// "45 сек"

formatter.format({ seconds: 5 });
// "5 сек"

Для очень коротких интервалов можно включить миллисекунды.

formatter.format({ seconds: 5, milliseconds: 500 });
// "5 сек и 500 мс"

Повторное использование экземпляров форматтера для повышения производительности

Создание нового форматтера включает загрузку данных локали и обработку параметров. Если вы форматируете несколько временных интервалов с одной и той же локалью и стилем, создайте форматтер один раз и используйте его повторно.

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

const durations = [
  { hours: 1, minutes: 30 },
  { hours: 2, minutes: 15 },
  { minutes: 45 }
];

durations.map(d => formatter.format(d));
// ["1 ч и 30 мин", "2 ч и 15 мин", "45 мин"]

Этот подход улучшает производительность при форматировании множества временных интервалов в циклах или при повторных рендерах.

Поддержка браузерами

API Intl.DurationFormat стало доступно в марте 2025 года. Оно работает в последних версиях Chrome, Edge, Firefox и Safari. Старые браузеры не поддерживают это API.

Проверьте поддержку перед использованием API.

if (typeof Intl.DurationFormat !== 'undefined') {
  const formatter = new Intl.DurationFormat('ru', { style: 'short' });
  return formatter.format(duration);
} else {
  return `${duration.hours}ч ${duration.minutes}м`;
}

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