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

Java Calendar 日曆 API 完全解析:核心原理、常用操作與實戰工具類

     編輯:Java 相關知識

Calendar 類架構速覽

public abstract class Calendar 
    implements Serializable, Cloneable, Comparable {
    
    // 工廠方法獲取實例
    public static Calendar getInstance()
    public static Calendar getInstance(TimeZone zone)
    public static Calendar getInstance(Locale locale)
    public static Calendar getInstance(TimeZone zone, Locale locale)
    
    // 核心字段 (17個)
    public final static int ERA = 0;           // 紀元 (BC/AD)
    public final static int YEAR = 1;          // 年
    public final static int MONTH = 2;         // 月 (0-11, JANUARY=0)
    public final static int WEEK_OF_YEAR = 3;  // 年內第幾周
    public final static int WEEK_OF_MONTH = 4; // 月內第幾周
    public final static int DATE = 5;          // 日 (1-31)
    public final static int DAY_OF_MONTH = 5;  // 同 DATE
    public final static int DAY_OF_YEAR = 6;   // 年內第幾天
    public final static int DAY_OF_WEEK = 7;   // 週幾 (SUNDAY=1)
    public final static int DAY_OF_WEEK_IN_MONTH = 8;
    public final static int AM_PM = 9;         // 上午/下午 (AM=0, PM=1)
    public final static int HOUR = 10;         // 12制時 (0-11)
    public final static int HOUR_OF_DAY = 11;  // 24制時 (0-23)
    public final static int MINUTE = 12;       // 分
    public final static int SECOND = 13;       // 秒
    public final static int MILLISECOND = 14;  // 毫秒
    public final static int ZONE_OFFSET = 15;  // 時區偏移 (毫秒)
    public final static int DST_OFFSET = 16;   // 夏令時偏移 (毫秒)
}

核心設計原理

  • 單一時間源:內部維護 protected long time (毫秒時間戳,相對 1970-01-01 UTC)
  • 字段延遲計算:17 個字段值存儲在 int[] fields,調用 get() 時觸發 complete() → computeFields() 從 time 反推
  • 子類實現具體日曆系統:GregorianCalendar (默認)、JapaneseImperialCalendar、BuddhistCalendar 等
  • 時區與 Locale 敏感:構造時傳入決定具體行為

常用 API 分類速查

1. 字段訪問與修改

方法說明示例
get(field)獲取字段值cal.get(Calendar.MONTH) → 8 (9月)
set(field, value)設置字段值 (延遲生效)cal.set(Calendar.YEAR, 2024)
add(field, amount)增加/減少字段值 (聯動大字段)cal.add(Calendar.MONTH, -1) 上個月
roll(field, amount)滾動字段值 (不聯動大字段)cal.roll(Calendar.MONTH, -1) 月份-1, 年份不變
clear(field)清空字段 (重置為最小值)cal.clear(Calendar.HOUR_OF_DAY)
isSet(field)判斷字段是否被顯式設置cal.isSet(Calendar.YEAR)

2. 邊界值查詢

// 理論極值 (不依賴當前日期)
cal.getMaximum(Calendar.DATE);        // 31
cal.getMinimum(Calendar.MONTH);       // 0

// 實際極值 (依賴當前日期)
cal.getActualMaximum(Calendar.DATE);  // 當月實際天數 (28/29/30/31)
cal.getActualMinimum(Calendar.DATE);  // 總是 1

3. 日期時間物件互轉

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

// Date → Calendar
cal.setTime(date);

// Calendar → 毫秒時間戳
long ms = cal.getTimeInMillis();
cal.setTimeInMillis(ms);

// 時區操作
TimeZone tz = cal.getTimeZone();
cal.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));

4. 比較與判斷

cal1.equals(cal2);       // 時間+時區+Locale 全等
cal1.before(cal2);       // 是否早於
cal1.after(cal2);        // 是否晚於
cal1.compareTo(cal2);    // -1/0/1

5. 寬容模式

cal.setLenient(true);   // 寬容:月份 13 自動進位到明年 1 月
cal.setLenient(false);  // 嚴格:月份 13 拋 IllegalArgumentException
// 異常在 get()/add()/roll() 等觸發計算時拋出,set() 只標記

6. 週相關設置

cal.getFirstDayOfWeek();           // 週幾為每周第一天 (US=SUNDAY, EU=MONDAY)
cal.setFirstDayOfWeek(Calendar.MONDAY);
cal.getMinimalDaysInFirstWeek();   // 第一周最少天數 (1 或 7)
cal.setMinimalDaysInFirstWeek(4);  // ISO 8601 標準:第一周含當年第一個周四

Java 8+ java.time 替代方案 (強烈推薦)

Calendarjava.time (不可變、線程安全、API 清晰)
Calendar.getInstance()LocalDateTime.now() / ZonedDateTime.now()
cal.get(YEAR)ldt.getYear()
cal.add(MONTH, 1)ldt.plusMonths(1)
cal.set(YEAR, 2024)ldt.withYear(2024)
cal.getTime()Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant())
SimpleDateFormatDateTimeFormatter.ofPattern("yyyy-MM-dd")

實戰工具類:CalendarUtils (生產級封裝)

public final class CalendarUtils {
    private CalendarUtils() {}
    
    // 獲取 Calendar 實例 (指定時區)
    public static Calendar getCalendar(TimeZone tz) {
        Calendar cal = Calendar.getInstance(tz);
        cal.setLenient(false); // 生產環境建議嚴格模式
        return cal;
    }
    
    // 當月天數
    public static int daysInMonth(int year, int month) { // month 1-12
        Calendar cal = Calendar.getInstance();
        cal.set(year, month - 1, 1);
        return cal.getActualMaximum(Calendar.DATE);
    }
    
    // 當月第一天是周幾 (1=SUN...7=SAT)
    public static int firstDayOfWeek(int year, int month) {
        Calendar cal = Calendar.getInstance();
        cal.set(year, month - 1, 1);
        return cal.get(Calendar.DAY_OF_WEEK);
    }
    
    // 日期加減天數
    public static Date addDays(Date date, int days) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.add(Calendar.DATE, days);
        return cal.getTime();
    }
    
    // 日期加減月數 (保持日數,超出則取月末)
    public static Date addMonths(Date date, int months) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.add(Calendar.MONTH, months);
        return cal.getTime();
    }
    
    // 判斷是否同一天
    public static boolean isSameDay(Date d1, Date d2) {
        Calendar c1 = Calendar.getInstance(), c2 = Calendar.getInstance();
        c1.setTime(d1); c2.setTime(d2);
        return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR)
            && c1.get(Calendar.DAY_OF_YEAR) == c2.get(Calendar.DAY_OF_YEAR);
    }
    
    // 格式化輸出
    public static String format(Date date, String pattern) {
        return new SimpleDateFormat(pattern).format(date);
    }
    
    // 解析字串
    public static Date parse(String str, String pattern) throws ParseException {
        return new SimpleDateFormat(pattern).parse(str);
    }
}

常見坑點避雷

  • 月份 0 基:Calendar.JANUARY = 0,設置月份時 必須 -1,獲取時 必須 +1 顯示給用戶
  • 可變性:Calendar 可變,多線程共享會導致數據競爭,每次使用 new 實例 或用 ThreadLocal
  • lenient 陷阱:預設寬容模式下 set(2024, 12, 32) 不報錯,實際變為 2025-02-01,生產環境關閉寬容模式
  • 時區陷阱:getTime() 返回 Date 無時區信息,跨時區業務必須用 ZonedDateTime 或攜帶時區上下文
  • 性能:Calendar 重量級,高頻調用建議遷移 java.time 或緩存 DateTimeFormatter

遷移指南:Calendar → java.time

// 舊代碼
Calendar cal = Calendar.getInstance();
cal.set(2024, Calendar.SEPTEMBER, 15);
cal.add(Calendar.DAY_OF_MONTH, 10);
Date result = cal.getTime();

// 新代碼
LocalDate date = LocalDate.of(2024, 9, 15);
LocalDate result = date.plusDays(10);
// 如需 Date
Date legacy = Date.from(result.atStartOfDay(ZoneId.systemDefault()).toInstant());

總結

Calendar 作為 Java 早期日期時間 API,核心在於「單一時間戳 + 17字段延遲計算 + 子類實現具體日曆系統」。掌握 get/set/add/roll 四大核心操作、ActualMaximum 實際邊界值、寬容模式與時區影響,即可應對 90% 維護場景。新代碼請全面遷移至 Java 8 java.time (LocalDate/LocalDateTime/ZonedDateTime),獲得不可變性、線程安全、清晰 API 與更好性能。維護舊代碼時,封裝 CalendarUtils 統一入口,關閉寬容模式,每次使用創建新實例,嚴防併發與邊界 Bug。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved