В библиотеке 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
Такой подход исключает скрытые побочные эффекты и делает цепочки операций предсказуемыми.
Базовые арифметические операции реализованы через методы
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 объединяет дату и время, позволяя
выполнять точечные временные сдвиги.
import { LocalDateTime } from '@js-joda/core';
const dt = LocalDateTime.of(2026, 5, 24, 10, 30);
const shifted = dt.plusHours(5).minusMinutes(15);
Поддерживаются операции:
plusHours / minusHoursplusMinutes / minusMinutesplusSeconds / minusSecondsplusNanos / minusNanosОсобенность заключается в том, что переходы через границы суток обрабатываются автоматически:
LocalDateTime.of(2026, 5, 24, 23, 50).plusMinutes(20)
// 2026-05-25T00:10
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 работает с абсолютными единицами времени:
секунды и наносекунды. Он применяется к 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 вычисляет разницу между датами в заданной
единице измерения.
const start = LocalDate.of(2026, 1, 1);
const end = LocalDate.of(2026, 5, 1);
const months = start.until(end, ChronoUnit.MONTHS);
import { ChronoUnit } from '@js-joda/core';
start.until(end, ChronoUnit.DAYS);
start.until(end, ChronoUnit.YEARS);
Результат зависит от выбранной единицы, так как календарная арифметика не является линейной.
Сравнение подходов:
Period.between(startDate, endDate);
Используется для:
Duration.between(startDateTime, endDateTime);
Используется для:
Помимо специализированных методов существует общий механизм через
TemporalAmount.
date.plus(Period.ofDays(10));
time.minus(Duration.ofMinutes(30));
Это позволяет унифицировать обработку разных типов интервалов.
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))
Первый случай учитывает календарь, второй — фиксированное количество секунд.
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);
Оба подхода дают одинаковый результат, но второй повышает читаемость.
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 → для времениChronoUnit → для вычислений разницыdate.plus(Period.ofMonths(2))
.plusDays(10);
time.plus(Duration.ofMinutes(90));