Арифметика с Duration

В библиотеке Luxon длительности представлены объектом Duration, который позволяет работать с промежутками времени как с полноценными значениями: складывать, вычитать, масштабировать, нормализовать и преобразовывать между единицами измерения. В отличие от простых числовых представлений времени, Duration учитывает сложность календарных единиц (дни, месяцы, годы), а также различие между фиксированными (секунды, минуты) и переменными (месяцы) интервалами.


Создание Duration как база для арифметики

Перед выполнением операций длительность должна быть создана в одном из поддерживаемых форматов.

import { Duration } from "luxon";

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

const d2 = Duration.fromObject({
  hours: 1,
  minutes: 45
});

Также возможны альтернативные способы:

Duration.fromMillis(3600000); // 1 час
Duration.fromISO("PT2H30M");  // ISO 8601

Ключевая особенность: Luxon хранит внутреннее представление в виде набора единиц, а не одного числа.


Сложение длительностей

Операция сложения выполняется методом plus. Он возвращает новый объект Duration, не изменяя исходный.

const d1 = Duration.fromObject({ hours: 2, minutes: 30 });
const d2 = Duration.fromObject({ hours: 1, minutes: 45 });

const result = d1.plus(d2);

Результат:

  • hours: 4
  • minutes: 15

Важно учитывать, что Luxon не всегда автоматически нормализует единицы до одного масштаба, если они заданы в разных формах.

Пример с разными единицами:

const d1 = Duration.fromObject({ minutes: 90 });
const d2 = Duration.fromObject({ hours: 1 });

const result = d1.plus(d2);

Здесь результат может остаться в смешанном виде, если не применить нормализацию.


Вычитание длительностей

Метод minus выполняет обратную операцию сложения.

const d1 = Duration.fromObject({ hours: 5 });
const d2 = Duration.fromObject({ hours: 2, minutes: 30 });

const result = d1.minus(d2);

Результат:

  • hours: 2
  • minutes: 30

Если вычитание приводит к отрицательным значениям, Luxon сохраняет знак на уровне всей длительности:

const d1 = Duration.fromObject({ hours: 1 });
const d2 = Duration.fromObject({ hours: 3 });

const result = d1.minus(d2);

Результат будет отрицательной длительностью.


Масштабирование длительности

Умножение

Метод mapUnits отсутствует для умножения, а масштабирование выполняется через mapUnits (для трансформации единиц) или через shiftTo + as + ручное умножение. Однако стандартный способ — использовать mapUnits для преобразований и multiply через сторонние операции:

Фактически Luxon предоставляет scale через mapUnits и ручное управление, но чаще используют:

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

const scaled = d.mapUnits(value => value * 2);

Результат: 60 минут.


Деление (обратное масштабирование)

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

const halved = d.mapUnits(value => value / 2);

Результат:

  • hours: 1

Нормализация длительности

Метод normalize приводит длительность к более “естественному” виду, перераспределяя единицы.

const d = Duration.fromObject({
  minutes: 120,
  seconds: 90
});

const normalized = d.normalize();

Результат:

  • hours: 2
  • minutes: 1
  • seconds: 30

Нормализация особенно важна после арифметических операций, когда значения выходят за стандартные пределы (например, 90 минут).


Преобразование единиц с shiftTo

Метод shiftTo позволяет преобразовать длительность в заданные единицы измерения.

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

const result = d.shiftTo("hours", "minutes");

Результат:

  • hours: 2
  • minutes: 30

Особенность: Luxon распределяет значения между указанными единицами, сохраняя точность.


Получение значения в конкретной единице

Метод as возвращает числовое значение длительности в выбранной единице.

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

d.as("minutes"); // 150
d.as("hours");   // 2.5

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


Арифметика с отрицательными длительностями

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

const d1 = Duration.fromObject({ hours: 1 });
const d2 = Duration.fromObject({ hours: 3 });

const result = d1.minus(d2);

Результат эквивалентен:

  • hours: -2

Можно явно инвертировать знак:

const neg = result.negate();

Комбинирование сложных единиц

Luxon позволяет выполнять операции над смешанными единицами без предварительной конвертации.

const d1 = Duration.fromObject({
  days: 1,
  hours: 5
});

const d2 = Duration.fromObject({
  hours: 20
});

const result = d1.plus(d2);

Результат:

  • days: 2
  • hours: 1

Поведение с месяцами и годами

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

const d1 = Duration.fromObject({ months: 1 });
const d2 = Duration.fromObject({ days: 30 });

const result = d1.plus(d2);

Здесь результат может оставаться в раздельных единицах, поскольку:

  • месяц ≠ 30 дней
  • Luxon не делает автоматическое приведение к дням без явного указания

Явная нормализация после операций

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

const d1 = Duration.fromObject({ minutes: 90 });
const d2 = Duration.fromObject({ minutes: 45 });

const result = d1.plus(d2).normalize();

Результат:

  • hours: 2
  • minutes: 15

Получение и установка значений

Для работы с отдельными компонентами используются get и set.

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

d.get("hours"); // 2

Изменение:

const upd ated = d.se t({ minutes: 45 });

Это создаёт новый объект длительности.


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

Luxon позволяет переводить длительность в абсолютное число миллисекунд.

const d = Duration.fromObject({ seconds: 90 });

d.toMillis(); // 90000

Обратное преобразование:

Duration.fromMillis(90000);

Сравнение длительностей через арифметику

Так как Duration не всегда сравнивается напрямую как число, используется преобразование:

const d1 = Duration.fromObject({ minutes: 90 });
const d2 = Duration.fromObject({ hours: 1 });

d1.as("minutes") > d2.as("minutes"); // true

Цепочки арифметических операций

Luxon позволяет строить цепочки преобразований:

const result = Duration.fromObject({ minutes: 60 })
  .plus(Duration.fromObject({ minutes: 30 }))
  .minus(Duration.fromObject({ minutes: 10 }))
  .shiftTo("hours", "minutes")
  .normalize();

Результат:

  • hours: 1
  • minutes: 20

Важные особенности арифметики Duration

  • Операции всегда возвращают новый объект
  • Смешанные единицы не всегда автоматически приводятся к одному масштабу
  • Месяцы и годы не конвертируются в фиксированные дни
  • Нормализация требуется для “человеческого” представления
  • as используется для числовых вычислений
  • shiftTo используется для перераспределения единиц

Работа с точностью и плавающими значениями

При масштабировании и делении возможны дробные значения:

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

const result = d.mapUnits(v => v / 3);

Результат:

  • minutes: 0.333…

Для устранения накопления погрешностей используется normalize или последующее округление через as.


Комбинация арифметики и преобразований

Типичный рабочий сценарий включает несколько этапов:

const work = Duration.fromObject({ hours: 8 });
const breakTime = Duration.fromObject({ minutes: 30 });

const total = work
  .minus(breakTime)
  .plus(Duration.fromObject({ minutes: 15 }))
  .shiftTo("hours", "minutes")
  .normalize();

Итоговая длительность отражает реальное рабочее время после корректировок.