عند العمل مع كائنات java.time مثل LocalDate، غالبًا ما تحتاج إلى إجراء تعديلات معقّدة على التواريخ، مثل إيجاد «الثلاثاء الثاني من الشهر». وبدلًا من حساب ذلك يدويًا، يمكنك استخدام TemporalAdjusters.
يوفّر صنف الأدوات المساعدة java.time.temporal.TemporalAdjusters (لاحظ حرف s في آخره) دوال static لكثير من تعديلات التواريخ الشائعة. وتُطبّق هذه التعديلات باستخدام الطريقة .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)
يتقاضى الموظفون رواتبهم في الخامس عشر من كل شهر، وبنفس قاعدة عطلة نهاية الأسبوع: فإذا وافق الخامس عشر يوم سبت أو أحد، كان يوم الرواتب هو الجمعة الذي قبله. بدلًا من كتابة القاعدة مرة ثانية، حوّلها إلى مُعدِّل قابل لإعادة الاستخدام.
نفّذ الطريقة 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 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.