W3docs

Java TemporalAdjusters

Вычисление дат относительно других в Java с помощью TemporalAdjusters — firstDayOfMonth, next, previous.

plusDays(7) добавляет семь дней. withDayOfMonth(15) переводит на 15-е число. Эти два метода покрывают простые случаи. TemporalAdjuster — это объект в форме функции, который передаётся в Temporal.with(adjuster) для обработки случаев, когда новая дата зависит от текущей более сложным образом: «первый понедельник следующего месяца», «последний день этого квартала», «американский День благодарения следующего года».

Интерфейс содержит один метод:

@FunctionalInterface
interface TemporalAdjuster {
  Temporal adjustInto(Temporal temporal);
}

Вам обычно не нужно реализовывать его самостоятельно. Класс java.time.temporal.TemporalAdjusters (обратите внимание на sAdjusters, множественное число) — это набор около дюжины встроенных корректоров, покрывающих типичные случаи, а LocalDate.with(adjuster) — способ их применения.

Встроенный каталог

Импортируйте статически — код читается лучше:

import static java.time.temporal.TemporalAdjusters.*;

Затем:

КорректорЭффект
firstDayOfMonth()день → первый день того же месяца
lastDayOfMonth()день → последний день того же месяца
firstDayOfNextMonth()день → первый день следующего месяца
firstDayOfYear()день → 1 января того же года
lastDayOfYear()день → 31 декабря того же года
firstDayOfNextYear()день → 1 января следующего года
next(DayOfWeek dow)следующий dow строго после даты
nextOrSame(DayOfWeek dow)сегодня, если это dow, иначе следующий dow
previous(DayOfWeek dow)ближайший dow строго до даты
previousOrSame(DayOfWeek dow)сегодня, если это dow, иначе предыдущий dow
dayOfWeekInMonth(int ord, DayOfWeek dow)n-й день недели в месяце: dayOfWeekInMonth(3, MONDAY) → 3-й понедельник
firstInMonth(DayOfWeek dow)первый dow в месяце (эквивалентно dayOfWeekInMonth(1, dow))
lastInMonth(DayOfWeek dow)последний dow в месяце

Две половины отвечают на вопросы «что такое первый/последний X» (верхняя половина) и «что такое следующий/предыдущий X» (нижняя половина). Объединяйте их в цепочку, когда вопрос сложнее:

LocalDate firstMondayNextMonth = today.with(firstDayOfNextMonth()).with(nextOrSame(DayOfWeek.MONDAY));

Это «первый понедельник, начиная с первого дня следующего месяца». Читайте слева направо; каждый with — это шаг.

Применение с with(adjuster)

LocalDate today = LocalDate.now();
LocalDate eom = today.with(lastDayOfMonth());
LocalDate nextMonday = today.with(next(DayOfWeek.MONDAY));
LocalDate thirdFriday = today.with(dayOfWeekInMonth(3, DayOfWeek.FRIDAY));

with присутствует на каждом Temporal: LocalDate, LocalDateTime, ZonedDateTime и даже OffsetDateTime. Корректор затрагивает только компоненты даты — применение lastDayOfMonth() к LocalDateTime не изменяет время.

Корректоры являются тотальными: они всегда возвращают результат. Нет исключения «что если сегодня не в нужном месяце» — lastDayOfMonth() от 31 января всё равно вернёт 31 января (последний день — сам день).

Lambda-корректоры

Поскольку TemporalAdjuster является @FunctionalInterface, вы можете написать собственный с помощью лямбды:

TemporalAdjuster nextWorkingDay = t -> {
  LocalDate d = LocalDate.from(t).plusDays(1);
  while (d.getDayOfWeek() == DayOfWeek.SATURDAY || d.getDayOfWeek() == DayOfWeek.SUNDAY) {
    d = d.plusDays(1);
  }
  return d;
};

LocalDate friday = LocalDate.of(2025, 11, 7);
LocalDate monday = friday.with(nextWorkingDay);                 // 2025-11-10

Паттерн: преобразуйте входящий Temporal в нужный тип даты (LocalDate.from(t)), вычислите новую дату, верните её. Тип возвращаемого значения — Temporal (интерфейс), но JDK принимает конкретное возвращаемое значение.

Для разового использования это избыточно — просто встройте логику рядом с местом вызова. Для вычисления, которое вы будете использовать в нескольких местах (следующий рабочий день, последний день квартала, официальный праздник), оформление его в виде корректора сохраняет читаемость мест вызова.

ofDateAdjuster для простого случая с лямбдой

Если ваш корректор работает только с LocalDate (распространённый случай), TemporalAdjusters.ofDateAdjuster(UnaryOperator<LocalDate>) является более чистой фабрикой:

TemporalAdjuster nextWorkingDay = TemporalAdjusters.ofDateAdjuster(d -> {
  LocalDate result = d.plusDays(1);
  while (result.getDayOfWeek().getValue() > 5) {
    result = result.plusDays(1);
  }
  return result;
});

Лямбда принимает и возвращает LocalDate. Фабрика оборачивает её как TemporalAdjuster. Именно к этому вы прибегаете в 90% случаев при написании пользовательских корректоров.

Практический пример: бизнес-даты, праздники, конец периода

Приведённая ниже программа использует корректоры для бухгалтерского календаря: конец месяца для выставления счетов, последний рабочий день месяца для закрытия денежных позиций, первый понедельник следующего месяца для регулярного совещания по планированию, корректор «следующий рабочий день» с учётом праздников и вычисление конца квартала.

java— editable, runs on the server

Что стоит взять из запуска:

  • Встроенные корректоры покрыли граничные случаи (firstDayOfMonth, lastDayOfMonth, firstDayOfYear и т.д.) без каких-либо арифметических действий с вашей стороны. Для «прикрепления даты к началу/концу месяца или года» они — правильный инструмент: яснее, чем ручные вычисления в стиле withDayOfMonth(1), и защищены от неожиданностей с длиной месяца.
  • next(MONDAY) и previous(FRIDAY) вернули строго другие даты; nextOrSame(TUESDAY) для вторника вернул сегодня. Запомните различие строгого и «или равного» — это источник большинства ошибок «на одну неделю», когда дата начала совпадает с целевым днём недели.
  • Цепочка firstDayOfNextMonth().nextOrSame(MONDAY) выразила «первый понедельник, начиная с первого дня следующего месяца» двумя шагами. Цепочка — одна строка; ручной эквивалент — шесть. Объединение корректоров в цепочку — идиоматичный способ их компоновки.
  • NEXT_BUSINESS_DAY пропустил выходные и праздник за один шаг. Набор HOLIDAYS был синтетическим примером из двух элементов; реальная реализация загружала бы данные из сервиса. Форма корректора одна и та же — оберните цикл в TemporalAdjusters.ofDateAdjuster(...) и можно использовать его в вызовах today.with(NEXT_BUSINESS_DAY) везде.
  • END_OF_QUARTER был однострочным пользовательским корректором, который определял третий месяц квартала даты и применял lastDayOfMonth(). Суть: сложные операции с датами принадлежат именованным константам TemporalAdjuster, где место вызова читается как someDate.with(END_OF_QUARTER) вместо встроенного шестистрочного арифметического блока. Держите логику работы с календарём в одном месте.

Что дальше

TemporalAdjusters завершают современный API java.time. Последние две главы этой части посвящены устаревшим типам, которые вы встретите в старом коде: Java Legacy Date Class для оригинального java.util.Date и Java Calendar Class для java.util.Calendar. Оба по-прежнему существуют для обеспечения совместимости; для обоих существует чистый путь преобразования в java.time.

Практика

Практика
Вам нужен 'первый понедельник следующего месяца' начиная с любой заданной даты. Какое выражение возвращает это значение наиболее идиоматично?
Вам нужен 'первый понедельник следующего месяца' начиная с любой заданной даты. Какое выражение возвращает это значение наиболее идиоматично?
Was this page helpful?