Добавление и вычитание через объекты

Работа с датами в Day.js строится вокруг иммутабельной модели: каждое преобразование возвращает новый объект даты, не изменяя исходный. Это особенно важно при сложных вычислениях, где последовательные операции добавления и вычитания должны оставаться предсказуемыми. Одним из расширенных способов управления временем является использование объектов при добавлении и вычитании, что становится доступным через плагин objectSupport.

Стандартный API Day.js использует сигнатуру:

dayjs().add(1, 'day')
dayjs().subtract(2, 'month')

Однако при работе с комплексными интервалами такой подход становится громоздким. Например, необходимо прибавить одновременно дни, часы и минуты. В таких случаях используется объектная форма:

dayjs().add({
  days: 2,
  hours: 3,
  minutes: 15
})

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

Подключение поддержки объектов

По умолчанию Day.js не интерпретирует объекты в методах add и subtract. Для активации этой функциональности используется плагин objectSupport:

import dayjs from 'dayjs'
import objectSupport from 'dayjs/plugin/objectSupport'

dayjs.extend(objectSupport)

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

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

Структура объекта интервалов

Объект, передаваемый в add или subtract, может содержать комбинацию следующих ключей:

  • years
  • months
  • weeks
  • days
  • hours
  • minutes
  • seconds
  • milliseconds

Пример комбинированного интервала:

dayjs().add({
  years: 1,
  months: 6,
  days: 10,
  hours: 4
})

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

Приоритет и порядок применения

При использовании объектной формы важно понимать, что Day.js применяет значения последовательно по внутренней логике преобразования календаря. Например:

dayjs('2024-01-31')
  .add({ months: 1 })

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

Аналогично:

dayjs('2024-01-31')
  .add({ days: 10 })

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

Вычитание через объект

Метод subtract также поддерживает объектную форму при активированном objectSupport:

dayjs().subtract({
  days: 5,
  hours: 2
})

Семантически это эквивалентно добавлению отрицательных значений, однако объектная форма делает код более выразительным при работе с симметричными временными изменениями.

Пример с комбинированным вычитанием:

dayjs('2025-06-15')
  .subtract({
    months: 2,
    weeks: 1,
    minutes: 30
  })

Внутренняя интерпретация объекта

При передаче объекта Day.js преобразует ключи в последовательные вызовы внутренних операций добавления/вычитания. Концептуально это эквивалентно цепочке:

dayjs()
  .add(2, 'days')
  .add(3, 'hours')
  .add(15, 'minutes')

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

Это важно при понимании того, как взаимодействуют различные единицы измерения. Например, добавление 90 минут не сохраняется как «90 минут», а преобразуется в 1 час 30 минут в итоговой дате.

Совместимость единиц измерения

Объектная форма требует корректного использования ключей. Неверные названия игнорируются:

dayjs().add({
  dayz: 5,     // будет проигнорировано
  days: 5      // будет применено
})

Также важно различать:

  • day и days (в контексте Day.js используется множественная форма)
  • week и weeks
  • month и months

Единообразие ключей критично при динамической генерации объектов интервала.

Работа с динамическими объектами

Часто объект интервала формируется из внешних данных:

const interval = {
  days: Number(inputDays),
  hours: Number(inputHours)
}

dayjs().add(interval)

Такой подход упрощает интеграцию с формами и API, где данные приходят в структурированном виде.

При этом Day.js не выполняет строгую валидацию типов, поэтому передача строковых значений может привести к неожиданным результатам:

dayjs().add({
  days: '5' // будет приведено к числу, но поведение зависит от контекста
})

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

Комбинирование add и subtract

Объектная форма не ограничивает последовательность операций. Можно комбинировать добавление и вычитание:

dayjs()
  .add({ days: 10 })
  .subtract({ hours: 3 })

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

Граничные случаи календаря

При работе с месяцами и годами возникает особенность нормализации дат:

dayjs('2024-03-31')
  .add({ months: 1 })

Если следующий месяц не содержит 31 числа, Day.js автоматически корректирует дату до последнего допустимого дня месяца.

Аналогично:

dayjs('2023-02-28')
  .add({ years: 1 })

переносит дату в следующий год с учётом календарных ограничений.

Производительность объектных операций

Объектная форма удобна, но внутри может приводить к нескольким последовательным вычислениям. В большинстве сценариев разница незначительна, однако при массовых операциях над большими наборами дат предпочтительнее использовать явные вызовы add/subtract с фиксированными единицами.

// более предсказуемый вариант для циклов
for (let i = 0; i < 1000; i++) {
  date = date.add(1, 'day')
}

Объектная форма больше ориентирована на читаемость, чем на микрооптимизацию.

Использование в конфигурационных моделях

Объекты интервалов часто используются как часть бизнес-логики:

const expirationPolicy = {
  days: 30,
  hours: 12
}

const expiresAt = dayjs(createdAt).add(expirationPolicy)

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

Нормализация результата

После применения объектных операций Day.js всегда возвращает нормализованную дату в пределах стандартного диапазона времени. Это означает:

  • перенос секунд в минуты
  • перенос минут в часы
  • перенос дней с учётом календаря
  • корректировку месяцев с разной длиной

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