Java TemporalAdjusters
Calcula fechas relativas en Java con TemporalAdjusters: firstDayOfMonth, next, previous y ajustadores personalizados.
plusDays(7) añade siete días. withDayOfMonth(15) salta al día 15. Esos dos cubren los casos simples. Un TemporalAdjuster es un objeto con forma de función que se pasa a Temporal.with(adjuster) para manejar los casos en que la nueva fecha depende de la actual de forma más compleja: "el primer lunes del mes siguiente", "el último día de este trimestre", "el Día de Acción de Gracias del año próximo en Estados Unidos".
La interfaz tiene un solo método:
@FunctionalInterface
interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}Normalmente no la implementas tú. La clase java.time.temporal.TemporalAdjusters (nota la s — Adjusters, plural) es un conjunto de aproximadamente una docena de ajustadores integrados que cubren los casos comunes, y LocalDate.with(adjuster) es la forma de aplicarlos.
El catálogo integrado
Importa de forma estática; el código se lee mejor así:
import static java.time.temporal.TemporalAdjusters.*;Luego:
| Ajustador | Efecto |
|---|---|
firstDayOfMonth() | día → primer día del mismo mes |
lastDayOfMonth() | día → último día del mismo mes |
firstDayOfNextMonth() | día → primer día del mes siguiente |
firstDayOfYear() | día → 1 de enero del mismo año |
lastDayOfYear() | día → 31 de diciembre del mismo año |
firstDayOfNextYear() | día → 1 de enero del año siguiente |
next(DayOfWeek dow) | el próximo dow estrictamente después de la fecha |
nextOrSame(DayOfWeek dow) | hoy si es dow, de lo contrario el próximo dow |
previous(DayOfWeek dow) | el dow más reciente estrictamente antes de la fecha |
previousOrSame(DayOfWeek dow) | hoy si es dow, de lo contrario el dow anterior |
dayOfWeekInMonth(int ord, DayOfWeek dow) | el n-ésimo día de la semana del mes: dayOfWeekInMonth(3, MONDAY) → 3er lunes |
firstInMonth(DayOfWeek dow) | primer dow del mes (equivalente a dayOfWeekInMonth(1, dow)) |
lastInMonth(DayOfWeek dow) | último dow del mes |
Las dos mitades responden a "¿cuál es el primero/último X?" (mitad superior) y "¿cuál es el siguiente/anterior X?" (mitad inferior). Encadénalos cuando la pregunta sea más compleja:
LocalDate firstMondayNextMonth = today.with(firstDayOfNextMonth()).with(nextOrSame(DayOfWeek.MONDAY));Eso es "el primer lunes en o después del primero del mes siguiente". Léelo de izquierda a derecha; cada with es un paso.
Aplicar con 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 en cada Temporal: LocalDate, LocalDateTime, ZonedDateTime, incluso OffsetDateTime. El ajustador solo toca los componentes de fecha — aplicar lastDayOfMonth() a un LocalDateTime deja la hora sin cambios.
Los ajustadores son totales: siempre devuelven un resultado. No existe un camino de excepción "¿qué pasa si hoy no está en el mes correcto?" — lastDayOfMonth() desde el 31 de enero sigue siendo el 31 de enero (el último día es él mismo).
Ajustadores lambda
Como TemporalAdjuster es una @FunctionalInterface, puedes escribir el tuyo propio con una 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-10El patrón: convierte el Temporal entrante al tipo de fecha que quieras (LocalDate.from(t)), calcula la nueva fecha, devuélvela. El tipo de retorno es Temporal (la interfaz), pero el JDK acepta el retorno concreto.
Para un uso puntual esto es excesivo — simplemente inserta la lógica junto al punto de llamada. Para un cálculo que usarás en múltiples lugares (día hábil siguiente, último día del trimestre, festivo observado), empaquetarlo como ajustador mantiene los puntos de llamada legibles.
ofDateAdjuster para el caso simple de lambda
Si tu ajustador trabaja solo con LocalDate (el caso común), TemporalAdjusters.ofDateAdjuster(UnaryOperator<LocalDate>) es la fábrica más limpia:
TemporalAdjuster nextWorkingDay = TemporalAdjusters.ofDateAdjuster(d -> {
LocalDate result = d.plusDays(1);
while (result.getDayOfWeek().getValue() > 5) {
result = result.plusDays(1);
}
return result;
});La lambda toma y devuelve un LocalDate. La fábrica lo envuelve como TemporalAdjuster. Esto es lo que usarás el 90% del tiempo al escribir ajustadores personalizados.
Un ejemplo completo: fechas hábiles, festivos y fin de período
El programa siguiente usa ajustadores para el calendario contable: fin de mes para facturación, último día hábil del mes para el cierre de caja, el primer lunes del mes siguiente para una reunión de planificación periódica, un ajustador "siguiente día hábil" con festivos, y cálculo del fin de trimestre.
Lo que se puede extraer de la ejecución:
- Los ajustadores integrados cubren los casos límite (
firstDayOfMonth,lastDayOfMonth,firstDayOfYear, etc.) sin ninguna aritmética de tu parte. Para "esta fecha ajustada al inicio/fin de su mes o año", son la herramienta correcta — más claros que el cálculo manual conwithDayOfMonth(1), e inmunes a las sorpresas por la longitud del mes. next(MONDAY)yprevious(FRIDAY)devolvieron fechas estrictamente distintas;nextOrSame(TUESDAY)en un martes devolvió hoy. Memoriza la distinción estricto-versus-o-igual; es la fuente de la mayoría de los bugs de "desfase de una semana" cuando la fecha de inicio cae en el día de la semana objetivo.- El encadenado
firstDayOfNextMonth().nextOrSame(MONDAY)expresó "el primer lunes en o después del primero del mes siguiente" en dos lecturas. La cadena es una línea; el equivalente manual son seis. Encadenar ajustadores es la forma idiomática de componerlos. NEXT_BUSINESS_DAYsaltó el fin de semana y un festivo de una vez. El conjuntoHOLIDAYSfue un ejemplo sintético de dos elementos; una implementación real lo cargaría desde un servicio. La forma del ajustador es la misma — envuelve el bucle enTemporalAdjusters.ofDateAdjuster(...)y puedes usarlo en llamadastoday.with(NEXT_BUSINESS_DAY)en todas partes.END_OF_QUARTERfue un ajustador personalizado de una línea que seleccionó el tercer mes del trimestre de la fecha y aplicólastDayOfMonth(). El punto: las operaciones de dominio complejas pertenecen a constantesTemporalAdjustercon nombre, donde el punto de llamada se leesomeDate.with(END_OF_QUARTER)en lugar de insertar un bloque aritmético de seis líneas. Mantén la lógica de calendario en un solo lugar.
Qué sigue
TemporalAdjusters cierra la API moderna java.time. Los dos últimos capítulos de esta parte cubren los tipos heredados que encontrarás en código antiguo: Java Legacy Date Class para el java.util.Date original, y Java Calendar Class para java.util.Calendar. Ambos siguen presentes por compatibilidad; ambos tienen una ruta de conversión limpia a java.time.