Cuando trabajas con objetos java.time como LocalDate, a menudo necesitas hacer ajustes de fecha 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) proporciona métodos estáticos para muchos ajustes de fecha habituales.
Estos ajustes se aplican con el método .with() sobre un objeto temporal (como LocalDate).
Usar with(TemporalAdjuster) devuelve una nueva instancia y no actualiza la instancia existente, ya que los objetos 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
Aquí tienes algunos ajustadores clave que resultan útiles para los cálculos de planificación:
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
Los ajustadores como next(), previous(), nextOrSame() y previousOrSame() encuentran la aparición siguiente o anterior de un día de la semana concreto, en relación con 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 objeto 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 enormemente 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 va viento en popa, y muchos de sus clientes se han vuelto habituales. En lugar de reservar cada visita por separado, un cliente habitual tiene una cita fija: «cada segundo martes», «el último viernes del mes», etcétera. La dueña te ha pedido un ayudante que calcule las fechas que hay detrás de esas citas, además de las fechas de la rutina mensual del propio salón.
Implementa el método SalonCalendar.standingAppointment(), que recibe un año, un mes, una ocurrencia (1 para la primera, 2 para la segunda, etcétera) 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)
Algunos clientes habituales prefieren la última ocurrencia de su día dentro del mes.
Implementa el método SalonCalendar.lastAppointment(), que recibe 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 de recepción 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 recibe una fecha y un día de la semana, y devuelve la primera fecha, a partir de 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 cierra sus 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 recibe un año y un mes, y devuelve el día de cierre de cuentas 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 día 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 laborable y mueve un sábado o un domingo al viernes anterior.
Después implementa el método SalonCalendar.payday(), que recibe 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)
No dudes en volver atrás y usar el ajustador también en bookkeepingDay().
Regístrate en Exercism para aprender y dominar Java con 27 conceptos159 ejercicios y mentoría humana real, todo gratis.