Calendar cal = Calendar.getInstance();
cal.clear(); // 必須先清除,否則繼承當前時間殘留值
cal.set(Calendar.YEAR, 2024);
cal.set(Calendar.MONTH, 1); // 2月 (0-based)
int maxDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH); // 29 (閏年)
// Calendar → Date
Date date = cal.getTime();
// Date → Calendar
Calendar cal2 = Calendar.getInstance();
cal2.setTime(date);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 24小時制 HH
String formatted = sdf.format(new Date()); // "2024-01-15 14:30:45"
// 解析
Date parsed = sdf.parse("2024-01-15 14:30:45");
⚠️ `SimpleDateFormat` 非執行緒安全,多執行緒環境必須:
// 某天是第幾週
Calendar cal = Calendar.getInstance();
cal.set(2024, Calendar.JANUARY, 1);
int weekOfYear = cal.get(Calendar.WEEK_OF_YEAR); // 1
// 第 1 週的週一是幾號
cal.set(2024, Calendar.JANUARY, 1);
cal.set(Calendar.WEEK_OF_YEAR, 1);
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
System.out.println(sdf.format(cal.getTime())); // 2024-01-01
Calendar cal = Calendar.getInstance();
cal.set(2024, Calendar.SEPTEMBER, 3); // 9月3日
cal.add(Calendar.DATE, -4); // 向前4天 → 8月30日 (月份自動進位)
cal.add(Calendar.DATE, 4); // 回到 9月3日
cal.set(2024, Calendar.SEPTEMBER, 3);
cal.roll(Calendar.DATE, -4); // 本月內循環 → 8月30日? 不! roll不進位月份 → 9月29日
cal.roll(Calendar.DATE, 4); // → 9月3日
結論:幾乎永遠用 `add()`,`roll()` 僅適用「同欄位循環」如時鐘表盤。
// 方案 A:毫秒差值 (純時間跨度)
public static long daysBetween(Date start, Date end) {
if (start.after(end)) { Date t = start; start = end; end = t; }
return (end.getTime() - start.getTime()) / (1000L * 60 * 60 * 24);
}
// 方案 B:Calendar 自然日差 (忽略時分秒)
public static int calendarDaysBetween(Calendar d1, Calendar d2) {
if (d1.after(d2)) { Calendar t = d1; d1 = d2; d2 = t; }
int days = d2.get(Calendar.DAY_OF_YEAR) - d1.get(Calendar.DAY_OF_YEAR);
int y2 = d2.get(Calendar.YEAR);
if (d1.get(Calendar.YEAR) != y2) {
d1 = (Calendar) d1.clone();
do {
days += d1.getActualMaximum(Calendar.DAY_OF_YEAR); // 當年總天數
d1.add(Calendar.YEAR, 1);
} while (d1.get(Calendar.YEAR) != y2);
}
return days;
}
💡 方案 B 解決「跨年」與「23:00 到隔日 01:00 算 1 天」等業務語義差異。
| Legacy | Modern (java.time) | 優勢 |
|---|---|---|
| `Date` | `Instant` / `LocalDateTime` | 不可變、納秒精度 |
| `Calendar` | `ZonedDateTime` / `LocalDate` | 領域模型清晰、時區支援完整 |
| `SimpleDateFormat` | `DateTimeFormatter` | 執行緒安全、預定義格式常數 |
| 手算間隔 | `ChronoUnit.DAYS.between(d1, d2)` | 語義明確、支援各時間單位 |
| `TimeZone` | `ZoneId` | IANA 時區資料庫、歷史變更支援 |
// Java 8 範例
LocalDate today = LocalDate.now();
LocalDate firstDayOfMonth = today.withDayOfMonth(1);
int daysInMonth = today.lengthOfMonth(); // 直接取得
long daysBetween = ChronoUnit.DAYS.between(startDate, endDate);
// 格式化
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String str = zdt.format(fmt);
ZonedDateTime parsed = ZonedDateTime.parse(str, fmt);