В библиотеке js-joda adjuster — это объект или функция, изменяющая дату или время по определённому правилу. Концепция пришла из Java Time API и позволяет инкапсулировать повторяемую логику модификации временных объектов.
Adjuster применяется через метод with():
const upd ated = date.with(adjuster)
Главная идея заключается в том, что операция изменения даты оформляется как самостоятельный переиспользуемый компонент.
Примеры типичных задач:
В основе механизма лежит интерфейс TemporalAdjuster.
Adjuster обязан реализовать метод:
adjustInto(temporal)
Этот метод принимает временной объект и возвращает новый модифицированный объект.
Простейшая структура custom adjuster:
const customAdjuster = {
adjustInto(temporal) {
return temporal.plusDays(1)
}
}
Использование:
const { LocalDate } = require('@js-joda/core')
const date = LocalDate.parse('2025-03-10')
const nextDay = date.with(customAdjuster)
console.log(nextDay.toString())
Результат:
2025-03-11
Все объекты в js-joda immutable.
Это означает:
Пример:
const date = LocalDate.parse('2025-01-01')
const adjuster = {
adjustInto(temporal) {
return temporal.plusWeeks(2)
}
}
const updated = date.with(adjuster)
console.log(date.toString())
console.log(updated.toString())
Вывод:
2025-01-01
2025-01-15
Наиболее распространённый способ — обычный объект с методом
adjustInto.
const {
LocalDate,
DayOfWeek
} = require('@js-joda/core')
const nextMondayAdjuster = {
adjustInto(temporal) {
let current = temporal
while (current.dayOfWeek() !== DayOfWeek.MONDAY) {
current = current.plusDays(1)
}
return current
}
}
const date = LocalDate.parse('2025-05-14')
const result = date.with(nextMondayAdjuster)
console.log(result.toString())
Результат:
2025-05-19
Библиотека содержит готовые adjuster’ы.
Модуль:
const { TemporalAdjusters } = require('@js-joda/core')
Пример:
const result = date.with(
TemporalAdjusters.firstDayOfMonth()
)
Наиболее полезные встроенные adjuster’ы:
| Adjuster | Назначение |
|---|---|
firstDayOfMonth() |
Первый день месяца |
lastDayOfMonth() |
Последний день месяца |
firstDayOfYear() |
Первый день года |
lastDayOfYear() |
Последний день года |
next() |
Следующий указанный день недели |
nextOrSame() |
Следующий или текущий день недели |
previous() |
Предыдущий день недели |
previousOrSame() |
Предыдущий или текущий день недели |
Adjuster можно создавать через функцию.
const workdayStartAdjuster = {
adjustInto(temporal) {
return temporal
.withHour(9)
.withMinute(0)
.withSecond(0)
.withNano(0)
}
}
Использование:
const {
LocalDateTime
} = require('@js-joda/core')
const dateTime = LocalDateTime.parse(
'2025-03-10T15:45:22'
)
const updated = dateTime.with(workdayStartAdjuster)
console.log(updated.toString())
Результат:
2025-03-10T09:00
Одна из самых востребованных задач — пропуск выходных.
const {
DayOfWeek
} = require('@js-joda/core')
const nextBusinessDayAdjuster = {
adjustInto(temporal) {
let current = temporal.plusDays(1)
while (
current.dayOfWeek() === DayOfWeek.SATURDAY ||
current.dayOfWeek() === DayOfWeek.SUNDAY
) {
current = current.plusDays(1)
}
return current
}
}
Проверка:
const friday = LocalDate.parse('2025-05-16')
const result = friday.with(nextBusinessDayAdjuster)
console.log(result.toString())
Результат:
2025-05-19
Реальные бизнес-системы обычно учитывают праздники.
const holidays = [
'2025-01-01',
'2025-01-07',
'2025-05-09'
]
const holidayAwareAdjuster = {
adjustInto(temporal) {
let current = temporal.plusDays(1)
while (true) {
const isWeekend =
current.dayOfWeek() === DayOfWeek.SATURDAY ||
current.dayOfWeek() === DayOfWeek.SUNDAY
const isHoliday =
holidays.includes(current.toString())
if (!isWeekend && !isHoliday) {
return current
}
current = current.plusDays(1)
}
}
}
Полезный подход — создание фабрики adjuster’ов.
function businessDaysLater(days) {
return {
adjustInto(temporal) {
let current = temporal
let added = 0
while (added < days) {
current = current.plusDays(1)
const isWeekend =
current.dayOfWeek() === DayOfWeek.SATURDAY ||
current.dayOfWeek() === DayOfWeek.SUNDAY
if (!isWeekend) {
added++
}
}
return current
}
}
}
Использование:
const result = date.with(
businessDaysLater(5)
)
Adjuster’ы удобно объединять в цепочки.
const result = date
.with(TemporalAdjusters.firstDayOfMonth())
.with(nextBusinessDayAdjuster)
Сценарий:
const endOfQuarterAdjuster = {
adjustInto(temporal) {
const month = temporal.monthValue()
let targetMonth
if (month <= 3) {
targetMonth = 3
} else if (month <= 6) {
targetMonth = 6
} else if (month <= 9) {
targetMonth = 9
} else {
targetMonth = 12
}
return temporal
.withMonth(targetMonth)
.with(TemporalAdjusters.lastDayOfMonth())
}
}
Использование:
const date = LocalDate.parse('2025-05-10')
const result = date.with(endOfQuarterAdjuster)
console.log(result.toString())
Результат:
2025-06-30
const startOfQuarterAdjuster = {
adjustInto(temporal) {
const month = temporal.monthValue()
let targetMonth
if (month <= 3) {
targetMonth = 1
} else if (month <= 6) {
targetMonth = 4
} else if (month <= 9) {
targetMonth = 7
} else {
targetMonth = 10
}
return temporal
.withMonth(targetMonth)
.withDayOfMonth(1)
}
}
Adjuster может изменять не только даты, но и время.
const roundToHourAdjuster = {
adjustInto(temporal) {
return temporal
.withMinute(0)
.withSecond(0)
.withNano(0)
}
}
Использование:
const dateTime = LocalDateTime.parse(
'2025-04-11T10:37:48'
)
const result = dateTime.with(roundToHourAdjuster)
console.log(result.toString())
Результат:
2025-04-11T10:00
Adjuster может принимать настройки.
function timeAdjuster(hour, minute) {
return {
adjustInto(temporal) {
return temporal
.withHour(hour)
.withMinute(minute)
.withSecond(0)
.withNano(0)
}
}
}
Использование:
const result = dateTime.with(
timeAdjuster(14, 30)
)
const nearestBusinessMonday = {
adjustInto(temporal) {
let current = temporal
while (current.dayOfWeek() !== DayOfWeek.MONDAY) {
current = current.plusDays(1)
}
while (
current.dayOfWeek() === DayOfWeek.SATURDAY ||
current.dayOfWeek() === DayOfWeek.SUNDAY
) {
current = current.plusDays(1)
}
return current
}
}
Иногда adjuster рассчитан только на конкретный тип.
const strictDateAdjuster = {
adjustInto(temporal) {
if (!temporal.plusDays) {
throw new Error(
'Adjuster supports only date objects'
)
}
return temporal.plusDays(1)
}
}
Adjuster часто комбинируют с query-механизмом.
const date = LocalDate.now()
const adjusted = date.with(
TemporalAdjusters.lastDayOfMonth()
)
const dayOfWeek = adjusted.dayOfWeek()
console.log(dayOfWeek.toString())
Подход composable позволяет строить сложную бизнес-логику.
function composeAdjusters(...adjusters) {
return {
adjustInto(temporal) {
return adjusters.reduce(
(current, adjuster) => current.with(adjuster),
temporal
)
}
}
}
Использование:
const complexAdjuster = composeAdjusters(
TemporalAdjusters.firstDayOfMonth(),
nextBusinessDayAdjuster,
timeAdjuster(9, 0)
)
При разработке сложных adjuster’ов важно учитывать количество создаваемых объектов.
Неэффективный вариант:
while (condition) {
current = current.plusDays(1)
}
При больших диапазонах это создаёт множество промежуточных immutable-объектов.
Более эффективный подход:
Set вместо массива для поиска.const holidays = new Se t([
'2025-01-01',
'2025-05-09'
])
Проверка:
holidays.has(date.toString())
Неправильно:
temporal.plusDays(1)
return temporal
Правильно:
return temporal.plusDays(1)
Неправильно:
adjustInto(temporal) {
temporal.plusDays(1)
}
Метод обязан вернуть новый temporal-объект.
Ошибка:
while (true) {
}
Любой цикл в adjuster должен гарантированно завершаться.
Неправильно:
current.plusDays(1)
Правильно:
current = current.plusDays(1)
Хорошая практика — хранить adjuster’ы отдельно:
date-adjusters/
business-day.js
quarter.js
holidays.js
Крупные приложения обычно имеют библиотеку:
nextBusinessDayendOfQuarterpayrollDatenextTradingDaysettlementDatestartOfFiscalYearВместо разбросанных вычислений:
date.plusDays(1)
лучше использовать:
date.with(nextBusinessDayAdjuster)
Так код становится:
const friday = LocalDate.parse('2025-05-16')
const result = friday.with(nextBusinessDayAdjuster)
console.assert(
result.toString() === '2025-05-19'
)
const beforeHoliday = LocalDate.parse('2025-05-08')
const result = beforeHoliday.with(
holidayAwareAdjuster
)
console.assert(
result.toString() === '2025-05-12'
)
Условие:
const payrollAdjuster = {
adjustInto(temporal) {
let current = temporal.withDayOfMonth(10)
while (
current.dayOfWeek() === DayOfWeek.SATURDAY ||
current.dayOfWeek() === DayOfWeek.SUNDAY
) {
current = current.minusDays(1)
}
return current
}
}
Использование:
const result = LocalDate
.parse('2025-08-01')
.with(payrollAdjuster)
console.log(result.toString())
const tradingDayAdjuster = {
adjustInto(temporal) {
let current = temporal
while (
current.dayOfWeek() === DayOfWeek.SATURDAY ||
current.dayOfWeek() === DayOfWeek.SUNDAY
) {
current = current.plusDays(1)
}
return current
}
}
const fiscalYearAdjuster = {
adjustInto(temporal) {
return temporal
.withMonth(4)
.withDayOfMonth(1)
}
}
const endOfWeekAdjuster = {
adjustInto(temporal) {
return temporal.with(
TemporalAdjusters.nextOrSame(
DayOfWeek.SUNDAY
)
)
}
}
В крупных проектах удобно создавать registry.
const adjusters = {
nextBusinessDay: nextBusinessDayAdjuster,
payroll: payrollAdjuster,
quarterEnd: endOfQuarterAdjuster
}
Использование:
date.with(adjusters.quarterEnd)
Adjuster’ы особенно полезны в:
Пример доменного вызова:
invoiceDate.with(paymentDueAdjuster)
Такой код значительно выразительнее ручных вычислений дат.