Коли ми працюємо з обʼєктами java.time на кшталт LocalDate, часто виникає потреба у складних коригуваннях дати, як-от пошук «другого вівторка місяця».
Замість того щоб обчислювати це вручну, можна скористатися TemporalAdjusters.
Утилітний клас java.time.temporal.TemporalAdjusters (звернімо увагу на «s» в кінці) надає статичні методи для багатьох типових коригувань дати.
Ці коригування застосовують через метод .with() на часовому обʼєкті (наприклад, LocalDate).
Використання with(TemporalAdjuster) повертає новий екземпляр і не оновлює наявний, оскільки обʼєкти java.time є незмінними.
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
Ось кілька ключових коригувальників, корисних для обчислень у розкладі:
Скористаймося dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek), щоб знайти n-те входження дня тижня.
// Find the second Tuesday of August 2024
LocalDate secondTuesday = date.with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.TUESDAY));
// => 2024-08-13
Скористаймося firstInMonth(DayOfWeek dayOfWeek) або lastInMonth(DayOfWeek dayOfWeek).
// Find the last Monday of August 2024
LocalDate lastMonday = date.with(TemporalAdjusters.lastInMonth(DayOfWeek.MONDAY));
// => 2024-08-26
Коригувальники на кшталт next(), previous(), nextOrSame() і previousOrSame() знаходять наступне або попереднє входження заданого дня тижня відносно поточної дати.
next()/previous(): не враховують поточну дату, навіть якщо вона збігається.nextOrSame()/previousOrSame(): враховують поточну дату, якщо вона збігається.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 - це функціональний інтерфейс, тому власний коригувальник можна написати як лямбду, що приймає часове значення для коригування і повертає скориговане.
Його застосовують через .with() так само, як і визначені наперед коригувальники, і всередині він може використовувати їх.
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
Використання цих коригувальників із класу TemporalAdjusters може значно спростити роботу з датами.
Салон краси в Нью-Йорку, з яким ми допомагали у вправі «Запис на красу», процвітає, і багато його клієнтів стали постійними. Замість того щоб записуватися на кожен візит окремо, постійний клієнт має сталу домовленість: «кожен другий вівторок», «остання пʼятниця місяця» тощо. Власниця попросила нас зробити помічник, який обчислює дати, що стоять за цими домовленостями, а також дати щомісячної рутини самого салону.
Реалізуйте метод SalonCalendar.standingAppointment(), який приймає рік, місяць, номер повторення (1 для першого, 2 для другого тощо) і день тижня.
Він повертає дату відповідного повторення цього дня тижня в межах місяця:
SalonCalendar calendar = new SalonCalendar();
calendar.standingAppointment(2024, Month.AUGUST, 2, DayOfWeek.TUESDAY);
// => LocalDate.of(2024, 8, 13)
Деяким постійним клієнтам до вподоби останній такий день тижня в місяці.
Реалізуйте метод SalonCalendar.lastAppointment(), який приймає рік, місяць і день тижня та повертає дату останнього повторення цього дня тижня в межах місяця:
SalonCalendar calendar = new SalonCalendar();
calendar.lastAppointment(2024, Month.AUGUST, DayOfWeek.FRIDAY);
// => LocalDate.of(2024, 8, 30)
Клієнт на рецепції просить «найближчу суботу».
Якщо день, у який він звертається, уже субота, то йдеться саме про цей день.
Реалізуйте метод SalonCalendar.nextAvailable(), який приймає дату й день тижня та повертає першу дату, що не раніше за задану і припадає на цей день тижня:
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)
Салон закриває облік в останній день кожного місяця.
Проте на вихідних салон не працює, тож якщо останній день місяця припадає на суботу чи неділю, облік закривають у пʼятницю перед цим.
Реалізуйте метод SalonCalendar.bookkeepingDay(), який приймає рік і місяць та повертає день закриття обліку в цьому місяці:
SalonCalendar calendar = new SalonCalendar();
calendar.bookkeepingDay(2024, Month.SEPTEMBER);
// => LocalDate.of(2024, 9, 30)
calendar.bookkeepingDay(2024, Month.AUGUST);
// => LocalDate.of(2024, 8, 30)
Працівники отримують зарплату 15-го числа кожного місяця, і діє те саме правило вихідних: коли 15-те припадає на суботу чи неділю, зарплату виплачують у пʼятницю перед цим. Замість того щоб писати це правило вдруге, зробімо з нього повторно використовуваний коригувальник.
Реалізуйте метод SalonCalendar.precedingWorkday(), який повертає TemporalAdjuster: він залишає будній день без змін, а суботу чи неділю переносить на попередню пʼятницю.
Потім реалізуйте метод SalonCalendar.payday(), який приймає рік і місяць, використовуючи цей коригувальник:
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)
Цілком можна повернутися й використати коригувальник так само і в bookkeepingDay().
Зареєструйтеся на Exercism, щоб вивчати й опановувати Java, а також 27 концепцій159 вправ та справжнє наставництво від людей, і все це безкоштовно.