前言
java.util.Calendar 是 JDK 1.1 引入的抽象類別,提供**曆法欄位操作**、**日期運算**、**時區處理**等功能。雖然 JDK 8 引入更現代的 java.time 套件 (LocalDateTime, ZonedDateTime 等),但 Calendar 在大量舊系統、Android 專案、特定曆法需求 (佛曆、日曆) 中仍廣泛使用。本文完整說明 Calendar 常用欄位、操作方法、時區處理與遷移建議。
核心概念
- 抽象基類:
Calendar 為抽象類,實際使用具體實作 GregorianCalendar (格里曆/公曆)。
- 欄位常數:
YEAR, MONTH (0-11), DATE/DAY_OF_MONTH, HOUR (0-11), HOUR_OF_DAY (0-23), MINUTE, SECOND, MILLISECOND, DAY_OF_WEEK, WEEK_OF_YEAR 等。
- 可變性:Calendar 物件**可變**,多執行緒環境**非安全**,需自行同步或使用
ThreadLocal。
基本用法範例
import java.util.*;
public class CalendarDemo {
public static void main(String[] args) {
// 1. 取得預設時區/地區的 GregorianCalendar 實例
Calendar cal = Calendar.getInstance(); // 等同 new GregorianCalendar()
// 2. 設定特定時間 (可選)
Date now = new Date();
cal.setTime(now);
// 3. 讀取各欄位
printFields(cal, "目前時間");
// 4. 日期運算:加減天數、月份、年份
cal.add(Calendar.DATE, 10); // 10 天後
printFields(cal, "10 天後");
cal.add(Calendar.MONTH, -3); // 3 個月前
printFields(cal, "3 個月前");
// 5. 設定特定欄位 (清除較低精度欄位避免殘留)
cal.clear(Calendar.HOUR_OF_DAY);
cal.set(Calendar.HOUR_OF_DAY, 15); // 設為下午 3 點
cal.clear(Calendar.MINUTE);
cal.set(Calendar.MINUTE, 30);
cal.clear(Calendar.SECOND);
cal.clear(Calendar.MILLISECOND);
printFields(cal, "設定為 15:30:00");
// 6. 時區資訊
System.out.println("時區偏移 (小時): " + cal.get(Calendar.ZONE_OFFSET) / (60*60*1000));
System.out.println("夏令時間偏移 (小時): " + cal.get(Calendar.DST_OFFSET) / (60*60*1000));
}
static void printFields(Calendar cal, String label) {
System.out.println("\n=== " + label + " ===");
System.out.println("ERA: " + cal.get(Calendar.ERA)); // 1=AD, 0=BC
System.out.println("YEAR: " + cal.get(Calendar.YEAR));
System.out.println("MONTH: " + (cal.get(Calendar.MONTH) + 1)); // 0-based → 1-12
System.out.println("DATE: " + cal.get(Calendar.DATE));
System.out.println("DAY_OF_WEEK: " + cal.get(Calendar.DAY_OF_WEEK)); // 1=Sun...7=Sat
System.out.println("HOUR_OF_DAY: " + cal.get(Calendar.HOUR_OF_DAY));
System.out.println("MINUTE: " + cal.get(Calendar.MINUTE));
System.out.println("SECOND: " + cal.get(Calendar.SECOND));
System.out.println("MILLISECOND: " + cal.get(Calendar.MILLISECOND));
System.out.println("ZONE_OFFSET: " + cal.get(Calendar.ZONE_OFFSET)/(60*60*1000) + "h");
System.out.println("DST_OFFSET: " + cal.get(Calendar.DST_OFFSET)/(60*60*1000) + "h");
}
}
關鍵操作方法對照表
| 操作 | 方法 | 說明 |
| 取得單一欄位 | get(int field) | 回傳 int 值 |
| 設定單一欄位 | set(int field, int value) | 不正規化,需呼叫 getTime() 才重新計算 |
| 日期加減 (推薦) | add(int field, int amount) | 正規化處理月份天數差異、閏年、夏令時間 |
| 日期滾動 (不進位) | roll(int field, int amount) | 如 1/31 roll 月份 +1 → 2/28 (不改變年份) |
| 清除欄位 | clear(int field) / clear() | 設為未定義狀態,下次 get/計算時重新推導 |
| 轉換 Date | getTime() / setTime(Date) | 與舊 API 互通 |
| 比較時間 | before(Object), after(Object), compareTo(Calendar) | 比較時間點 |
| 複製 | clone() | 建立獨立副本 |
常見陷阱與最佳實務
1. MONTH 為 0-based (0=一月, 11=十二月)
cal.set(Calendar.MONTH, 0); // 一月,非 1
System.out.println(cal.get(Calendar.MONTH) + 1); // 顯示時 +1
2. set() 不立即正規化,需 getTime() 觸發
cal.set(Calendar.MONTH, 1); // 二月
cal.set(Calendar.DATE, 31); // 設為 2/31 (不存在)
Date d = cal.getTime(); // 自動正規化 → 3/2 或 3/3 (視閏年)
System.out.println(d);
3. 多執行緒不安全
// 錯誤:共享單一 Calendar 實例
static Calendar shared = Calendar.getInstance();
// 正確:ThreadLocal 隔離
static ThreadLocal threadCal = ThreadLocal.withInitial(Calendar::getInstance);
4. 時區處理
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("Asia/Taipei"));
// 或
cal.setTimeZone(TimeZone.getTimeZone("UTC"));
與 java.time (JDK 8+) 互通遷移
// Calendar → Instant → ZonedDateTime
Calendar cal = Calendar.getInstance();
Instant instant = cal.toInstant();
ZonedDateTime zdt = instant.atZone(ZoneId.of("Asia/Taipei"));
// ZonedDateTime → Calendar
ZonedDateTime now = ZonedDateTime.now();
Calendar cal2 = Calendar.from(now.toInstant());
cal2.setTimeZone(TimeZone.getTimeZone(now.getZone()));
遷移建議對照
| Calendar | java.time 替代 | 優勢 |
Calendar.getInstance() | LocalDateTime.now() / ZonedDateTime.now() | 不可變、執行緒安全、API 語意清晰 |
cal.add(DAY, 1) | date.plusDays(1) | 流式 API、不可變 |
cal.get(YEAR) | date.getYear() | 型別安全、方法名直觀 |
cal.setTimeZone(tz) | zdt.withZoneSameInstant(zone) | 明確區分 Instant/Local/Zoned |
SimpleDateFormat | DateTimeFormatter | 執行緒安全、預定義格式 |
特殊曆法支援
JDK 內建 JapaneseImperialCalendar (和曆)、BuddhistCalendar (佛曆)。第三方庫 (ICU4J) 提供農曆、波斯曆、伊斯蘭曆等完整支援。
總結
- Calendar 適用於**舊系統維護、Android (API < 26)、特殊曆法需求**。
- 核心操作:
getInstance()、get/set/add/roll/clear、getTime()/setTime()。
- 注意:**MONTH 0-based、set 非即時正規化、非執行緒安全、時區明確設定**。
- **新專案優先使用 java.time**,舊系統逐步遷移,邊界轉換用
toInstant()/Calendar.from()。
- 上一篇文章: JDBC 技術架構與核心 API 完整介紹
- 下一篇文章: JSP 連線 SQL Server 查詢資料完整範例 (JDBC-ODBC Bridge)
Copyright ©
程式碼之家 All Rights Reserved