Wenn du mit java.time-Objekten wie LocalDate arbeitest, musst du oft komplexe Datumsanpassungen vornehmen, zum Beispiel den „zweiten Dienstag des Monats“ finden.
Statt das von Hand zu berechnen, kannst du TemporalAdjusters verwenden.
Die Hilfsklasse java.time.temporal.TemporalAdjusters (beachte das Plural-s) bietet statische Methoden für viele gängige Datumsanpassungen.
Du wendest diese Anpassungen mit der Methode .with() auf ein Temporal-Objekt (wie LocalDate) an.
Die Verwendung von with(TemporalAdjuster) gibt eine neue Instanz zurück und aktualisiert die vorhandene Instanz nicht, da java.time-Objekte unveränderlich sind.
import java.time.LocalDate;
import java.time.Month;
import java.time.DayOfWeek;
import java.time.temporal.TemporalAdjusters;
LocalDate date = LocalDate.of(2024, Month.AUGUST, 15); // 2024-08-15 (a Thursday)
// Use a predefined adjuster: find the last day of the month
LocalDate lastDay = date.with(TemporalAdjusters.lastDayOfMonth());
// => 2024-08-31
Hier sind einige wichtige Adjuster, die für Terminberechnungen nützlich sind:
Mit dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek) findest du das n-te Vorkommen eines Wochentags.
// Find the second Tuesday of August 2024
LocalDate secondTuesday = date.with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.TUESDAY));
// => 2024-08-13
Verwende firstInMonth(DayOfWeek dayOfWeek) oder lastInMonth(DayOfWeek dayOfWeek).
// Find the last Monday of August 2024
LocalDate lastMonday = date.with(TemporalAdjusters.lastInMonth(DayOfWeek.MONDAY));
// => 2024-08-26
Adjuster wie next(), previous(), nextOrSame() und previousOrSame() finden das nächste oder vorherige Vorkommen eines Wochentags relativ zum aktuellen Datum.
next()/previous(): Das aktuelle Datum zählt nicht mit, wenn es auf diesen Wochentag fällt.nextOrSame()/previousOrSame(): Das aktuelle Datum zählt mit, wenn es auf diesen Wochentag fällt.LocalDate thursday = LocalDate.of(2024, Month.AUGUST, 15); // A Thursday
// Find the next Thursday (exclusive)
LocalDate nextThursday = thursday.with(TemporalAdjusters.next(DayOfWeek.THURSDAY));
// => 2024-08-22
// Find the next Thursday (inclusive) - returns the same date
LocalDate nextOrSameThursday = thursday.with(TemporalAdjusters.nextOrSame(DayOfWeek.THURSDAY));
// => 2024-08-15
// Find the previous Monday (exclusive)
LocalDate previousMonday = thursday.with(TemporalAdjusters.previous(DayOfWeek.MONDAY));
// => 2024-08-12
TemporalAdjuster ist ein funktionales Interface, du kannst also einen eigenen Adjuster als Lambda schreiben, das das anzupassende Temporal entgegennimmt und das angepasste zurückgibt.
Er wird genau wie die vordefinierten Adjuster mit .with() angewendet und kann diese intern nutzen.
import java.time.temporal.TemporalAdjuster;
// An adjuster that moves a weekend date to the following Monday
TemporalAdjuster skipWeekend = temporal -> {
DayOfWeek day = DayOfWeek.from(temporal);
if (day == DayOfWeek.SATURDAY || day == DayOfWeek.SUNDAY) {
return temporal.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
}
return temporal;
};
LocalDate saturday = LocalDate.of(2024, Month.AUGUST, 17);
LocalDate monday = saturday.with(skipWeekend);
// => 2024-08-19
Mit diesen Adjustern aus der Klasse TemporalAdjusters lassen sich Datumsberechnungen deutlich vereinfachen.
Der Schönheitssalon in New York, dem du in der Übung „Booking Up For Beauty“ geholfen hast, läuft gut, und viele seiner Kundinnen und Kunden sind inzwischen Stammkunden. Statt jeden Termin einzeln zu buchen, hat ein Stammkunde eine feste Abmachung: „jeden zweiten Dienstag“, „den letzten Freitag im Monat“ und so weiter. Die Inhaberin hat dich um einen Helfer gebeten, der die Termine hinter diesen Abmachungen ausrechnet, ebenso wie die Termine der monatlichen Routine des Salons.
Implementiere die Methode SalonCalendar.standingAppointment(), die ein Jahr, einen Monat, ein Vorkommen (1 für das erste, 2 für das zweite und so weiter) und einen Wochentag entgegennimmt.
Sie gibt das Datum zurück, auf das dieses Vorkommen des Wochentags im Monat fällt:
SalonCalendar calendar = new SalonCalendar();
calendar.standingAppointment(2024, Month.AUGUST, 2, DayOfWeek.TUESDAY);
// => LocalDate.of(2024, 8, 13)
Manche Stammkunden bevorzugen das letzte Vorkommen ihres Wochentags im Monat.
Implementiere die Methode SalonCalendar.lastAppointment(), die ein Jahr, einen Monat und einen Wochentag entgegennimmt und das Datum des letzten Vorkommens dieses Wochentags im Monat zurückgibt:
SalonCalendar calendar = new SalonCalendar();
calendar.lastAppointment(2024, Month.AUGUST, DayOfWeek.FRIDAY);
// => LocalDate.of(2024, 8, 30)
Eine Kundin am Empfang fragt nach „dem nächsten Samstag“.
Ist der Tag, an dem sie fragt, selbst schon ein Samstag, meint sie genau diesen Tag.
Implementiere die Methode SalonCalendar.nextAvailable(), die ein Datum und einen Wochentag entgegennimmt und das erste Datum ab dem angegebenen Datum zurückgibt, das auf diesen Wochentag fällt:
SalonCalendar calendar = new SalonCalendar();
calendar.nextAvailable(LocalDate.of(2024, 8, 15), DayOfWeek.SATURDAY);
// => LocalDate.of(2024, 8, 17)
calendar.nextAvailable(LocalDate.of(2024, 8, 17), DayOfWeek.SATURDAY);
// => LocalDate.of(2024, 8, 17)
Der Salon schließt seine Bücher am letzten Tag jedes Monats.
Am Wochenende ist der Salon allerdings geschlossen. Fällt der letzte Tag des Monats also auf einen Samstag oder einen Sonntag, werden die Bücher am Freitag davor geschlossen.
Implementiere die Methode SalonCalendar.bookkeepingDay(), die ein Jahr und einen Monat entgegennimmt und den Buchhaltungstag dieses Monats zurückgibt:
SalonCalendar calendar = new SalonCalendar();
calendar.bookkeepingDay(2024, Month.SEPTEMBER);
// => LocalDate.of(2024, 9, 30)
calendar.bookkeepingDay(2024, Month.AUGUST);
// => LocalDate.of(2024, 8, 30)
Das Personal wird am 15. jedes Monats bezahlt, mit derselben Wochenendregel: Fällt der 15. auf einen Samstag oder einen Sonntag, ist der Zahltag der Freitag davor. Statt die Regel ein zweites Mal zu schreiben, machst du daraus einen wiederverwendbaren Adjuster.
Implementiere die Methode SalonCalendar.precedingWorkday(), die einen TemporalAdjuster zurückgibt, der einen Wochentag unverändert lässt und einen Samstag oder einen Sonntag auf den vorhergehenden Freitag zurückschiebt.
Implementiere dann die Methode SalonCalendar.payday(), die ein Jahr und einen Monat entgegennimmt und dabei diesen Adjuster verwendet:
SalonCalendar calendar = new SalonCalendar();
LocalDate.of(2024, 6, 15).with(calendar.precedingWorkday());
// => LocalDate.of(2024, 6, 14)
calendar.payday(2024, Month.JUNE);
// => LocalDate.of(2024, 6, 14)
calendar.payday(2024, Month.AUGUST);
// => LocalDate.of(2024, 8, 15)
Du kannst gern zurückgehen und den Adjuster auch in bookkeepingDay() verwenden.
Melde dich bei Exercism an, um Java mit 27 Konzepte159 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.