Java TemporalAdjusters
Calculez des dates relatives en Java avec TemporalAdjusters — firstDayOfMonth, next, previous.
plusDays(7) ajoute sept jours. withDayOfMonth(15) saute au 15. Ces deux méthodes couvrent les cas simples. Un TemporalAdjuster est un objet en forme de fonction que vous passez à Temporal.with(adjuster) pour gérer les cas où la nouvelle date dépend de la date courante d'une manière plus complexe : « le premier lundi du mois prochain », « le dernier jour de ce trimestre », « la Thanksgiving américaine de l'année prochaine ».
L'interface possède une seule méthode :
@FunctionalInterface
interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}Vous n'avez généralement pas à l'implémenter. La classe java.time.temporal.TemporalAdjusters (notez le s — Adjusters, pluriel) est une boîte à outils d'une douzaine de modificateurs intégrés couvrant les cas courants, et LocalDate.with(adjuster) est la façon de les appliquer.
Le catalogue des intégrés
Importez-les statiquement ; le code se lit mieux ainsi :
import static java.time.temporal.TemporalAdjusters.*;Ensuite :
| Modificateur | Effet |
|---|---|
firstDayOfMonth() | jour → premier jour du même mois |
lastDayOfMonth() | jour → dernier jour du même mois |
firstDayOfNextMonth() | jour → premier jour du mois suivant |
firstDayOfYear() | jour → 1er janvier de la même année |
lastDayOfYear() | jour → 31 décembre de la même année |
firstDayOfNextYear() | jour → 1er janvier de l'année suivante |
next(DayOfWeek dow) | le prochain dow strictement après la date |
nextOrSame(DayOfWeek dow) | aujourd'hui si c'est dow, sinon le prochain dow |
previous(DayOfWeek dow) | le dow le plus récent strictement avant la date |
previousOrSame(DayOfWeek dow) | aujourd'hui si c'est dow, sinon le précédent dow |
dayOfWeekInMonth(int ord, DayOfWeek dow) | nième jour de la semaine du mois : dayOfWeekInMonth(3, MONDAY) → 3e lundi |
firstInMonth(DayOfWeek dow) | premier dow du mois (équivalent à dayOfWeekInMonth(1, dow)) |
lastInMonth(DayOfWeek dow) | dernier dow du mois |
Les deux moitiés répondent à « quel est le premier/dernier X » (moitié supérieure) et « quel est le prochain/précédent X » (moitié inférieure). Enchaînez-les lorsque la question est plus complexe :
LocalDate firstMondayNextMonth = today.with(firstDayOfNextMonth()).with(nextOrSame(DayOfWeek.MONDAY));C'est-à-dire « le premier lundi à partir du premier du mois prochain ». Lisez de gauche à droite ; chaque with est une étape.
Appliquer avec 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 existe sur tout Temporal : LocalDate, LocalDateTime, ZonedDateTime, même OffsetDateTime. Le modificateur ne touche que les composants de date — appliquer lastDayOfMonth() à un LocalDateTime laisse l'heure inchangée.
Les modificateurs sont totaux : ils retournent toujours un résultat. Il n'y a pas de chemin d'exception « et si aujourd'hui n'est pas dans le bon mois » — lastDayOfMonth() depuis le 31 janvier est toujours le 31 janvier (le dernier jour est lui-même).
Modificateurs lambda
Comme TemporalAdjuster est une @FunctionalInterface, vous pouvez écrire le vôtre avec une lambda :
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-10Le schéma : convertir le Temporal entrant dans le type de date souhaité (LocalDate.from(t)), calculer la nouvelle date, la retourner. Le type de retour est Temporal (l'interface), mais le JDK accepte le retour concret.
Pour un usage ponctuel, c'est excessif — incorporez simplement la logique à côté du point d'appel. Pour un calcul que vous utiliserez à plusieurs endroits (prochain jour ouvrable, dernier jour du trimestre, jour férié observé), le regrouper comme modificateur garde les points d'appel lisibles.
ofDateAdjuster pour le cas lambda simple
Si votre modificateur ne fonctionne que sur LocalDate (le cas courant), TemporalAdjusters.ofDateAdjuster(UnaryOperator<LocalDate>) est la fabrique plus propre :
TemporalAdjuster nextWorkingDay = TemporalAdjusters.ofDateAdjuster(d -> {
LocalDate result = d.plusDays(1);
while (result.getDayOfWeek().getValue() > 5) {
result = result.plusDays(1);
}
return result;
});La lambda prend et retourne un LocalDate. La fabrique l'encapsule en tant que TemporalAdjuster. C'est ce que vous utilisez dans 90% des cas lors de l'écriture de modificateurs personnalisés.
Un exemple complet : dates ouvrées, jours fériés, fin de période
Le programme ci-dessous utilise des modificateurs pour le calendrier comptable : fin de mois pour la facturation, dernier jour ouvrable du mois pour la clôture des caisses, premier lundi du mois prochain pour une réunion de planification régulière, un modificateur « prochain jour ouvrable » tenant compte des jours fériés, et le calcul de fin de trimestre.
Ce qu'il faut retenir de l'exécution :
- Les modificateurs intégrés ont couvert les cas limites (
firstDayOfMonth,lastDayOfMonth,firstDayOfYear, etc.) sans aucune arithmétique de votre côté. Pour « cette date alignée sur le début/fin de son mois ou de son année », ce sont les bons outils — plus clairs que le calcul manuelwithDayOfMonth(1), et immunisés contre les surprises de longueur de mois. next(MONDAY)etprevious(FRIDAY)ont retourné des dates strictement différentes ;nextOrSame(TUESDAY)sur un mardi a retourné aujourd'hui. Mémorisez la distinction strict-vs-ou-même ; c'est la source de la plupart des bugs « décalé d'une semaine » quand la date de départ tombe sur le jour de la semaine cible.- L'enchaînement
firstDayOfNextMonth().nextOrSame(MONDAY)a exprimé « le premier lundi à partir du premier du mois prochain » en deux lectures. La chaîne tient sur une ligne ; l'équivalent manuel en prend six. Enchaîner les modificateurs est la façon idiomatique de les composer. NEXT_BUSINESS_DAYa sauté le week-end et un jour férié en une seule opération. L'ensembleHOLIDAYSétait un exemple synthétique à deux éléments ; une implémentation réelle chargerait depuis un service. La forme du modificateur est la même — enveloppez la boucle dansTemporalAdjusters.ofDateAdjuster(...)et vous pouvez l'utiliser dans les appelstoday.with(NEXT_BUSINESS_DAY)partout.END_OF_QUARTERétait un modificateur personnalisé en une ligne qui sélectionnait le troisième mois du trimestre de la date et appliquaitlastDayOfMonth(). Le point : les opérations de domaine complexes appartiennent à des constantesTemporalAdjusternommées, où le point d'appel se litsomeDate.with(END_OF_QUARTER)au lieu d'incorporer un bloc arithmétique de six lignes. Gardez la logique du calendrier en un seul endroit.
Et maintenant
TemporalAdjusters clôturent l'API java.time moderne. Les deux derniers chapitres de cette partie couvrent les types hérités que vous rencontrerez dans du code plus ancien : Java Legacy Date Class pour le java.util.Date original, et Java Calendar Class pour java.util.Calendar. Les deux sont toujours présents pour la compatibilité ; les deux ont un chemin de conversion propre vers java.time.