Quando trabalhas com objetos java.time como LocalDate, muitas vezes precisas de fazer ajustes de datas complexos, como encontrar a "segunda terça-feira do mês".
Em vez de calculares isto manualmente, podes usar TemporalAdjusters.
A classe utilitária java.time.temporal.TemporalAdjusters (atenção ao "s" do plural) disponibiliza métodos estáticos para muitos ajustes de datas comuns.
Aplicas estes ajustes com o método .with() num objeto temporal (como LocalDate).
Usar with(TemporalAdjuster) devolve uma nova instância e não atualiza a instância existente, porque os objetos java.time são imutáveis.
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
Eis alguns ajustadores essenciais para cálculos de agendamento:
Usa dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek) para encontrar a enésima ocorrência de um dia da semana.
// Find the second Tuesday of August 2024
LocalDate secondTuesday = date.with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.TUESDAY));
// => 2024-08-13
Usa 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
Ajustadores como next(), previous(), nextOrSame() e previousOrSame() encontram a ocorrência seguinte ou anterior de um determinado dia da semana, em relação à data atual.
next()/previous(): excluem a data atual se coincidir.nextOrSame()/previousOrSame(): incluem a data atual se coincidir.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 é uma interface funcional, por isso podes escrever um ajustador teu como uma lambda que recebe o objeto temporal a ajustar e devolve o objeto já ajustado.
Aplica-se com .with() tal como os ajustadores predefinidos, e pode usá-los 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 estes ajustadores da classe TemporalAdjusters pode simplificar bastante as tarefas de manipulação de datas.
O salão de beleza de Nova Iorque que ajudaste no exercício Marcações de Beleza está a correr bem, e muitos dos seus clientes já são habituais. Em vez de marcar cada visita em separado, um cliente habitual tem um acordo fixo: «todas as segundas terças-feiras», «a última sexta-feira do mês», e assim por diante. A dona do salão pediu-te um auxiliar que descubra as datas por trás desses acordos, bem como as datas da rotina mensal do próprio salão.
Implementa o método SalonCalendar.standingAppointment() que recebe um ano, um mês, uma ocorrência (1 para a primeira, 2 para a segunda, e assim por diante) e um dia da semana.
Devolve a data dessa ocorrência do dia da semana dentro do mês:
SalonCalendar calendar = new SalonCalendar();
calendar.standingAppointment(2024, Month.AUGUST, 2, DayOfWeek.TUESDAY);
// => LocalDate.of(2024, 8, 13)
Alguns clientes habituais preferem a última ocorrência do seu dia no mês.
Implementa o método SalonCalendar.lastAppointment() que recebe um ano, um mês e um dia da semana, e devolve a data da última ocorrência desse dia da semana dentro do mês:
SalonCalendar calendar = new SalonCalendar();
calendar.lastAppointment(2024, Month.AUGUST, DayOfWeek.FRIDAY);
// => LocalDate.of(2024, 8, 30)
Um cliente no balcão pede «o próximo sábado».
Se o dia em que faz o pedido já for um sábado, quer dizer esse mesmo dia.
Implementa o método SalonCalendar.nextAvailable() que recebe uma data e um dia da semana, e devolve a primeira data igual ou posterior à data indicada que calhe nesse dia da 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)
O salão fecha as contas no último dia de cada mês.
No entanto, ao fim de semana o salão está fechado, por isso, quando o último dia do mês calha a um sábado ou a um domingo, as contas fecham-se na sexta-feira anterior.
Implementa o método SalonCalendar.bookkeepingDay() que recebe um ano e um mês, e devolve o dia de fecho de contas desse mês:
SalonCalendar calendar = new SalonCalendar();
calendar.bookkeepingDay(2024, Month.SEPTEMBER);
// => LocalDate.of(2024, 9, 30)
calendar.bookkeepingDay(2024, Month.AUGUST);
// => LocalDate.of(2024, 8, 30)
Os funcionários recebem a 15 de cada mês, com a mesma regra de fim de semana: quando o dia 15 calha a um sábado ou a um domingo, o pagamento é na sexta-feira anterior. Em vez de escreveres a regra uma segunda vez, transforma-a num ajustador reutilizável.
Implementa o método SalonCalendar.precedingWorkday() que devolve um TemporalAdjuster que deixa um dia útil inalterado e move um sábado ou um domingo para a sexta-feira anterior.
Depois implementa o método SalonCalendar.payday(), que recebe um ano e um mês, usando esse 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)
Fica à vontade para voltar atrás e usar também o ajustador no bookkeepingDay().
Inscreve-te no Exercism para aprenderes e dominares Java com 27 conceitos159 exercícios, e mentoria humana real, tudo grátis.