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

Java 日期時間處理完全指南:Date、Calendar、SimpleDateFormat 核心用法與陷阱

     編輯:J2EE

演進脈絡:從 Date 到 Calendar 再到 java.time

  • JDK 1.0:java.util.Date 包含年/月/日/時/分/秒完整功能,但設計缺陷多(不可變、非執行緒安全、國際化弱)。
  • JDK 1.1:引入 Calendar、DateFormat 等,Date 降級為「毫秒時間戳包裝器」(自 1970-01-01 00:00:00 UTC 起算的毫秒數)。
  • JDK 8:全新 java.time 套件(JSR-310),不可變、執行緒安全、豐富 API,**新專案首選**。

本文聚焦 Date/Calendar/SimpleDateFormat 經典體系(維護舊專案必備),文末附遷移建議。

核心類別速覽

類別套件核心職責執行緒安全
Datejava.util時間戳容器(long milliseconds)✅ 不可變
Calendarjava.util日期運算、欄位存取、時區、國際化❌ 可變、非安全
DateFormatjava.text抽象基類,格式化/解析❌ 非安全
SimpleDateFormatjava.text自訂模式格式化/解析❌ 非安全

實戰範例集錦

1. 取得當前時間戳與 Date 物件

// 方式一:Date 無參建構子(內部呼叫 System.currentTimeMillis())
Date now = new Date();
long timestamp = now.getTime(); // 1001803809710 (例)

// 方式二:直接取得時間戳
long ts = System.currentTimeMillis();
Date now2 = new Date(ts);

2. 自訂格式輸出:SimpleDateFormat

// 模式字元對照:E=星期, M=月, d=日, y=年, H=時(24), m=分, s=秒, S=毫秒
SimpleDateFormat sdf = new SimpleDateFormat("EEEE-MMMM-dd-yyyy HH:mm:ss.SSS");
String formatted = sdf.format(new Date());
// 輸出範例:Saturday-September-29-2001 18:50:09.710

// 常用預設模式
SimpleDateFormat shortFmt   = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat fullFmt    = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat iso8601Fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); // ISO 8601 帶時區

3. 字串解析為 Date

SimpleDateFormat parser = new SimpleDateFormat("MM-dd-yyyy");
String input = "9-29-2001";
try {
    Date date = parser.parse(input); // 嚴格依模式解析
    System.out.println(date.getTime()); // 1001736000000
} catch (ParseException e) {
    // 輸入不符模式拋出 ParseException
    e.printStackTrace();
}

4. 內建風格格式化:DateFormat.getXxxInstance()

Date now = new Date();
// 參數:日期風格, 時間風格 (SHORT/MEDIUM/LONG/FULL)
DateFormat shortDt = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
DateFormat medDt   = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
DateFormat longDt  = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
DateFormat fullDt  = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL);

System.out.println(shortDt.format(now)); // 9/29/01 6:44 PM
System.out.println(medDt.format(now));   // Sep 29, 2001 6:44:45 PM
System.out.println(longDt.format(now));  // September 29, 2001 6:44:45 PM EDT
System.out.println(fullDt.format(now));  // Saturday, September 29, 2001 6:44:45 PM EDT

5. Calendar 日期運算與欄位存取

Calendar cal = Calendar.getInstance(); // 預設時區、Locale
cal.setTime(new Date()); // 從 Date 初始化

// 取得欄位
int year  = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH); // 0-based! 0=一月
int day   = cal.get(Calendar.DAY_OF_MONTH);
int dow   = cal.get(Calendar.DAY_OF_WEEK); // 1=Sunday ... 7=Saturday

// 日期運算(可正負)
cal.add(Calendar.DAY_OF_MONTH, 10);   // 加 10 天
cal.add(Calendar.MONTH, -1);          // 減 1 月
cal.roll(Calendar.DAY_OF_MONTH, 5);   // 滾動:不改變較大欄位(月/年)

// 設定特定時間
cal.set(2024, Calendar.DECEMBER, 25, 12, 0, 0); // 2024-12-25 12:00:00
cal.set(Calendar.HOUR_OF_DAY, 0); // 清零時分秒
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);

⚠️ 必須避開的經典坑

坑點錯誤代碼正確做法原因
SimpleDateFormat 非執行緒安全static final SimpleDateFormat SF = new SimpleDateFormat("..."); 多執行緒共用每次 new、ThreadLocal、或改用 DateTimeFormatter (JDK 8+)內部共享 Calendar 導致競態
月份 0-basedcal.set(2024, 12, 25) → 2025 年 1 月使用 Calendar.JANUARY...DECEMBER 常數Calendar.MONTH 0=一月
DAY_OF_WEEK 1=Sundayif (dow == 7) 認為週六使用 Calendar.SATURDAY 常數1=Sunday, 7=Saturday
Date 建構子棄用new Date(2024, 11, 25)Calendar.set + getTime() 或 LocalDate.ofJDK 1.1 起棄用,行為易誤解
時區預設為 JVM 環境跨時區部署時間不一致明確設定 cal.setTimeZone(TimeZone.getTimeZone("UTC"))生產環境必須統一時區
格式化/解析模式不嚴謹"yyyy-MM-dd" 解析 "2024/12/25" 拋異常輸入預處理或多模式嘗試SimpleDateFormat 預設嚴格模式

JDK 8+ java.time 遷移速查表

新專案請直接使用 java.time,以下為對應關係:

舊 API新 API (java.time)說明
DateInstant時間戳(納秒精度)
CalendarZonedDateTime / LocalDateTime帶時區/無時區日期時間
DateFormatDateTimeFormatter不可變、執行緒安全、支援 ISO 標準
TimeZoneZoneId / ZoneOffset時區 ID / 固定偏移
Date.toInstant()Instant.ofEpochMilli(ts)互轉橋樑
cal.toInstant()ZonedDateTime.ofInstant(instant, zone)Calendar → 新類型

新 API 範例

// 當前時間(系統預設時區)
LocalDateTime now = LocalDateTime.now();
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Taipei"));

// 格式化(執行緒安全,可 static final)
static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String str = now.format(FMT);

// 解析
LocalDate date = LocalDate.parse("2024-12-25");
LocalDateTime dt = LocalDateTime.parse("2024-12-25T12:00:00");

// 日期運算(不可變,鏈式呼叫)
LocalDate nextWeek = date.plusWeeks(1).minusDays(2);

// 期間計算
Period p = Period.between(LocalDate.now(), LocalDate.of(2025, 1, 1));
System.out.println(p.getYears() + "年" + p.getMonths() + "月" + p.getDays() + "日");

總結建議

  1. 維護舊代碼:熟練掌握 Calendar + SimpleDateFormat(ThreadLocal) 組合,嚴守時區與執行緒安全。
  2. 新專案/重構:全面拥抱 java.time,享受不可變、流暢 API、標準化格式帶來的生產力與穩定性。
  3. 資料庫交互:JDBC 4.2+ 直接支援 LocalDate/LocalDateTime/Instant(PreparedStatement.setObject())。
  4. 序列化/JSON:Jackson/Gson 內建支援 java.time,配置 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS = false 輸出 ISO 字串。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved