Арифметические операции с датами

В библиотеке js-joda все операции с датами и временем строятся вокруг неизменяемых объектов. Любое арифметическое преобразование не модифицирует исходное значение, а возвращает новый экземпляр.

Ключевой принцип:

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

import { LocalDate } from '@js-joda/core';

const date = LocalDate.of(2026, 5, 24);
const nextDay = date.plusDays(1);

console.log(date.toString());     // 2026-05-24
console.log(nextDay.toString());  // 2026-05-25

Такой подход исключает скрытые побочные эффекты и делает цепочки операций предсказуемыми.


Смещение даты с помощью plus и minus

Базовые арифметические операции реализованы через методы plusXxx и minusXxx. Они работают с календарными единицами: днями, неделями, месяцами, годами.

Дни и недели

const date = LocalDate.of(2026, 1, 10);

const d1 = date.plusDays(5);
const d2 = date.minusDays(10);

const w1 = date.plusWeeks(2);
const w2 = date.minusWeeks(1);

Операции с неделями фактически эквивалентны смещению на 7 дней, но семантически остаются календарными.


Арифметика месяцев и годов

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

const date = LocalDate.of(2026, 1, 31);

const nextMonth = date.plusMonths(1);
console.log(nextMonth.toString());

Результат зависит от правил нормализации:

  • если целевой месяц короче исходного дня, день корректируется до последнего допустимого
  • сохраняется логическая целостность календаря

Пример:

LocalDate.of(2026, 1, 31).plusMonths(1)
// 2026-02-28

Аналогично работают:

date.plusYears(2);
date.minusYears(1);

LocalDateTime и комбинированная арифметика

LocalDateTime объединяет дату и время, позволяя выполнять точечные временные сдвиги.

import { LocalDateTime } from '@js-joda/core';

const dt = LocalDateTime.of(2026, 5, 24, 10, 30);

const shifted = dt.plusHours(5).minusMinutes(15);

Поддерживаются операции:

  • plusHours / minusHours
  • plusMinutes / minusMinutes
  • plusSeconds / minusSeconds
  • plusNanos / minusNanos

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

LocalDateTime.of(2026, 5, 24, 23, 50).plusMinutes(20)
// 2026-05-25T00:10

Period: календарная арифметика

Period представляет календарные интервалы: годы, месяцы и дни. Он применяется к LocalDate.

import { Period, LocalDate } from '@js-joda/core';

const period = Period.of(1, 2, 10);

const date = LocalDate.of(2026, 1, 15);
const result = date.plus(period);

Структура Period:

  • годы
  • месяцы
  • дни

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

Period.ofMonths(1).plusDays(15)

Duration: точная временная арифметика

Duration работает с абсолютными единицами времени: секунды и наносекунды. Он применяется к LocalTime, LocalDateTime, Instant.

import { Duration, LocalTime } from '@js-joda/core';

const duration = Duration.ofHours(2).plusMinutes(30);

const time = LocalTime.of(10, 0);
const result = time.plus(duration);

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


Разница между датами: until и between

LocalDate.until

Метод until вычисляет разницу между датами в заданной единице измерения.

const start = LocalDate.of(2026, 1, 1);
const end = LocalDate.of(2026, 5, 1);

const months = start.until(end, ChronoUnit.MONTHS);

ChronoUnit

import { ChronoUnit } from '@js-joda/core';

start.until(end, ChronoUnit.DAYS);
start.until(end, ChronoUnit.YEARS);

Результат зависит от выбранной единицы, так как календарная арифметика не является линейной.


Разность через Duration и Period

Сравнение подходов:

Period (календарный подход)

Period.between(startDate, endDate);

Используется для:

  • человеческих интервалов (годы, месяцы, дни)
  • календарных вычислений

Duration (абсолютный подход)

Duration.between(startDateTime, endDateTime);

Используется для:

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

Сложение и вычитание через универсальные методы

Помимо специализированных методов существует общий механизм через TemporalAmount.

date.plus(Period.ofDays(10));
time.minus(Duration.ofMinutes(30));

Это позволяет унифицировать обработку разных типов интервалов.


Работа с ChronoUnit как универсальным измерителем

ChronoUnit используется для:

  • вычисления разницы
  • масштабирования интервалов
  • унифицированного смещения
date.plus(1, ChronoUnit.WEEKS);
date.minus(3, ChronoUnit.MONTHS);

Внутренне js-joda преобразует единицы в соответствующие календарные или временные операции.


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

Все операции можно комбинировать:

const result = LocalDate
  .of(2026, 1, 1)
  .plusMonths(1)
  .plusDays(10)
  .minusWeeks(2);

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


Граничные переходы и нормализация значений

При арифметике дат возникают ситуации переполнения:

  • конец месяца
  • переход через год
  • смена суток

js-joda выполняет нормализацию автоматически:

LocalDate.of(2026, 12, 31).plusDays(1)
// 2027-01-01

Для времени:

LocalTime.of(23, 59).plusMinutes(2)
// 00:01

Различие поведения календарных и точных интервалов

Сравнение Period и Duration проявляется в арифметике месяцев:

LocalDate.of(2026, 1, 31).plusMonths(1)

и в точной арифметике секунд:

LocalDateTime.of(2026, 1, 1, 0, 0).plus(Duration.ofDays(30))

Первый случай учитывает календарь, второй — фиксированное количество секунд.


Практика комбинирования LocalDateTime и Duration

import { LocalDateTime, Duration } from '@js-joda/core';

const start = LocalDateTime.of(2026, 5, 24, 8, 0);

const workflow = start
  .plusHours(3)
  .plusMinutes(45)
  .plus(Duration.ofMinutes(30));

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


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

Методы minusXxx эквивалентны использованию отрицательных значений в plusXxx:

date.plusDays(-5);
date.minusDays(5);

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


Использование until для сложных интервалов

const start = LocalDate.of(2020, 1, 1);
const end = LocalDate.of(2026, 5, 24);

const years = start.until(end, ChronoUnit.YEARS);
const months = start.until(end, ChronoUnit.MONTHS);
const days = start.until(end, ChronoUnit.DAYS);

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


Арифметика в контексте временных зон

При работе с ZonedDateTime арифметика учитывает переходы часовых поясов и летнего времени:

import { ZonedDateTime } from '@js-joda/core';

const zdt = ZonedDateTime.now();

const shifted = zdt.plusHours(5);

Переходы через DST могут приводить к:

  • пропуску локального времени
  • повтору часов
  • смещению фактической длительности интервала

Это принципиально отличает ZonedDateTime от LocalDateTime.


Композиция Period и Duration в одной модели

Типичная ошибка — смешивание календарных и абсолютных единиц без понимания их природы. Корректный подход:

  • Period → для дат
  • Duration → для времени
  • ChronoUnit → для вычислений разницы
date.plus(Period.ofMonths(2))
    .plusDays(10);

time.plus(Duration.ofMinutes(90));