Fluent interface

Fluent interface в js-joda основан на цепочках вызовов методов, возвращающих новые неизменяемые экземпляры временных объектов. Каждый вызов трансформации не модифицирует исходное значение, а создаёт новый объект с применёнными изменениями. Это формирует выразительный стиль работы с датами и временем, в котором последовательность операций читается как единое выражение.

Ключевая особенность заключается в том, что все временные типы библиотеки — LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant — спроектированы как immutable-объекты. Fluent interface становится естественным следствием этой модели.


Базовая структура цепочек вызовов

Fluent API в js-joda строится вокруг идеи последовательного преобразования состояния:

LocalDate.now()
  .plusDays(5)
  .minusMonths(2)
  .plusYears(1)

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

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


Immutable как основа fluent-подхода

В отличие от классических Date API, где операции часто изменяют объект, js-joda гарантирует неизменяемость:

const date = LocalDate.of(2024, 1, 10);
const updated = date.plusDays(10);

date.toString();      // 2024-01-10
updated.toString();   // 2024-01-20

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


Композиция операций над датами

Fluent interface позволяет строить сложные выражения из простых операций:

const result = LocalDate.now()
  .plusWeeks(2)
  .withDayOfMonth(1)
  .minusDays(1);

Здесь последовательно применяются:

  • сдвиг на 2 недели
  • установка первого дня месяца
  • смещение на один день назад

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


Fluent работа с временем

Для LocalTime цепочки аналогичны, но оперируют временными компонентами:

const time = LocalTime.of(10, 30)
  .plusHours(3)
  .plusMinutes(45)
  .minusSeconds(10);

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


Комбинирование даты и времени

LocalDateTime объединяет оба контекста и поддерживает более сложные цепочки:

const dt = LocalDateTime.now()
  .plusDays(1)
  .plusHours(2)
  .withMinute(0)
  .withSecond(0);

Fluent интерфейс здесь позволяет формировать нормализованные временные метки, например, для начала событий или дедлайнов.


Работа с часовыми поясами

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

const zoned = ZonedDateTime.now()
  .plusDays(1)
  .withZoneSameInstant(ZoneId.of("Europe/Berlin"))
  .plusHours(2);

Методы делятся на две категории:

  • операции над моментом времени (plusDays, minusHours)
  • преобразования зоны (withZoneSameInstant, withZoneSameLocal)

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


Функциональная трансформация через with

Методы with* являются ключевым элементом fluent API, позволяя заменять отдельные компоненты:

const modified = LocalDate.now()
  .withYear(2030)
  .withMonth(12)
  .withDayOfMonth(25);

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


Парсинг и последующая цепочка операций

Fluent interface распространяется и на результаты парсинга строк:

const date = LocalDate.parse("2025-05-10")
  .plusDays(10)
  .minusMonths(1);

Парсинг создаёт начальный объект, после чего он участвует в цепочке преобразований без дополнительных шагов.


Формирование периодов и длительностей

Fluent подход используется и при работе с промежутками времени:

const period = Period.ofMonths(2)
  .plusWeeks(1)
  .plusDays(3);

или для длительностей:

const duration = Duration.ofHours(5)
  .plusMinutes(30)
  .minusSeconds(15);

Каждый метод возвращает новый объект периода или длительности, поддерживая единый стиль API.


Интеграция с Instant

Instant представляет момент времени в UTC и поддерживает fluent операции:

const instant = Instant.now()
  .plusSeconds(60)
  .plusMillis(500);

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


Вложенные цепочки и выразительные конструкции

Fluent interface позволяет формировать вложенные выражения, где результат одной цепочки используется как вход для другой:

const result = ZonedDateTime.now()
  .plusDays(1)
  .toLocalDate()
  .atStartOfDay()
  .plusHours(6);

Здесь происходит переход между типами:

  • ZonedDateTime
  • LocalDate
  • LocalDateTime

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


Использование арифметики времени в цепочках

Fluent API поддерживает симметричную работу с добавлением и вычитанием:

const adjusted = LocalDate.now()
  .plusDays(10)
  .minusDays(3)
  .plusDays(1)
  .minusMonths(2);

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


Нормализация значений внутри цепочек

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

const time = LocalTime.of(23, 50)
  .plusMinutes(30);

Результат переносит значение на следующий день, сохраняя корректность модели времени. Fluent интерфейс скрывает эти детали за последовательностью методов.


Условные трансформации через цепочки

Fluent стиль часто комбинируется с внешней логикой:

let result = LocalDate.now();

result = result.plusDays(5);

if (result.getDayOfWeek().equals(DayOfWeek.SATURDAY)) {
  result = result.plusDays(2);
}

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


Повторное применение цепочек к одному значению

Один и тот же исходный объект может использоваться в нескольких независимых цепочках:

const base = LocalDate.of(2024, 6, 1);

const a = base.plusDays(10).plusMonths(1);
const b = base.minusDays(5).withYear(2025);

Fluent интерфейс в сочетании с immutable-моделью позволяет строить независимые вычислительные ветви.


Сочетание fluent API с функциями высшего порядка

Цепочки могут комбинироваться с функциями, возвращающими временные объекты:

const shift = (date) => date.plusDays(3).minusMonths(1);

const result = shift(LocalDate.now())
  .plusYears(2)
  .withDayOfMonth(15);

Fluent интерфейс остаётся совместимым с функциональным стилем, не нарушая композиционность.


Построение декларативных временных выражений

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

const schedule = LocalDateTime.now()
  .plusDays(7)
  .withHour(9)
  .withMinute(0)
  .withSecond(0)
  .withNano(0);

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


Поведение методов при выходе за границы календаря

Fluent API скрывает сложность календарных вычислений:

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

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


Единообразие интерфейсов across типов

Fluent стиль js-joda унифицирует работу со всеми временными типами:

  • plus* — добавление
  • minus* — вычитание
  • with* — замена компонентов
  • to* — преобразование типов

Эта структура делает переход между LocalDate, LocalTime и ZonedDateTime предсказуемым и симметричным.


Композиционная природа fluent-цепочек

Каждая цепочка представляет собой композицию функций:

f(g(h(x)))

но записанную в линейной форме:

x.h().g().f()

В js-joda этот принцип распространяется на все временные операции, создавая единый язык описания преобразований времени.