學習軌道
/
Java
Java
/
練習
/
美容院常客
美容院常客

美容院常客

學習練習

簡介

時間調整器

處理LocalDate這類java.time物件時,你常常需要做複雜的日期調整,例如找出「該月的第二個星期二」。 與其自己手動計算,你可以使用**TemporalAdjusters**。

java.time.temporal.TemporalAdjusters(注意字尾有複數的 s)這個工具類別提供了許多常見日期調整的靜態方法。 這些調整是透過時間物件(例如LocalDate)上的.with()方法來套用。

Note

使用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)找出某個星期幾在該月的第幾次出現。

// 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是函式介面,所以你可以用 lambda 寫一個自己的調整器:它接收要調整的時間物件,並回傳調整後的結果。 套用方式和預先定義的調整器一樣,都是透過.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」練習中幫忙的紐約美容院經營得有聲有色,許多客戶也成了常客。 常客不再每次分別預約,而是有固定的安排:「每隔一個星期二」、「每月的最後一個星期五」等等。 老闆請你寫一個小工具,算出這些安排背後的日期,以及美容院每月例行公事的日期。

1. 找出常客的固定預約

實作 SalonCalendar.standingAppointment() 方法,它接受年、月、出現次數(1 代表第一次,2 代表第二次,依此類推)以及星期幾。 它會回傳該月內這個星期幾第幾次出現的日期:

SalonCalendar calendar = new SalonCalendar();
calendar.standingAppointment(2024, Month.AUGUST, 2, DayOfWeek.TUESDAY);
// => LocalDate.of(2024, 8, 13)

2. 找出常客每月最後一次的預約

有些常客偏好當月最後一次出現的那一天。 實作 SalonCalendar.lastAppointment() 方法,它接受年、月與星期幾,並回傳該月內這個星期幾最後一次出現的日期:

SalonCalendar calendar = new SalonCalendar();
calendar.lastAppointment(2024, Month.AUGUST, DayOfWeek.FRIDAY);
// => LocalDate.of(2024, 8, 30)

3. 找出臨時客人的下一個可預約日

櫃檯的客人問:「下一個星期六是什麼時候?」 如果他們發問當天就是星期六,指的就是當天。 實作 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)

4. 找出結帳日

美容院在每個月的最後一天結帳。 不過週末不營業,所以當月最後一天落在星期六或星期日時,會提前在前一個星期五結帳。 實作 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)

5. 把週末規則重複用在發薪日

員工在每月 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() 裡喔。

透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Java Exercism

準備好開始 美容院常客 了嗎?

註冊 Exercism,透過 27 個概念159 個練習 和真人引導來學習並精通 Java,全部免費。