Форматирование Duration

Представление длительности и внутренние единицы

В библиотеке Luxon длительность (Duration) представляет собой абстракцию промежутка времени, независимого от конкретной даты и часового пояса. Внутренне Duration хранится как набор полей: годы, месяцы, недели, дни, часы, минуты, секунды и миллисекунды. Такой подход позволяет описывать как короткие интервалы (например, 1500 миллисекунд), так и сложные составные длительности (например, 1 год 2 месяца 3 дня 4 часа).

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

Нормализация играет ключевую роль перед форматированием. Метод normalize() перераспределяет значения между единицами, например, 90 секунд превращаются в 1 минуту 30 секунд, но только в пределах доступных единиц.

Базовые способы получения текстового представления

Для преобразования Duration в строку используется несколько подходов, каждый из которых ориентирован на разные сценарии отображения.

ISO 8601 формат

Одним из стандартных представлений является ISO 8601 Duration формат. Он используется для обмена данными между системами и строго стандартизирован.

Метод toISO() возвращает строку в формате:

  • PT — обозначение длительности
  • H — часы
  • M — минуты
  • S — секунды

Пример:

import { Duration } from "luxon";

const d = Duration.fromObject({
  hours: 2,
  minutes: 30,
  seconds: 15
});

d.toISO(); // "PT2H30M15S"

ISO-формат удобен для сериализации, но не предназначен для человекочитаемого отображения.

Объектное представление

Метод toObject() возвращает структуру, которая соответствует внутреннему состоянию Duration.

d.toObject();
// { hours: 2, minutes: 30, seconds: 15 }

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

Человеко-читаемое форматирование

Метод toHuman

Одним из наиболее гибких способов отображения Duration является toHuman(). Он преобразует длительность в строку, учитывая локализацию и ненулевые единицы.

d.toHuman();
// "2 hours, 30 minutes, 15 seconds"

Особенности поведения:

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

При необходимости можно контролировать формат через опции:

d.toHuman({ unitDisplay: "short" });
// "2 hr, 30 min, 15 sec"

Также доступна настройка локали:

d.setLocale("ru").toHuman();
// "2 часа, 30 минут, 15 секунд"

Форматирование через шаблоны

Метод toFormat

Наиболее мощный инструмент форматирования Duration — toFormat. Он использует токены, аналогичные форматированию дат, но адаптированные под длительности.

Основная идея заключается в описании шаблона, где каждый символ соответствует определённой единице времени.

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

const d = Duration.fromObject({
  hours: 5,
  minutes: 7,
  seconds: 9
});

d.toFormat("hh:mm:ss");
// "05:07:09"

Основные токены

  • h — часы
  • m — минуты
  • s — секунды
  • S — миллисекунды
  • d — дни (в зависимости от контекста)
  • M — месяцы
  • y — годы

Повторение символа влияет на форматирование:

  • h — без ведущих нулей
  • hh — с ведущим нулём (два знака)
  • hhh — расширенный формат (при больших значениях)

Пример различий:

const d = Duration.fromObject({ hours: 3 });

d.toFormat("h");   // "3"
d.toFormat("hh");  // "03"

Комбинирование единиц

Форматирование позволяет комбинировать различные части длительности:

const d = Duration.fromObject({
  days: 1,
  hours: 4,
  minutes: 20
});

d.toFormat("d 'days' hh:mm");
// "1 days 04:20"

Кавычки используются для вставки литерального текста без интерпретации токенов.

Нормализация перед форматированием

Перед применением toFormat часто требуется привести Duration к согласованному виду. Без этого возможны неожиданные результаты, например, наличие 90 минут вместо 1 часа 30 минут.

const d = Duration.fromObject({
  hours: 1,
  minutes: 90
});

d.toFormat("hh:mm"); 
// "01:90"

d.normalize().toFormat("hh:mm");
// "02:30"

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

Форматирование с фиксированными единицами

Luxon позволяет использовать shiftTo для управления тем, какие единицы будут участвовать в форматировании.

const d = Duration.fromObject({
  seconds: 3600
}).shiftTo("hours", "minutes");

d.toFormat("hh:mm");
// "01:00"

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

Локализация форматирования

Форматирование Duration может учитывать локаль, особенно при использовании toHuman. В случае toFormat локализация не влияет напрямую на токены, но может использоваться совместно с преобразованием единиц.

const d = Duration.fromObject({
  hours: 2,
  minutes: 15
}).setLocale("fr");

d.toHuman();
// "2 heures, 15 minutes"

Локализация особенно важна в интерфейсах, где длительность отображается пользователю, а не используется технически.

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

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

const d = Duration.fromObject({
  seconds: 1,
  milliseconds: 250
});

d.toFormat("s.SS");
// "1.25"

Количество S влияет на точность отображения:

  • S — десятки миллисекунд
  • SS — сотни миллисекунд
  • SSS — полные миллисекунды

Работа с длительностями неопределённой структуры

При отсутствии явной структуры Duration может содержать смешанные единицы, требующие приведения перед форматированием.

const d = Duration.fromObject({
  minutes: 125
});

d.toFormat("hh:mm");
// "02:125"

d.normalize().toFormat("hh:mm");
// "02:05"

Такие ситуации возникают при агрегации данных из разных источников.

Форматирование больших интервалов

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

const d = Duration.fromObject({
  years: 1,
  months: 2,
  days: 10
});

d.toFormat("y-MM-dd");
// "1-02-10"

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

Влияние отрицательных значений

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

const d = Duration.fromObject({
  minutes: -90
}).normalize();

d.toFormat("hh:mm");
// "-01:30"

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

Использование форматирования в цепочках преобразований

Форматирование Duration редко используется изолированно. Чаще оно является завершающим этапом цепочки преобразований:

  • создание Duration из объектов или ISO-строк
  • нормализация
  • приведение единиц
  • локализация
  • финальное форматирование
Duration.fromObject({ seconds: 5000 })
  .shiftTo("hours", "minutes", "seconds")
  .normalize()
  .toFormat("hh:mm:ss");

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