1. 日期與時間的加減運算
在程式開發中,處理日期與時間—不僅僅是「把日期漂亮地顯示在畫面上」。我們經常需要:
- 了解距離重要事件還有多少天;
- 計算使用者的年齡;
- 判斷兩個事件之間過了多少時間;
- 檢查截止日是否已到;
- 在日期上加上特定的天、週、月或小時。
例如,在我們的應用程式中,我們想提醒使用者距離任務的截止日還有 3 天;或在生日已經過去時向他致意(如果日期已在過去)。
為此在 java.time 中有專門的類別與方法,讓這些計算既簡單又安全。
加減運算的基本方法
所有日期與時間類別(LocalDate、LocalTime、LocalDateTime、ZonedDateTime、Instant)都有以下方法:
- plusXxx() — 加上(天、月、年、時、分等)
- minusXxx() — 減去(同上)
import java.time.LocalDate;
LocalDate today = LocalDate.now(); // 今天的日期
LocalDate tomorrow = today.plusDays(1); // 明天
LocalDate nextMonth = today.plusMonths(1); // 一個月後
LocalDate lastWeek = today.minusWeeks(1); // 一週前
System.out.println("今天:" + today);
System.out.println("明天:" + tomorrow);
System.out.println("一個月後:" + nextMonth);
System.out.println("一週前:" + lastWeek);
這些方法都會回傳新的物件,而不會修改原本的物件。不可變性—就是一切!
import java.time.LocalTime;
LocalTime now = LocalTime.now();
LocalTime inTwoHours = now.plusHours(2);
LocalTime tenMinutesAgo = now.minusMinutes(10);
System.out.println("現在:" + now);
System.out.println("2 小時後:" + inTwoHours);
System.out.println("10 分鐘前:" + tenMinutesAgo);
組合式方法
LocalDate vacation = today.plusMonths(2).plusDays(10);
System.out.println("休假:" + vacation);
2. 取得日期差:Period 與 Duration
當需要知道兩個日期或時間點之間經過了多久時,兩位主角登場—Period 與 Duration。一起更深入地認識它們吧。
Period 與 Duration 有何不同?
- Period — 用於處理日期(年、月、日)。例如:「3 年 2 個月 5 天」。
- Duration — 用於處理時間(小時、分鐘、秒、奈秒)。例如:「5 小時 30 分鐘」。
| 類別 | 用途 | 使用範例 |
|---|---|---|
|
日期之間的差異 | 日期之間相隔多少年/月/日 |
|
時間點之間的差異 | 事件之間相隔多少小時/分鐘/秒 |
範例:計算使用者的年齡
import java.time.LocalDate;
import java.time.Period;
LocalDate birthday = LocalDate.of(2000, 1, 15); // 生日
LocalDate today = LocalDate.now();
Period age = Period.between(birthday, today);
System.out.println("年齡:" + age.getYears() + " 年," +
age.getMonths() + " 個月," +
age.getDays() + " 天");
類比來說:Period 比較像「人類語言」的日期差(「我 24 年 5 個月 12 天大」)。
範例:距離截止日還有多久
import java.time.LocalDate;
LocalDate deadline = LocalDate.of(2025, 7, 1);
LocalDate today = LocalDate.now();
if (today.isBefore(deadline)) {
Period left = Period.between(today, deadline);
System.out.println("距離截止日還有:" +
left.getMonths() + " 個月和 " +
left.getDays() + " 天");
} else {
System.out.println("截止日已過!");
}
Duration:時間上的差異
import java.time.LocalDateTime;
import java.time.Duration;
LocalDateTime start = LocalDateTime.of(2025, 6, 1, 10, 0, 0);
LocalDateTime end = LocalDateTime.of(2025, 6, 1, 15, 30, 0);
Duration duration = Duration.between(start, end);
System.out.println("持續時間:" + duration.toHours() + " 小時 " +
(duration.toMinutes() % 60) + " 分鐘");
Duration 以秒、分鐘、小時、天等單位來衡量差異(其中「天」固定為 24 小時,無視曆法上的特殊情況)。
比較:什麼時候用 Period,什麼時候用 Duration?
- 如果只有日期(沒有時間)—使用 Period。
- 如果包含時間(例如 LocalDateTime、Instant)—使用 Duration。
3. 日期與時間的比較: isBefore, isAfter, equals
有時只需要判斷:日期已經到了嗎?事件在過去還是未來?為此,所有日期與時間類別都有以下方法:
- isBefore(otherDate)
- isAfter(otherDate)
- isEqual(otherDate)(或直接 equals)
import java.time.LocalDate;
LocalDate today = LocalDate.now();
LocalDate deadline = LocalDate.of(2025, 7, 1);
if (today.isBefore(deadline)) {
System.out.println("還有時間!");
} else if (today.isEqual(deadline)) {
System.out.println("今天是截止日!");
} else {
System.out.println("截止日已過 :(");
}
import java.time.LocalTime;
LocalTime now = LocalTime.now();
LocalTime lunch = LocalTime.of(13, 0);
if (now.isAfter(lunch)) {
System.out.println("已經吃過午餐!");
} else {
System.out.println("離午餐還有一段時間。");
}
重要:只能比較相同型別的物件(LocalDate 對 LocalDate、LocalDateTime 對 LocalDateTime 等)。
4. 實作練習:計算差值與檢查期限
範例:距離任務截止日還有多少天
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
LocalDate today = LocalDate.now();
LocalDate deadline = LocalDate.of(2025, 7, 1);
long daysLeft = ChronoUnit.DAYS.between(today, deadline);
if (daysLeft > 0) {
System.out.println("距離截止日還有 " + daysLeft + " 天");
} else if (daysLeft == 0) {
System.out.println("今天是截止日!");
} else {
System.out.println("截止日已過 " + Math.abs(daysLeft) + " 天前");
}
ChronoUnit.DAYS.between() — 一種方便的方式,用來取得兩個日期之間精確的天數(不考慮月份與年份)。
範例:兩個事件之間相隔多少秒
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
LocalDateTime start = LocalDateTime.of(2025, 6, 1, 10, 0, 0);
LocalDateTime end = LocalDateTime.of(2025, 6, 1, 15, 30, 0);
long seconds = ChronoUnit.SECONDS.between(start, end);
System.out.println("事件之間經過了 " + seconds + " 秒");
範例:計算任務的年齡
import java.time.LocalDate;
import java.time.Period;
LocalDate created = LocalDate.of(2025, 5, 20);
LocalDate today = LocalDate.now();
Period period = Period.between(created, today);
System.out.println("任務建立於 " +
period.getDays() + " 天、" +
period.getMonths() + " 個月," +
period.getYears() + " 年前。");
範例:兩個 Instant 之間的持續時間
import java.time.Instant;
import java.time.Duration;
Instant start = Instant.now();
// ... 執行一些操作 ...
Instant end = Instant.now();
Duration duration = Duration.between(start, end);
System.out.println("執行耗時 " + duration.toMillis() + " 毫秒");
5. 使用 Period 與 Duration 的特點
Period 的特點
- Period 依照曆法計算差值:例如在 2 月 28 日與 3 月 1 日之間—為 1 天,即使是不同月份。
- 可以分別取得年、月、日的數量:getYears()、getMonths()、getDays()。
- 若需要取得日期間的總天數—請使用 ChronoUnit.DAYS.between()。
Duration 的特點
- Duration 處理精確時間(小時、分鐘、秒)。
- 可與 LocalTime、LocalDateTime、Instant 搭配使用。
- 不能用於沒有時間的日期(LocalDate)—否則會拋出例外。
範例:Period 與 Duration 的差異
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Duration;
import java.time.Period;
LocalDate date1 = LocalDate.of(2025, 2, 28);
LocalDate date2 = LocalDate.of(2025, 3, 1);
Period period = Period.between(date1, date2); // 2 天(閏年)
long days = java.time.temporal.ChronoUnit.DAYS.between(date1, date2); // 2 天
System.out.println("Period: " + period.getDays() + " 天");
System.out.println("ChronoUnit: " + days + " 天");
LocalDateTime dt1 = LocalDateTime.of(2025, 6, 1, 23, 0);
LocalDateTime dt2 = LocalDateTime.of(2025, 6, 2, 1, 0);
Duration duration = Duration.between(dt1, dt2); // 2 小時
System.out.println("Duration: " + duration.toHours() + " 小時");
6. 用 Duration 與 Period 以易讀的方式輸出差值
當你為使用者計算日期或時間的差異時,顯示成「123456 秒」並不友善。通常我們會希望得到像「2 天 3 小時 15 分鐘」這樣的格式。
範例:輸出日期與時間的差異
import java.time.LocalDateTime;
import java.time.Duration;
LocalDateTime start = LocalDateTime.of(2025, 6, 1, 14, 0);
LocalDateTime end = LocalDateTime.of(2025, 6, 3, 16, 30);
Duration duration = Duration.between(start, end);
long days = duration.toDays();
long hours = duration.minusDays(days).toHours();
long minutes = duration.minusDays(days).minusHours(hours).toMinutes();
System.out.println("差異:" + days + " 天 " +
hours + " 小時 " + minutes + " 分鐘");
7. 使用 ZonedDateTime 與 Instant 的注意事項
- 位於不同行政時區的兩個 ZonedDateTime 之間的差異,以絕對時間計算,而非各自時區的「人類時間」。
- 夏令時間/冬令時間的切換可能會影響日期之間的小時差。
範例:ZonedDateTime 之間的差異
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.Duration;
ZonedDateTime MinskTime = ZonedDateTime.of(2025, 6, 1, 12, 0, 0, 0, ZoneId.of("Europe/Minsk"));
ZonedDateTime nyTime = ZonedDateTime.of(2025, 6, 1, 5, 0, 0, 0, ZoneId.of("America/New_York"));
Duration diff = Duration.between(nyTime, MinskTime);
System.out.println("差異:" + diff.toHours() + " 小時");
8. 處理日期與時間時的常見錯誤
錯誤 №1:混淆 Period 與 Duration。
若試圖將 Duration 用在 LocalDate 上,會拋出例外。處理日期請使用 Period,處理時間—使用 Duration。
錯誤 №2:忽略時區。
若在不同時區比較日期與時間,可能得到出乎意料的結果。最好將一切轉換到同一個時區,或使用 Instant。
錯誤 №3:以為 plus/minus 方法會修改物件。
所有 java.time 的物件都是不可變的!別忘了把結果指派給新的變數(例如,today = today.plusDays(1))。
錯誤 №4:誤以為 getDays() 是總天數。
事實上它只代表扣掉年與月之後的剩餘天數。若要總天數請使用 ChronoUnit.DAYS.between()。
錯誤 №5:比較不同型別的物件。
isBefore、isAfter、isEqual 僅適用於相同型別的物件(例如,LocalDate 對 LocalDate)。
GO TO FULL VERSION