首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java Calendar 時間操作常用方法詳解與避坑指南 (set vs add 陷阱)

     編輯:Java 相關知識

前言

Calendar 是 JDK 1.1 引入的日期時間操作主力類,雖然 JDK 8+ 推出 java.time,但大量存量代碼仍在使用。本文系統整理 Calendar 常用 API,重點揭示 set() 與 add() 的關鍵差異,這是無數開發者踩過的大坑。

一、Calendar 基礎認知

  • 抽象類,用 Calendar.getInstance() 獲取具體實現 (預設 GregorianCalendar)
  • 欄位常數:YEAR, MONTH, DATE/DAY_OF_MONTH, HOUR_OF_DAY, MINUTE, SECOND, MILLISECOND
  • 月份 0-based:JANUARY=0 ... DECEMBER=11 (極易寫錯)
  • 非執行緒安全,多執行緒環境用 ThreadLocal 或 java.time

二、核心 API 速查表

操作方法範例
獲取實例getInstance()Calendar cal = Calendar.getInstance();
獲取欄位值get(field)int year = cal.get(Calendar.YEAR);
設定欄位值set(field, value)cal.set(Calendar.YEAR, 2024);
批次設定set(y, M, d, h, m, s)cal.set(2024, 5, 20, 14, 30, 0);
增加/減少add(field, amount)cal.add(Calendar.DATE, -30);
滾動 (不進位)roll(field, amount)cal.roll(Calendar.MONTH, 1);
轉 DategetTime()Date date = cal.getTime();
從 Date 設定setTime(date)cal.setTime(new Date());
毫秒時間戳getTimeInMillis()long ms = cal.getTimeInMillis();
設定毫秒時間戳setTimeInMillis(ms)cal.setTimeInMillis(0);
清除/重設clear() / clear(field)cal.clear();
比較before/after/equals/compareTocal1.after(cal2)
最大/最小值getActualMaximum(field)int days = cal.getActualMaximum(Calendar.DATE);
週相關getFirstDayOfWeek()cal.getFirstDayOfWeek(); // 預設 SUNDAY=1

三、核心陷阱:set() vs add()

錯誤代碼:想取「兩個月前」卻用了 set

// 需求:獲取兩個月前的日期 (假設今天 2024-05-20)
Calendar cal = Calendar.getInstance();
cal.set(Calendar.DATE, -60); // ❌ 錯誤!將「日」欄位直接設為 -60
// 結果:月份不變,日期變成負數 → 內部正規化後跑到極早的日期

正確代碼:使用 add() 進行日期運算

Calendar cal = Calendar.getInstance();
cal.add(Calendar.MONTH, -2); // ✅ 正確:月份欄位減 2,自動處理進位/閏年
// 結果:2024-03-20

根本原因對比

特性set(field, value)add(field, amount)
語意直接覆寫欄位值在欄位值基礎上增減
進位處理不自動進位 (需手動正規化)自動處理進位、借位、閏年
適用場景設定具體年月日時分秒日期運算:N 天前、M 月後
範例cal.set(MONTH, 11) → 12月cal.add(MONTH, 1) → 下個月

補救寫法:若必須用 set 也可先 get 再運算

// 笨但可行
int today = cal.get(Calendar.DATE);
cal.set(Calendar.DATE, today - 60); // 先獲取當前值再運算
// 但強烈建議直接用 add()

四、實戰避坑案例集

案例 1:定時任務「每天 00:00 執行」計算下次執行時間

// ❌ 錯誤:set 會重置時分秒,但若當前已過 00:00 會停在今天
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);

// ✅ 正確:先歸零,若已過今天 00:00 再加一天
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
if (cal.getTimeInMillis() <= System.currentTimeMillis()) {
    cal.add(Calendar.DATE, 1);
}

案例 2:獲取「本月第一天 00:00」與「本月最後一天 23:59:59」

Calendar cal = Calendar.getInstance();
// 本月第一天
cal.set(Calendar.DAY_OF_MONTH, 1);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
Date monthStart = cal.getTime();

// 本月最後一天
cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
cal.set(Calendar.HOUR_OF_DAY, 23);
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
cal.set(Calendar.MILLISECOND, 999);
Date monthEnd = cal.getTime();

案例 3:計算兩日期相差月數

public static int monthDiff(Date start, Date end) {
    Calendar s = Calendar.getInstance(); s.setTime(start);
    Calendar e = Calendar.getInstance(); e.setTime(end);
    return (e.get(Calendar.YEAR) - s.get(Calendar.YEAR)) * 12
         + (e.get(Calendar.MONTH) - s.get(Calendar.MONTH));
}

案例 4:判斷閏年與月份天數

// 方法 1:Calendar 內建
cal.set(Calendar.YEAR, 2024);
cal.set(Calendar.MONTH, Calendar.FEBRUARY);
int days = cal.getActualMaximum(Calendar.DAY_OF_MONTH); // 29

// 方法 2:手動判斷 (不依賴 Calendar 實例)
public static boolean isLeap(int year) {
    return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
public static int daysInMonth(int year, int month) { // month 1-12
    if (month == 2) return isLeap(year) ? 29 : 28;
    if (month == 4 || month == 6 || month == 9 || month == 11) return 30;
    return 31;
}

五、Date ↔ Calendar ↔ String 互轉最佳實踐

// 1. Date → Calendar
Calendar cal = Calendar.getInstance();
cal.setTime(date);

// 2. Calendar → Date
Date date = cal.getTime();

// 3. Date → String (執行緒安全寫法)
String str = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);

// 4. String → Date
Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(str);

// 5. Calendar → String (直接用 getTime() 轉 Date 再格式化)
String str = new SimpleDateFormat("yyyy-MM-dd").format(cal.getTime());

// 6. String → Calendar
Calendar cal = Calendar.getInstance();
cal.setTime(new SimpleDateFormat("yyyy-MM-dd").parse(str));

六、從 Calendar 遷移到 java.time (JDK 8+)

Calendar 代碼java.time 等效優勢
cal.add(MONTH, -2)localDate.minusMonths(2)不可變、鏈式、無 0-based 月份坑
cal.set(YEAR, 2024)localDate.withYear(2024)語意清晰、回傳新物件
cal.getActualMaximum(DATE)yearMonth.lengthOfMonth()YearMonth 物件語意精確
cal.getTimeInMillis()instant.toEpochMilli()Instant 專為時間戳設計
new SimpleDateFormat...DateTimeFormatter.ofPattern...執行緒安全、可快取、支援解析嚴格模式

七、總結:Calendar 使用鐵律

  1. 日期運算永遠用 add(),絕不使用 set() 做加減
  2. 月份記住 0-based:用 Calendar.JANUARY...DECEMBER 常數
  3. 獲取月份天數用 getActualMaximum(Calendar.DATE)
  4. SimpleDateFormat 區域變數或 ThreadLocal,別 static 共用
  5. 新代碼優先用 java.time (LocalDate/LocalDateTime/ZonedDateTime)
  6. 多執行緒環境不要共享 Calendar 實例
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved