Java 標準庫中 java.util.Date、java.util.Calendar、java.text.DateFormat 組成了時間處理的「鐵三角」。雖然 Java 8 引入了全新的 java.time 包,但存量系統中這三大類仍佔據主導地位。本文將系統講解它們的類層次、核心方法、格式化解析、日期運算、時區處理,並對比 java.sql.Date 差異,助你構建完整時間處理知識體系。
| 類/接口 | 類型 | 核心職責 | 關鍵說明 |
|---|---|---|---|
java.util.Date | 具體類 | 時間戳容器(毫秒精度) | JDK 1.0 引入,早期包含大量棄用方法(getYear/setMonth 等),現僅作毫秒數承載 |
java.text.DateFormat | 抽象類 | 日期/時間格式化與解析規範 | 提供靜態工廠方法獲取本地化格式化器 |
java.text.SimpleDateFormat | 具體類 | 模式字符串驅動的格式化實現 | 非線程安全,支援自定義模式(yyyy-MM-dd HH:mm:ss 等) |
java.util.Calendar | 抽象類 | 日曆字段運算與時區感知 | 替代 Date 棄用方法,支援加減運算、字段獲取/設置 |
java.util.GregorianCalendar | 具體類 | 公曆(格里高利曆)實現 | Calendar 唯一公共具體子類,處理閏年、時代(BC/AD) |
// 1. 獲取系統當前時間戳
Date now = new Date(); // 等價 new Date(System.currentTimeMillis())
long timestamp = now.getTime(); // 1970-01-01 00:00:00 UTC 起的毫秒數
long timestamp2 = System.currentTimeMillis(); // 更高效,避免創建對象
// 2. 從毫秒數構造 Date
Date specific = new Date(1_000_000_000_000L); // 2001-09-09 01:46:40 UTC
// 3. Date ↔ String(需配合格式化類)
String iso = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").format(now); // 2024-01-15T14:30:00+08:00
| 字母 | 含義 | 示例 |
|---|---|---|
| y | 年 | yy=24, yyyy=2024 |
| M | 月 | M=1, MM=01, MMM=Jan, MMMM=January |
| d | 月中的天 | d=5, dd=05 |
| H | 小時(0-23) | H=9, HH=09 |
| m | 分鐘 | m=5, mm=05 |
| s | 秒 | s=9, ss=09 |
| S | 毫秒 | SSS=005 |
| E | 星期 | E=Tue, EEEE=Tuesday |
| Z/X | 時區偏移 | Z=+0800, XXX=+08:00 |
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 格式化 Date → String
String formatted = fmt.format(new Date()); // "2024-01-15 14:30:00"
// 解析 String → Date(必須捕獲 ParseException)
try {
Date parsed = fmt.parse("2024-01-15 14:30:00");
System.out.println(parsed.getTime());
} catch (ParseException e) {
throw new IllegalArgumentException("日期格式錯誤", e);
}
// 線程安全最佳實踐:ThreadLocal 緩存或每次新建
ThreadLocal TL_FMT = ThreadLocal.withInitial(
() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
Date now = new Date(); DateFormat shortFmt = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); DateFormat mediumFmt = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM); DateFormat longFmt = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG); DateFormat fullFmt = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL); // 輸出示例(Locale.US) // SHORT: 1/15/24, 2:30 PM // MEDIUM: Jan 15, 2024, 2:30:00 PM // LONG: January 15, 2024, 2:30:00 PM CST // FULL: Monday, January 15, 2024, 2:30:00 PM China Standard Time
兩個參數分別控制「日期風格」與「時間風格」,均為 int 常量:SHORT, MEDIUM, LONG, FULL、DEFAULT(=MEDIUM)。
YEAR, MONTH(0-11), DAY_OF_MONTH, DAY_OF_WEEK, DAY_OF_YEAR, WEEK_OF_MONTH, WEEK_OF_YEAR, HOUR(0-11), HOUR_OF_DAY(0-23), MINUTE, SECOND, MILLISECOND, ZONE_OFFSET, DST_OFFSET, ERA(BC/AD)
GregorianCalendar cal = new GregorianCalendar(); // 默認時區、Locale
cal.setTime(new Date()); // 同步系統時間
// 1. 獲取字段
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH) + 1; // 0-based → 1-based
int day = cal.get(Calendar.DAY_OF_MONTH);
// 2. 設置字段(會重新計算其他字段)
cal.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY); // 調整到本週五
cal.set(2024, Calendar.DECEMBER, 25); // 直接設置年月日
// 3. 加減運算(自動處理進位、閏年、月長短)
cal.add(Calendar.DAY_OF_MONTH, 7); // 7 天后
cal.add(Calendar.MONTH, -3); // 3 月前
cal.roll(Calendar.DAY_OF_MONTH, 10); // 僅滾動天,不改變月/年
// 4. 時區轉換
cal.setTimeZone(TimeZone.getTimeZone("UTC"));
Date utcTime = cal.getTime(); // 獲取對應 UTC 時刻的 Date
DateFormat full = DateFormat.getDateInstance(DateFormat.FULL);
GregorianCalendar cal = new GregorianCalendar();
cal.setTime(new Date());
cal.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY); // 對齊最近的週五
int found = 0;
while (found < 10) {
cal.add(Calendar.DAY_OF_MONTH, 7); // 下一個週五
if (cal.get(Calendar.DAY_OF_MONTH) == 13) {
System.out.println(full.format(cal.getTime()));
found++;
}
}
| 特性 | java.util.Date | java.sql.Date |
|---|---|---|
| 繼承關係 | 無 | extends java.util.Date |
| 精度 | 毫秒 | 僅保留日期部分(時分秒毫秒歸零) |
| 用途 | 通用業務邏輯 | JDBC 對應 SQL DATE 類型 |
| 構造 | new Date() | new java.sql.Date(utilDate.getTime()) |
| toString() | Mon Jan 15 14:30:00 CST 2024 | 2024-01-15 |
最佳實踐: DAO 層入參/出參使用 java.time.LocalDate(Java 8+)或 java.sql.Date;Service/Controller 層統一使用 java.time.LocalDateTime 或 java.util.Date;轉換由持久層框架(MyBatis/Hibernate)自動處理,手動轉換僅在邊界必要時進行。
ThreadLocal、局部變量或 Java 8 DateTimeFormatter(不可變、線程安全)。Calendar.MONTH 0-based 陷阱:1月=0,12月=11,設置/獲取時必須 ±1。Date.toString()、SimpleDateFormat 默認使用 JVM 默認時區(TimeZone.getDefault()),分佈式系統務必顯式指定時區或統一 UTC 存儲。Date.getYear(), Date.setHours() 等編譯期即報警,嚴禁使用。LocalDate, LocalDateTime, ZonedDateTime, Instant, Duration, Period, DateTimeFormatter — 不可變、線程安全、API 清晰、時區處理徹底。Date 承載時間戳,Calendar 操作日曆字段,DateFormat 處理人類可讀表達,三者分工明確、協作緊密。掌握其核心 API、常見陷阱、線程安全模式,是維護存量 Java 系統、平滑遷移至 java.time 的必修課。建議團隊建立「時間處理編碼規約」:統一 UTC 存儲、顯式時區傳遞、禁用棄用方法、新代碼強制 java.time,從源頭消除時間類 Bug。