Java TemporalAdjusters
Datumsberechnungen in Java mit TemporalAdjusters – firstDayOfMonth, next, previous und eigene Adjuster.
plusDays(7) fügt sieben Tage hinzu. withDayOfMonth(15) springt auf den 15. Diese beiden Varianten decken die einfachen Fälle ab. Ein TemporalAdjuster ist ein funktionsartiges Objekt, das Sie an Temporal.with(adjuster) übergeben, um Fälle zu behandeln, in denen das neue Datum vom aktuellen auf kompliziertere Weise abhängt: „der erste Montag des nächsten Monats", „der letzte Tag dieses Quartals", „das amerikanische Thanksgiving des nächsten Jahres".
Das Interface hat eine einzige Methode:
@FunctionalInterface
interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}Normalerweise implementieren Sie diese nicht selbst. Die Klasse java.time.temporal.TemporalAdjusters (beachten Sie das s — Adjusters, Plural) enthält etwa ein Dutzend eingebauter Adjuster für die gängigsten Fälle, und mit LocalDate.with(adjuster) wenden Sie diese an.
Der eingebaute Katalog
Importieren Sie statisch; der Code liest sich so besser:
import static java.time.temporal.TemporalAdjusters.*;Dann:
| Adjuster | Wirkung |
|---|---|
firstDayOfMonth() | Tag → erster Tag desselben Monats |
lastDayOfMonth() | Tag → letzter Tag desselben Monats |
firstDayOfNextMonth() | Tag → erster Tag des nächsten Monats |
firstDayOfYear() | Tag → 1. Januar desselben Jahres |
lastDayOfYear() | Tag → 31. Dezember desselben Jahres |
firstDayOfNextYear() | Tag → 1. Januar des nächsten Jahres |
next(DayOfWeek dow) | der nächste dow strikt nach dem Datum |
nextOrSame(DayOfWeek dow) | heute, wenn es dow ist, sonst nächster dow |
previous(DayOfWeek dow) | der jüngste dow strikt vor dem Datum |
previousOrSame(DayOfWeek dow) | heute, wenn es dow ist, sonst vorheriger dow |
dayOfWeekInMonth(int ord, DayOfWeek dow) | n-ter Wochentag des Monats: dayOfWeekInMonth(3, MONDAY) → 3. Montag |
firstInMonth(DayOfWeek dow) | erster dow des Monats (entspricht dayOfWeekInMonth(1, dow)) |
lastInMonth(DayOfWeek dow) | letzter dow des Monats |
Die obere Hälfte beantwortet „Was ist der erste/letzte X?" und die untere Hälfte „Was ist der nächste/vorherige X?". Verketten Sie sie, wenn die Frage komplexer ist:
LocalDate firstMondayNextMonth = today.with(firstDayOfNextMonth()).with(nextOrSame(DayOfWeek.MONDAY));Das bedeutet: „der erste Montag am oder nach dem ersten des nächsten Monats." Von links nach rechts lesen; jedes with ist ein Schritt.
Anwenden mit 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 existiert auf jedem Temporal: LocalDate, LocalDateTime, ZonedDateTime, sogar OffsetDateTime. Der Adjuster berührt nur die Datumskomponenten — die Anwendung von lastDayOfMonth() auf ein LocalDateTime lässt die Uhrzeit unverändert.
Adjuster sind total: Sie liefern immer ein Ergebnis. Es gibt keinen Ausnahmepfad für „Was, wenn heute nicht im richtigen Monat ist?" — lastDayOfMonth() vom 31. Januar gibt weiterhin den 31. Januar zurück (der letzte Tag ist er selbst).
Lambda-Adjuster
Da TemporalAdjuster ein @FunctionalInterface ist, können Sie eigene mit einem Lambda schreiben:
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-10Das Muster: Den eingehenden Temporal in den gewünschten Datumstyp umwandeln (LocalDate.from(t)), das neue Datum berechnen, zurückgeben. Der Rückgabetyp ist Temporal (das Interface), aber das JDK akzeptiert den konkreten Rückgabewert.
Für den einmaligen Gebrauch ist das übertrieben — schreiben Sie die Logik einfach direkt an die Aufrufstelle. Für eine Berechnung, die Sie an mehreren Stellen verwenden (nächster Werktag, letzter Tag des Quartals, beobachteter Feiertag), hält sie die Aufrufstellen lesbar, wenn Sie sie als Adjuster bündeln.
ofDateAdjuster für den einfachen Lambda-Fall
Wenn Ihr Adjuster nur mit LocalDate arbeitet (der häufigste Fall), ist TemporalAdjusters.ofDateAdjuster(UnaryOperator<LocalDate>) die sauberere Factory:
TemporalAdjuster nextWorkingDay = TemporalAdjusters.ofDateAdjuster(d -> {
LocalDate result = d.plusDays(1);
while (result.getDayOfWeek().getValue() > 5) {
result = result.plusDays(1);
}
return result;
});Das Lambda nimmt ein LocalDate und gibt eines zurück. Die Factory verpackt es als TemporalAdjuster. Das ist das Mittel der Wahl in 90 % der Fälle beim Schreiben eigener Adjuster.
Ein ausgearbeitetes Beispiel: Geschäftsdaten, Feiertage, Periodenende
Das folgende Programm verwendet Adjuster für den Buchhaltungskalender: Monatsende für die Abrechnung, letzter Geschäftstag des Monats für den Cash Close, erster Montag des nächsten Monats für ein regelmäßiges Planungsmeeting, einen feiertagsbewussten „nächsten Werktag"-Adjuster und die Berechnung des Quartalsendes.
Was aus der Ausführung zu entnehmen ist:
- Die eingebauten Adjuster deckten die Grenzfälle ab (
firstDayOfMonth,lastDayOfMonth,firstDayOfYearusw.) ohne jegliche Arithmetik Ihrerseits. Für „dieses Datum auf den Anfang/Ende seines Monats oder Jahres runden" sind sie das richtige Werkzeug — klarer alswithDayOfMonth(1)-Berechnungen per Hand und immun gegen Überraschungen bei der Monatslänge. next(MONDAY)undprevious(FRIDAY)lieferten strikt verschiedene Daten;nextOrSame(TUESDAY)an einem Dienstag lieferte das heutige Datum. Merken Sie sich die Unterscheidung strikt-vs-oder-gleich; sie ist die Quelle der meisten „um eine Woche daneben"-Fehler, wenn das Ausgangsdatum zufällig auf den Zielwochentag fällt.- Das verkettete
firstDayOfNextMonth().nextOrSame(MONDAY)drückte „den ersten Montag am oder nach dem ersten des nächsten Monats" in zwei Lesevorgängen aus. Die Kette ist eine Zeile; das handgefertigte Äquivalent sind sechs. Das Verketten von Adjustern ist die idiomatische Art, sie zu kombinieren. NEXT_BUSINESS_DAYübersprang das Wochenende und einen Feiertag in einem Schritt. DieHOLIDAYS-Menge war ein synthetisches Zwei-Elemente-Beispiel; eine echte Implementierung würde aus einem Dienst laden. Die Adjuster-Form ist dieselbe — wickeln Sie die Schleife inTemporalAdjusters.ofDateAdjuster(...)und Sie können ihn überall intoday.with(NEXT_BUSINESS_DAY)-Aufrufen einsetzen.END_OF_QUARTERwar ein einzeiliger benutzerdefinierter Adjuster, der den dritten Monat des Quartals des Datums auswählte undlastDayOfMonth()anwendete. Der Punkt: Komplexe Domänenoperationen gehören in benannteTemporalAdjuster-Konstanten, bei denen die AufrufstellesomeDate.with(END_OF_QUARTER)liest, anstatt einen sechszeiligen Arithmetikblock einzubetten. Halten Sie die Kalenderlogik an einem Ort.
Was kommt als nächstes
TemporalAdjusters schließen die moderne java.time-API ab. Die letzten beiden Kapitel dieses Teils behandeln die Legacy-Typen, denen Sie in älterem Code begegnen: Java Legacy Date Class für das ursprüngliche java.util.Date und Java Calendar Class für java.util.Calendar. Beide sind noch aus Kompatibilitätsgründen vorhanden; beide haben einen sauberen Konvertierungspfad zu java.time.