Одной из ключевых особенностей библиотеки js-joda является полная
иммутабельность всех объектов даты и времени. Любой экземпляр
LocalDate, LocalTime,
LocalDateTime, ZonedDateTime,
Duration, Period и других типов никогда не
изменяет собственное состояние после создания.
Такой подход заимствован из Java API java.time и
обеспечивает предсказуемое поведение при работе с датами, временем,
часовыми поясами и вычислениями.
Иммутабельный объект — это объект, состояние которого нельзя изменить после создания.
Если требуется получить новое значение, создаётся новый объект, а исходный остаётся неизменным.
Пример с обычным JavaScript-объектом:
const user = {
name: 'Alex'
};
user.name = 'John';
console.log(user.name); // John
Объект изменился напрямую.
В Js-joda подобное невозможно:
const date = LocalDate.of(2025, 5, 10);
const newDate = date.plusDays(5);
console.log(date.toString()); // 2025-05-10
console.log(newDate.toString()); // 2025-05-15
Метод plusDays() не изменяет исходную дату. Вместо этого
создаётся новый экземпляр.
Изменение объекта в одном месте программы может неожиданно повлиять на другое место, где используется тот же экземпляр.
Иммутабельность полностью устраняет подобную проблему.
const start = LocalDate.of(2025, 1, 1);
function addWeek(date) {
return date.plusWeeks(1);
}
const result = addWeek(start);
console.log(start.toString()); // 2025-01-01
console.log(result.toString()); // 2025-01-08
Исходное значение гарантированно остаётся прежним.
Иммутабельные объекты безопасны для многопоточной среды, потому что их нельзя изменить после создания.
Хотя JavaScript традиционно работает в одном потоке, современные среды используют:
Иммутабельность предотвращает состояние гонки и случайное изменение данных.
Если объект никогда не меняется, становится проще понимать поток данных.
При отладке достаточно выяснить:
Отсутствуют скрытые изменения состояния.
Побочный эффект — изменение внешнего состояния функции.
Иммутабельность делает функции чище и безопаснее.
Плохой пример с изменяемыми объектами:
function moveDate(date) {
date.setDate(date.getDate() + 1);
}
Функция изменяет аргумент напрямую.
Эквивалент в Js-joda:
function moveDate(date) {
return date.plusDays(1);
}
Исходный объект остаётся нетронутым.
Тип LocalDate представляет только дату:
const date = LocalDate.of(2025, 7, 20);
Любые операции создают новый объект:
const updated = date.plusMonths(2);
console.log(date.toString()); // 2025-07-20
console.log(updated.toString()); // 2025-09-20
Это касается всех методов:
plusDays()minusDays()plusWeeks()plusMonths()withYear()withMonth()withDayOfMonth()Методы with* создают копию объекта с изменённым
значением.
const date = LocalDate.of(2025, 3, 10);
const changed = date.withYear(2030);
console.log(date.toString()); // 2025-03-10
console.log(changed.toString()); // 2030-03-10
Исходный экземпляр не затрагивается.
Тип LocalTime также полностью неизменяем.
const time = LocalTime.of(10, 30);
const updated = time.plusHours(2);
console.log(time.toString()); // 10:30
console.log(updated.toString()); // 12:30
Невозможно изменить часы, минуты или секунды внутри существующего объекта.
LocalDateTime объединяет дату и время.
const dateTime = LocalDateTime.of(2025, 5, 10, 14, 20);
const next = dateTime.plusDays(3);
console.log(dateTime.toString());
console.log(next.toString());
Результат:
2025-05-10T14:20
2025-05-13T14:20
Первоначальное значение сохраняется.
Особенно важна неизменяемость при работе с часовыми поясами.
const zoned = ZonedDateTime.now(ZoneId.of('Europe/Paris'));
const tokyo = zoned.withZoneSameInstant(
ZoneId.of('Asia/Tokyo')
);
console.log(zoned.toString());
console.log(tokyo.toString());
Создаётся новый объект с другим часовым поясом.
Исходный экземпляр не изменяется.
Иммутабельность делает безопасными цепочки преобразований.
const result = LocalDate.now()
.plusMonths(1)
.minusDays(5)
.withDayOfMonth(1);
Каждый метод возвращает новый объект.
Это позволяет строить декларативные цепочки без риска изменения промежуточных значений.
Встроенный объект Date является изменяемым.
const date = new Date();
date.setFullYear(2030);
Метод изменяет существующий экземпляр.
Это приводит к множеству проблем:
Пример ошибки:
const original = new Date();
const copy = original;
copy.setMonth(11);
console.log(original);
Изменятся оба значения, потому что переменные указывают на один объект.
В Js-joda такое невозможно.
Иммутабельность особенно важна при копировании ссылок.
const first = LocalDate.of(2025, 1, 1);
const second = first;
const third = second.plusDays(10);
console.log(first.toString()); // 2025-01-01
console.log(second.toString()); // 2025-01-01
console.log(third.toString()); // 2025-01-11
Даже если несколько переменных указывают на один объект, его состояние не изменится.
Методы изменения даты всегда возвращают новый экземпляр.
const date = LocalDate.of(2025, 8, 10);
const a = date.plusDays(1);
const b = date.plusMonths(1);
const c = date.minusWeeks(2);
Переменная date остаётся неизменной.
Типы Period и Duration также
иммутабельны.
const period = Period.ofDays(5);
const updated = period.plusMonths(1);
console.log(period.toString()); // P5D
console.log(updated.toString()); // P1M5D
const duration = Duration.ofHours(2);
const changed = duration.plusMinutes(30);
console.log(duration.toString()); // PT2H
console.log(changed.toString()); // PT2H30M
Иммутабельность делает функции безопаснее.
function calculateDeadline(date) {
return date
.plusWeeks(2)
.withDayOfMonth(1);
}
const start = LocalDate.of(2025, 4, 10);
const deadline = calculateDeadline(start);
console.log(start.toString()); // 2025-04-10
console.log(deadline.toString()); // 2025-05-01
Функция не изменяет аргумент.
Js-joda хорошо сочетается с функциональным подходом.
Основные принципы функционального стиля:
Иммутабельность делает такие практики естественными.
Пример:
const normalize = date =>
date.withDayOfMonth(1);
const shift = date =>
date.plusMonths(1);
const result = shift(
normalize(LocalDate.now())
);
Каждая функция возвращает новый объект.
На первый взгляд может показаться, что создание новых объектов неэффективно.
Однако современные JavaScript-движки хорошо оптимизируют короткоживущие объекты:
Преимущества безопасности и предсказуемости значительно превосходят стоимость создания новых экземпляров.
Распространённая ошибка — забыть сохранить новый объект.
Неправильно:
let date = LocalDate.now();
date.plusDays(1);
console.log(date);
Дата не изменится.
Правильно:
let date = LocalDate.now();
date = date.plusDays(1);
console.log(date);
Разработчики, привыкшие к Date, часто ожидают изменение
текущего экземпляра.
const date = LocalDate.now();
date.minusWeeks(1);
date.plusMonths(2);
Никакие изменения не сохранятся.
Необходимо использовать результат каждого вызова.
Поскольку объекты не изменяются, сравнение становится надёжнее.
const first = LocalDate.of(2025, 1, 1);
const second = first.plusDays(1);
console.log(first.equals(second)); // false
Исходный объект гарантированно не изменился между сравнениями.
Иммутабельность особенно полезна в:
Причины:
const createdAt = LocalDateTime.now();
const publishedAt = createdAt
.plusDays(7)
.withHour(9)
.withMinute(0);
console.log(createdAt.toString());
console.log(publishedAt.toString());
createdAt остаётся исходной датой создания.
Неизменяемые объекты значительно упрощают тесты.
test('add one week', () => {
const source = LocalDate.of(2025, 1, 1);
const result = source.plusWeeks(1);
expect(source.toString())
.toBe('2025-01-01');
expect(result.toString())
.toBe('2025-01-08');
});
Тесты становятся стабильнее, потому что отсутствуют скрытые изменения состояния.
Каждый объект Js-joda хранит собственное состояние:
При вызове метода:
const next = date.plusDays(1);
создаётся новый экземпляр с изменённым значением, а старый объект остаётся прежним.
Js-joda фактически использует концепцию copy-on-write:
Это фундаментальный принцип библиотеки.
Практически весь API библиотеки построен вокруг неизменяемости.
Каждый метод:
Благодаря этому код с Js-joda получается: