Lorsque tu travailles avec des objets java.time comme LocalDate, il faut souvent effectuer des ajustements de date complexes, par exemple trouver le « deuxième mardi du mois ».
Plutôt que de calculer cela à la main, on peut utiliser TemporalAdjusters.
La classe utilitaire java.time.temporal.TemporalAdjusters (attention au « s » du pluriel) fournit des méthodes statiques pour de nombreux ajustements de date courants.
On applique ces ajustements avec la méthode .with() sur un objet temporel (comme LocalDate).
Utiliser with(TemporalAdjuster) renvoie une nouvelle instance et ne met pas à jour l'instance existante, car les objets java.time sont immuables.
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
Voici quelques ajusteurs essentiels, bien utiles pour les calculs de planification :
Utilise dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek) pour trouver la n-ième occurrence d'un jour de la semaine.
// Find the second Tuesday of August 2024
LocalDate secondTuesday = date.with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.TUESDAY));
// => 2024-08-13
Utilise firstInMonth(DayOfWeek dayOfWeek) ou lastInMonth(DayOfWeek dayOfWeek).
// Find the last Monday of August 2024
LocalDate lastMonday = date.with(TemporalAdjusters.lastInMonth(DayOfWeek.MONDAY));
// => 2024-08-26
Des ajusteurs comme next(), previous(), nextOrSame() et previousOrSame() trouvent l'occurrence suivante ou précédente d'un jour de la semaine donné, par rapport à la date courante.
next()/previous() : excluent la date courante si elle correspond.nextOrSame()/previousOrSame() : incluent la date courante si elle correspond.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 est une interface fonctionnelle : tu peux donc écrire ton propre ajusteur sous forme de lambda, qui prend l'objet temporel à ajuster et renvoie l'objet ajusté.
Il s'applique avec .with(), exactement comme les ajusteurs prédéfinis, et il peut les utiliser en interne.
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
Utiliser ces ajusteurs de la classe TemporalAdjusters simplifie énormément les tâches de manipulation de dates.
Le salon de beauté de New York pour lequel tu as travaillé dans l'exercice Prendre rendez-vous pour la beauté se porte bien, et beaucoup de ses clients sont devenus des habitués. Au lieu de réserver chaque visite séparément, un habitué a un rendez-vous fixe : « un mardi sur deux », « le dernier vendredi du mois », et ainsi de suite. La propriétaire t'a demandé un outil qui calcule les dates qui se cachent derrière ces rendez-vous, ainsi que les dates de la routine mensuelle du salon.
Implémente la méthode SalonCalendar.standingAppointment() qui prend une année, un mois, une occurrence (1 pour la première, 2 pour la deuxième, etc.) et un jour de la semaine.
Elle renvoie la date de cette occurrence du jour de la semaine dans le mois :
SalonCalendar calendar = new SalonCalendar();
calendar.standingAppointment(2024, Month.AUGUST, 2, DayOfWeek.TUESDAY);
// => LocalDate.of(2024, 8, 13)
Certains habitués préfèrent la dernière occurrence de leur jour dans le mois.
Implémente la méthode SalonCalendar.lastAppointment() qui prend une année, un mois et un jour de la semaine, et renvoie la date de la dernière occurrence de ce jour de la semaine dans le mois :
SalonCalendar calendar = new SalonCalendar();
calendar.lastAppointment(2024, Month.AUGUST, DayOfWeek.FRIDAY);
// => LocalDate.of(2024, 8, 30)
Un client à l'accueil demande « le prochain samedi ».
Si le jour où il demande est déjà un samedi, il parle de ce jour-là même.
Implémente la méthode SalonCalendar.nextAvailable() qui prend une date et un jour de la semaine, et renvoie la première date à partir de la date donnée, celle-ci incluse, qui tombe ce jour de la semaine :
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)
Le salon clôture ses comptes le dernier jour de chaque mois.
Mais le salon est fermé le week-end, donc quand le dernier jour du mois tombe un samedi ou un dimanche, les comptes sont clôturés le vendredi précédent.
Implémente la méthode SalonCalendar.bookkeepingDay() qui prend une année et un mois, et renvoie le jour de clôture de ce mois :
SalonCalendar calendar = new SalonCalendar();
calendar.bookkeepingDay(2024, Month.SEPTEMBER);
// => LocalDate.of(2024, 9, 30)
calendar.bookkeepingDay(2024, Month.AUGUST);
// => LocalDate.of(2024, 8, 30)
Le personnel est payé le 15 de chaque mois, avec la même règle du week-end : quand le 15 tombe un samedi ou un dimanche, le jour de paie est le vendredi précédent. Plutôt que d'écrire la règle une seconde fois, transforme-la en un ajusteur réutilisable.
Implémente la méthode SalonCalendar.precedingWorkday() qui renvoie un TemporalAdjuster laissant un jour de semaine inchangé et ramenant un samedi ou un dimanche au vendredi précédent.
Implémente ensuite la méthode SalonCalendar.payday(), qui prend une année et un mois, en utilisant cet ajusteur :
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)
N'hésite pas à revenir en arrière pour utiliser cet ajusteur dans bookkeepingDay() également.
Inscris-toi sur Exercism pour apprendre et maîtriser Java avec 27 concepts159 exercices, et un vrai mentorat humain, le tout gratuitement.