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

Java Date 與 Calendar 深度解析:從基礎用法到線程安全的現代替代方案

     編輯:Java 程式設計入門

java.util.Date 與 java.util.Calendar 是 Java 早期日期時間 API,雖設計缺陷眾多 (可變性、月份 0 基、線程不安全、時區處理混亂),但在存量代碼、舊系統維護、特定框架整合中仍廣泛存在。本文系統梳理核心用法、格式化陷阱、Calendar 操作模式,並引導遷移至 java.time (JSR-310) 現代 API。

Date 核心用法與陷阱

獲取當前時間

Date now = new Date(); // 當前毫秒時間戳
long timestamp = now.getTime(); // 自 1970-01-01 00:00:00 UTC 毫秒數
System.out.println(timestamp); // 1700000000000
System.out.println(now); // toString(): "Mon Nov 14 10:30:00 CST 2023" (依賴默認 Locale/時區)

格式化輸出:SimpleDateFormat (非線程安全!)

Date date = new Date();

// 用戶自定義模式
SimpleDateFormat sdf1 = new SimpleDateFormat("EEEE, MMMM dd, yyyy HH:mm:ss z", Locale.US);
System.out.println(sdf1.format(date)); // "Tuesday, November 14, 2023 10:30:00 CST"

// 標準風格 (Locale 敏感)
DateFormat df = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.SHORT, Locale.CHINA);
System.out.println(df.format(date)); // "2023年11月14日 星期二 上午10:30"

// ⚠️ 致命陷阱:SimpleDateFormat 非線程安全!
// 錯誤:static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd"); // 多線程並發會亂碼/拋異常
// 正確:ThreadLocal 或 每次 new / 使用 DateTimeFormatter (java.time)

Calendar 核心操作模式

Calendar 為抽象類,常用 GregorianCalendar 實現。

獲取與設置字段

Calendar cal = Calendar.getInstance(); // 默認時區、Locale
cal.setTime(new Date()); // 從 Date 同步

// 獲取字段
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH); // 0=1月, 11=12月 ⚠️
int day = cal.get(Calendar.DAY_OF_MONTH);
int hour = cal.get(Calendar.HOUR_OF_DAY); // 24制
int dow = cal.get(Calendar.DAY_OF_WEEK); // 1=周日, 7=周六

// 設置字段 (會重新計算時間戳)
cal.set(Calendar.YEAR, 2025);
cal.set(Calendar.MONTH, Calendar.JANUARY); // 常量可讀
cal.set(2025, Calendar.FEBRUARY, 14, 18, 30, 0); // 批量設置

// 日期運算
cal.add(Calendar.DAY_OF_MONTH, 7); // 一週後
cal.add(Calendar.MONTH, -3); // 三個月前
cal.roll(Calendar.DAY_OF_MONTH, 10); // 滾動:不改變更大字段 (月/年)

常用模式:本週五、月底、下個月第一天

// 本週五
cal.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY);

// 本月最後一天
cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));

// 下個月第一天 00:00:00
cal.add(Calendar.MONTH, 1);
cal.set(Calendar.DAY_OF_MONTH, 1);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);

DateFormat 格式化風格常量

常量日期示例 (zh_CN)時間示例
SHORT23/11/1410:30
MEDIUM2023/11/1410:30:00
LONG2023年11月14日10:30:00 CST
FULL2023年11月14日 星期二上午10時30分00秒 CST

核心設計缺陷總結

缺陷DateCalendar影響
可變性✅ (setTime)✅ (set/add/roll)共享實例導致數據竄改、難以推理
月份 0 基❌✅ (JANUARY=0)極易 Off-by-one 錯誤
年份偏移 1900✅ (getYear() 返回 year-1900)❌直接輸出誤導
線程安全❌❌多線程環境不可共享實例
時區處理模糊 (toString 用默認)單一時區實例跨時區業務極其痛苦
API 一致性殘缺 (多方法棄用)膨脹、命名不一學習成本高、易誤用

現代替代:java.time (JSR-310, Java 8+)

不可變、線程安全、領域驅動設計、ISO-8601 原生支援

核心類對應

舊 API新 API說明
DateInstant時間線上的瞬間 (UTC,納秒精度)
CalendarZonedDateTime / LocalDateTime帶時區/不帶時區的日期時間
Date/Calendar (僅日期)LocalDate年月日,無時間、無時區
Date/Calendar (僅時間)LocalTime時分秒納秒,無日期、無時區
SimpleDateFormatDateTimeFormatter線程安全、預定義格式、解析嚴格
TimeZoneZoneId / ZoneOffsetIANA 時區庫 (America/New_York)

遷移代碼對照

// 當前時間
Instant now = Instant.now(); // UTC
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
LocalDateTime ldt = LocalDateTime.now();
LocalDate today = LocalDate.now();

// 格式化 (線程安全,可 static final)
static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = zdt.format(FMT);

// 解析
LocalDate parsed = LocalDate.parse("2023-11-14", DateTimeFormatter.ISO_LOCAL_DATE);

// 日期運算 (不可變,返回新實例)
LocalDate nextWeek = today.plusWeeks(1);
LocalDate firstDayOfMonth = today.withDayOfMonth(1);
LocalDate lastDayOfMonth = today.with(TemporalAdjusters.lastDayOfMonth());
ZonedDateTime meeting = zdt.withHour(18).withMinute(30).withSecond(0);

// 時區轉換
ZonedDateTime nyTime = zdt.withZoneSameInstant(ZoneId.of("America/New_York"));

// 舊新互轉
Date oldDate = Date.from(instant);
Instant instant = oldDate.toInstant();
GregorianCalendar cal = GregorianCalendar.from(zdt);
ZonedDateTime zdt2 = cal.toZonedDateTime();

遷移策略建議

  1. 新代碼全面使用 java.time:零理由繼續用舊 API
  2. 邊界轉換層:DAO/外部介面接收 Instant/LocalDateTime,內部轉換
  3. 資料庫驅動支援:JDBC 4.2+ 直接 getObject(rs, Instant.class)、setObject(ps, instant)
  4. 序列化:Jackson JavaTimeModule、Protobuf google.protobuf.Timestamp
  5. 漸進式替換舊代碼:工具類 DateUtils.toInstant(date)、DateUtils.toLocalDate(cal)

總結

Date/Calendar 是 Java 早期妥協產物,可變性、月份 0 基、線程不安全、時區模糊 四大原罪導致無數 Bug。java.time (JSR-310) 以不可變、領域建模、線程安全、ISO 標準 徹底解決這些問題。維護舊代碼時理解舊 API 行為邊界,新代碼堅決使用 java.time,邊界處統一轉換,是專業 Java 開發者的必修功課。

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