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

Java 日期時間處理大全:Calendar 經典用法、Java 8+ 新 API 遷移指南與常見坑點

     編輯:Java 相關知識

日期時間處理是業務開發高頻需求。本文系統整理 java.util.Calendar 經典用法 (維護舊代碼必備)、java.time 現代 API (新專案首選)、格式化/解析、運算、邊界計算、時區處理等核心場景,並列出避坑清單。

一、Calendar 經典用法 (Java 7 及之前)

1. 取得實例與格式化輸出

Calendar cal = Calendar.getInstance(); // 預設時區、Locale
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("Now: " + sdf.format(cal.getTime()));

2. 日期運算:add / roll

cal.add(Calendar.DAY_OF_MONTH, -1);  // 昨天 (會改變月份/年份)
cal.add(Calendar.MONTH, 3);          // 3個月後
cal.roll(Calendar.DAY_OF_MONTH, 10); // 只滾動天,不改變月/年

3. 取得各欄位值

int year  = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH) + 1; // 0-based!
int day   = cal.get(Calendar.DAY_OF_MONTH);
int hour  = cal.get(Calendar.HOUR_OF_DAY); // 24制
int min   = cal.get(Calendar.MINUTE);
int sec   = cal.get(Calendar.SECOND);

4. 邊界計算

// 本月最大天數
int maxDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH);

// 本月第一天 00:00:00
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);

// 本月最後一天 23:59:59.999
cal.set(Calendar.DAY_OF_MONTH, maxDay);
cal.set(Calendar.HOUR_OF_DAY, 23);
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
cal.set(Calendar.MILLISECOND, 999);

5. 兩日期天數差 (含時區陷阱)

public long daysBetween(Date d1, Date d2) {
  Calendar c1 = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
  Calendar c2 = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
  c1.setTime(d1); c2.setTime(d2);
  // 歸零時分秒毫秒,比較自然日
  for (Calendar c : Arrays.asList(c1, c2)) {
    c.set(Calendar.HOUR_OF_DAY, 0);
    c.set(Calendar.MINUTE, 0);
    c.set(Calendar.SECOND, 0);
    c.set(Calendar.MILLISECOND, 0);
  }
  return (c2.getTimeInMillis() - c1.getTimeInMillis()) / (1000*60*60*24);
}

二、Java 8+ java.time 現代 API (強烈推薦)

核心類別對照

舊類別新類別說明
DateInstant時間戳 (UTC)
CalendarZonedDateTime帶時區完整日期時間
無LocalDateTime無時區日期時間 (常用)
無LocalDate / LocalTime純日期/純時間
SimpleDateFormatDateTimeFormatter線程安全、不可變
Calendar.addplusDays/minusMonths流式 API
getTimeInMillisChronoUnit.between精確差值計算

常用操作範例

// 1. 當前時間
LocalDateTime now = LocalDateTime.now();
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Taipei"));

// 2. 格式化/解析
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String str = now.format(fmt);
LocalDateTime parsed = LocalDateTime.parse("2024-01-15 10:30:00", fmt);

// 3. 運算
LocalDate tomorrow = LocalDate.now().plusDays(1);
LocalDate lastMonth = LocalDate.now().minusMonths(1);
LocalDateTime nextHour = LocalDateTime.now().plusHours(1);

// 4. 自然日差 (自動處理夏令時)
long days = ChronoUnit.DAYS.between(LocalDate.now(), LocalDate.now().plusWeeks(2));

// 5. 邊界
LocalDate firstDay = LocalDate.now().withDayOfMonth(1);
LocalDate lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());

// 6. 星期判斷
DayOfWeek dow = LocalDate.now().getDayOfWeek(); // MONDAY..SUNDAY

三、常見坑點避坑指南

  1. SimpleDateFormat 非線程安全:多執行緒環境必須用 ThreadLocal 或每次新建,或改用 DateTimeFormatter (不可變、線程安全)。
  2. Calendar 月份 0-based:JANUARY=0,取值時 +1,設值時 -1。
  3. 時區預設陷阱:Calendar.getInstance() 用 JVM 時區,跨地域部署必須顯式指定 TimeZone.getTimeZone("UTC") 或 ZoneId.of("Asia/Taipei")。
  4. 夏令時導致 23/25 小時天:自然日差請歸零時分秒後用 UTC 計算,或直接用 LocalDate/ChronoUnit.DAYS。
  5. Date 构造函數棄用:new Date(year, month, day) 年份為 1900-based、月份 0-based,極易出錯,一律用 Calendar 或 LocalDate.of。
  6. 序列化傳輸:傳輸層建議用 ISO-8601 字串 (2024-01-15T10:30:00+08:00) 或 Instant.toEpochMilli() 長整數,避免時區歧義。

四、遷移建議

  • 新專案:全面使用 java.time,拒絕 Date/Calendar/SimpleDateFormat。
  • 舊專案:邊緣新增代碼用 java.time,內部轉換:Date.from(instant)、date.toInstant()、LocalDateTime.ofInstant(instant, zone)。
  • 資料庫層:MyBatis/JPA 2.1+ 直接支援 LocalDateTime、LocalDate、Instant 映射。

總結

Calendar 以 add/get/set 完成萬年曆邏輯,但月份 0-based、非線程安全、時區隱性依賴等坑點眾多。Java 8 java.time 以不可變、線程安全、領域驅動設計 (LocalDate/LocalTime/Instant/ZonedDateTime) 徹底解決歷史包袱。**新代碼零成本遷移,舊代碼邊際替換**,是目前最穩健策略。

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