وقتی با اشیایی از نوع 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 میتواند کارهای دستکاری تاریخ را بسیار سادهتر کند.
سالن زیباییای در نیویورک که در تمرین «Booking Up For Beauty» به آن کمک کردید، کارش گرفته و بسیاری از مشتریانش به مشتری ثابت تبدیل شدهاند. مشتری ثابت به جای اینکه هر بار جداگانه نوبت بگیرد، یک قرار همیشگی دارد: «یک سهشنبه در میان»، «آخرین جمعهی ماه» و از این قبیل. صاحب سالن از شما ابزاری کمکی خواسته که تاریخهای پشت این قرارها و همچنین تاریخهای برنامهی ماهانهی خود سالن را حساب کند.
متد 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)
حقوق کارکنان پانزدهم هر ماه پرداخت میشود، با همان قاعدهی آخر هفته: وقتی پانزدهم به شنبه یا یکشنبه بیفتد، روز پرداخت جمعهی پیش از آن است. به جای اینکه این قاعده را بار دوم بنویسید، آن را به یک تنظیمکنندهی قابل استفادهی مجدد تبدیل کنید.
متد 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() هم استفاده کنید.