計算「下一天」看似簡單,實則涉及月份天數差異、閏年規則、邊界進位。傳統手工邏輯冗長易錯,Java 8 `java.time` 一行代碼搞定。本文對比手工實現、Calendar、LocalDate 三種方案,給出生產級最佳實踐。
輸入:`2024-02-28` → 輸出:`2024-02-29` (閏年)
輸入:`2024-12-31` → 輸出:`2025-01-01` (跨年)
// 原始碼風格:巨大的 switch-case 處理月份天數、閏年判斷
public String nextDayManual(int year, int month, int day) {
// 1. 校驗輸入合法性 (省略)
// 2. 判斷當月天數
int daysInMonth;
switch (month) {
case 1: case 3: case 5: case 7: case 8: case 10: case 12: daysInMonth = 31; break;
case 4: case 6: case 9: case 11: daysInMonth = 30; break;
case 2:
boolean leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
daysInMonth = leap ? 29 : 28;
break;
default: throw new IllegalArgumentException("Invalid month");
}
// 3. 計算第二天
if (day < daysInMonth) {
day++;
} else {
day = 1;
if (month < 12) month++;
else { month = 1; year++; }
}
return String.format("%04d-%02d-%02d", year, month, day);
}
痛點:代碼冗長、閏年邏輯易寫錯、測試用例多 (月末、年末、閏年、平年)、無法複用、時區/夏令時無感知。
public String nextDayCalendar(int year, int month, int day) {
Calendar cal = Calendar.getInstance(); // 非線程安全!併發需 ThreadLocal 或新建
cal.set(year, month - 1, day); // 月份 0 基:1月=0
cal.add(Calendar.DAY_OF_MONTH, 1); // 自動處理進位、閏年
return String.format("%04d-%02d-%02d",
cal.get(Calendar.YEAR),
cal.get(Calendar.MONTH) + 1,
cal.get(Calendar.DAY_OF_MONTH));
}
痛點:可變對象非線程安全、月份 0 基反直覺、API 繁瑣、時區依賴默認時區、已被 `java.time` 取代。
// 核心一行:不可變、線程安全、語義清晰、自動處理所有邊界
public String nextDayLocalDate(int year, int month, int day) {
LocalDate date = LocalDate.of(year, month, day);
LocalDate next = date.plusDays(1);
return next.toString(); // ISO 格式 yyyy-MM-dd
}
// 解析字符串輸入
public String nextDayParse(String input, String pattern) {
DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pattern);
LocalDate date = LocalDate.parse(input, fmt);
return date.plusDays(1).format(fmt);
}
// 批量計算 / 日期範圍
public List dateRange(LocalDate start, LocalDate end) {
return start.datesUntil(end.plusDays(1)).collect(Collectors.toList()); // Java 9+
}
| 維度 | 手工邏輯 | Calendar | LocalDate |
|---|---|---|---|
| 代碼行數 | 30+ | 8-10 | 1-3 |
| 線程安全 | 是 (無狀態) | 否 (可變) | 是 (不可變) |
| 閏年/邊界 | 手動實現易錯 | 內建 | 內建、經過海量測試 |
| 時區感知 | 無 | 依賴默認時區 | 無時區 (純日期),跨時區用 ZonedDateTime |
| 可讀性 | 低 | 中 | 高 (領域語言) |
| 擴展性 | 差 | 一般 | 強 (Period、ChronoUnit、TemporalAdjuster) |
| 性能 | 快 (簡單算術) | 中 (對象創建、同步) | 快 (輕量對象、逃逸分析優化) |
// 下一個工作日 (跳過週末)
LocalDate nextWorkday = date.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY))
.with(TemporalAdjusters.next(DayOfWeek.MONDAY)); // 簡化版
// 本月最後一天
LocalDate monthEnd = date.with(TemporalAdjusters.lastDayOfMonth());
// 下一個特定日期 (如每月 15 號)
LocalDate next15th = date.withDayOfMonth(15);
if (!next15th.isAfter(date)) next15th = next15th.plusMonths(1);
// 自定義調整器:下一個交易日 (需結合節假日日曆)
public class NextTradingDayAdjuster implements TemporalAdjuster {
private final HolidayCalendar calendar;
@Override
public LocalDate adjustInto(LocalDate date) {
LocalDate next = date.plusDays(1);
while (calendar.isHoliday(next) || next.getDayOfWeek().getValue() > 5) {
next = next.plusDays(1);
}
return next;
}
}
計算下一天:`LocalDate.of(y,m,d).plusDays(1)`。拋棄手工 switch-case 與 `Calendar`,擁抱 `java.time` 不可變、線程安全、領域語義豐富的現代 API。一行代碼替換幾十行邏輯,消除閏年/邊界 Bug,提升可讀性與維護性。