Cuando trabajas con objetos de java.time como LocalDate, muchas veces necesitas hacer ajustes de fechas más complejos, como encontrar «el segundo martes del mes».
En lugar de calcularlo a mano, puedes usar TemporalAdjusters.
La clase de utilidades java.time.temporal.TemporalAdjusters (fíjate en la «s» del plural) ofrece métodos estáticos para muchos ajustes de fechas habituales.
Estos ajustes los aplicas con el método .with() sobre un objeto temporal (como LocalDate).
Usar with(TemporalAdjuster) devuelve una instancia nueva y no actualiza la instancia existente, ya que los objetos de java.time son inmutables.
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
Estos son algunos ajustadores clave que resultan útiles para calcular programaciones:
Usa dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek) para encontrar la enésima aparición de un día de la semana.
// Find the second Tuesday of August 2024
LocalDate secondTuesday = date.with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.TUESDAY));
// => 2024-08-13
Usa firstInMonth(DayOfWeek dayOfWeek) o lastInMonth(DayOfWeek dayOfWeek).
// Find the last Monday of August 2024
LocalDate lastMonday = date.with(TemporalAdjusters.lastInMonth(DayOfWeek.MONDAY));
// => 2024-08-26
Ajustadores como next(), previous(), nextOrSame() y previousOrSame() encuentran la aparición siguiente o anterior de un día de la semana dado, tomando como referencia la fecha actual.
next()/previous(): excluyen la fecha actual si coincide.nextOrSame()/previousOrSame(): incluyen la fecha actual si coincide.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 es una interfaz funcional, así que puedes escribir tu propio ajustador como una lambda que recibe el objeto temporal que hay que ajustar y devuelve el ya ajustado.
Se aplica con .with() igual que los ajustadores predefinidos, y puede usarlos internamente.
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
Usar estos ajustadores de la clase TemporalAdjusters puede simplificar muchísimo las tareas de manipulación de fechas.
El salón de belleza de Nueva York en el que ayudaste en el ejercicio Booking Up For Beauty ha ido muy bien, y muchas de sus clientas se han vuelto habituales. En lugar de reservar cada visita por separado, una clienta habitual tiene un arreglo fijo: «cada segundo martes», «el último viernes del mes», y así sucesivamente. La dueña te ha pedido un ayudante que calcule las fechas que hay detrás de estos arreglos, además de las fechas de la rutina mensual del propio salón.
Implementa el método SalonCalendar.standingAppointment() que toma un año, un mes, una ocurrencia (1 para la primera, 2 para la segunda, y así sucesivamente) y un día de la semana.
Devuelve la fecha de esa ocurrencia del día de la semana dentro del mes:
SalonCalendar calendar = new SalonCalendar();
calendar.standingAppointment(2024, Month.AUGUST, 2, DayOfWeek.TUESDAY);
// => LocalDate.of(2024, 8, 13)
Algunas clientas habituales prefieren la última ocurrencia de su día en el mes.
Implementa el método SalonCalendar.lastAppointment() que toma un año, un mes y un día de la semana, y devuelve la fecha de la última ocurrencia de ese día de la semana dentro del mes:
SalonCalendar calendar = new SalonCalendar();
calendar.lastAppointment(2024, Month.AUGUST, DayOfWeek.FRIDAY);
// => LocalDate.of(2024, 8, 30)
Un cliente en el mostrador pide «el próximo sábado».
Si el día en que lo pide ya es sábado, se refiere a ese mismo día.
Implementa el método SalonCalendar.nextAvailable() que toma una fecha y un día de la semana, y devuelve la primera fecha igual o posterior a la fecha dada que caiga en ese día de la semana:
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)
El salón hace el cierre de cuentas el último día de cada mes.
Sin embargo, el salón está cerrado los fines de semana, así que cuando el último día del mes cae en sábado o en domingo, las cuentas se cierran el viernes anterior.
Implementa el método SalonCalendar.bookkeepingDay() que toma un año y un mes, y devuelve el día de cierre contable de ese mes:
SalonCalendar calendar = new SalonCalendar();
calendar.bookkeepingDay(2024, Month.SEPTEMBER);
// => LocalDate.of(2024, 9, 30)
calendar.bookkeepingDay(2024, Month.AUGUST);
// => LocalDate.of(2024, 8, 30)
El personal cobra el día 15 de cada mes, con la misma regla del fin de semana: cuando el 15 cae en sábado o en domingo, el día de pago es el viernes anterior. En lugar de escribir la regla por segunda vez, conviértela en un ajustador reutilizable.
Implementa el método SalonCalendar.precedingWorkday() que devuelve un TemporalAdjuster que deja sin cambios un día entre semana y mueve un sábado o un domingo al viernes anterior.
Después implementa el método SalonCalendar.payday(), que toma un año y un mes, usando ese ajustador:
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)
Si quieres, vuelve atrás y usa también el ajustador en bookkeepingDay().
Regístrate en Exercism para aprender y dominar Java con 27 conceptos159 ejercicios y mentoría humana real, todo gratis.