Java 中的日期時間處理涉及 Calendar、Date、DateFormat 三大核心類。本文系統梳理它們的架構關係、Calendar 的內部運作原理、17 個核心欄位定義,以及完整的 API 使用指南,助你建立堅實的時間處理知識體系。

關鍵點:
Calendar 與 Date 皆以「距 1970-01-01 00:00:00 GMT 的毫秒數」為時間基準。Calendar.getInstance() 根據 Locale 與 TimeZone 返回具體實現(預設 GregorianCalendar,日文環境返回 JapaneseImperialCalendar,泰文環境返回 BuddhistCalendar)。DateFormat(實際為 SimpleDateFormat)處理。protected long time; // 毫秒時間戳
protected int fields[]; // 17 個欄位值陣列
protected boolean isSet[]; // 欄位是否已設定標記
protected int stamp[]; // 欄位設定時間戳(用於判斷臟資料)
| 常量 | 值 | 含義 | 範例/範圍 |
|---|---|---|---|
| ERA | 0 | 紀元 | 0=BC, 1=AD |
| YEAR | 1 | 年 | 2024 |
| MONTH | 2 | 月(0 起始) | 0=JANUARY ... 11=DECEMBER |
| WEEK_OF_YEAR | 3 | 年間第幾週 | 1-53 |
| WEEK_OF_MONTH | 4 | 月內第幾週 | 1-6 |
| DATE / DAY_OF_MONTH | 5 | 月內第幾天 | 1-31 |
| DAY_OF_YEAR | 6 | 年間第幾天 | 1-366 |
| DAY_OF_WEEK | 7 | 星期幾 | 1=SUNDAY ... 7=SATURDAY |
| DAY_OF_WEEK_IN_MONTH | 8 | 月內第幾個該星期幾 | 1-5 |
| AM_PM | 9 | 上午/下午 | 0=AM, 1=PM |
| HOUR | 10 | 12 小時制時 | 0-11 |
| HOUR_OF_DAY | 11 | 24 小時制時 | 0-23 |
| MINUTE | 12 | 分 | 0-59 |
| SECOND | 13 | 秒 | 0-59 |
| MILLISECOND | 14 | 毫秒 | 0-999 |
| ZONE_OFFSET | 15 | 時區偏移毫秒 | - |
| DST_OFFSET | 16 | 夏令時偏移毫秒 | - |
public int get(int field) {
complete(); // 1. 觸發完整計算
return internalGet(field); // 2. 直接讀取 fields[field]
}
protected void complete() {
if (!isTimeSet) updateTime(); // 毫秒 → 欄位
if (!areFieldsSet || !areAllFieldsSet) {
computeFields(); // 核心計算邏輯
areAllFieldsSet = areFieldsSet = true;
}
}
computeFields() 在 GregorianCalendar 中實現,將毫秒數拆解為年月日時分秒,處理閏年、格里曆切換、時區偏移等複雜邏輯。
getMaximum(field):理論最大值(如 DATE 最大 31)getActualMaximum(field):當前日期下的實際最大值(如 9 月 DATE 最大 30)getMinimum(field) / getActualMinimum(field):同理最小值| 方法 | 用途 | 關鍵差異 |
|---|---|---|
get(field) | 取得當前欄位值 | - |
set(field, value) | 設定欄位值(延遲計算) | 不立即重算 time,標記 isSet |
add(field, amount) | 增加/減少欄位值(聯動大欄位) | 月份 +1 可能導致年份 +1 |
roll(field, amount) | 循環滾動欄位值(不聯動大欄位) | 12 月 roll +1 → 1 月,年份不變 |
clear(field) | 重置欄位為最小值 | 慎用,可能破壞日期一致性 |
isSet(field) | 判斷欄位是否被顯式設定 | - |
equals()、before()、after()、compareTo()setLenient(true):允許非法值(如 MONTH=12 自動進位),get() 時正規化setLenient(false):嚴格檢查,get()/add()/roll() 時拋出 IllegalArgumentException// Date 互轉
Date getTime();
void setTime(Date date);
// 毫秒互轉
long getTimeInMillis();
void setTimeInMillis(long millis);
// 時區
TimeZone getTimeZone();
void setTimeZone(TimeZone zone);
getFirstDayOfWeek() / setFirstDayOfWeek():週起始日(美式 Sunday,法式 Monday)getMinimalDaysInFirstWeek() / setMinimalDaysInFirstWeek():首週最少天數(影響 WEEK_OF_YEAR 計算)原文提供的 CalendarTest 涵蓋所有 API 類別的驗證邏輯,核心測試方法包括:
testAllCalendarSections():遍歷 17 個欄位,驗證 min/max/actualMin/actualMin/set/roll/add 行為testComparatorAPIs():驗證 equals/before/after/compareTotestLenientAPIs():展示寬容/非寬容模式異常拋出時機testTimeAPIs():Date/毫秒/時區互轉與多參數 set()testOtherAPIs():clone、週曆規則對 WEEK_OF_YEAR 的影響getActualMaximum() 而非 getMaximum() 進行邊界檢查。add(),單欄位循環用 roll()。Calendar 非線程安全,請使用 ThreadLocal 或每次新建實例。java.time 套件(LocalDate、ZonedDateTime 等),僅維護舊代碼時使用 Calendar。Calendar 以毫秒時間戳為單一真相來源,透過 complete() → computeFields() 鏈路按需計算 17 個欄位。理解其「延遲計算」、「寬容模式」、「add/roll 語義差異」是正確使用的關鍵。雖然現代 Java 推薦 java.time,但深入掌握 Calendar 內部機制對維護存量系統、排查時區/閏年 Bug 仍具不可替代的價值。