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

Java 日期時間處理大全:Date、Calendar、SimpleDateFormat 與線程安全最佳實踐

     編輯:Java 相關知識

核心類別體系

類別包特點用途
Datejava.util毫秒時間戳封裝,大多方法已棄用時間點傳遞、資料庫交互、序列化
Calendarjava.util抽象基類,GregorianCalendar 實現日期運算、欄位獲取/設置、國際化
DateFormatjava.text抽象格式化基類日期↔字串轉換
SimpleDateFormatjava.text具體實現,**非線程安全**自定義模式格式化/解析

Date 基礎操作

// 當前時間
Date now = new Date();                 // 相當於 new Date(System.currentTimeMillis())
long timestamp = now.getTime();        // 毫秒時間戳
Date specific = new Date(timestamp);   // 時間戳還原

// 日期運算 (毫秒級)
long oneDay = 24 * 60 * 60 * 1000L;
Date tomorrow = new Date(now.getTime() + oneDay);
Date yesterday = new Date(now.getTime() - oneDay);

// 兩日期差天數
long diffDays = (date1.getTime() - date2.getTime()) / oneDay;

Calendar 日期運算 (推薦方式)

Calendar cal = Calendar.getInstance(); // 當前時間,默認 Locale/TimeZone
cal.set(2023, Calendar.NOVEMBER, 15);  // 月份 0-based: JANUARY=0
cal.add(Calendar.DAY_OF_MONTH, 10);    // 加 10 天
cal.add(Calendar.HOUR_OF_DAY, -3);     // 減 3 小時
cal.set(Calendar.MINUTE, 30);          // 設定分鐘
Date result = cal.getTime();           // 轉回 Date

// 獲取欄位
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH) + 1; // 轉為 1-based
int day = cal.get(Calendar.DAY_OF_MONTH);
int weekDay = cal.get(Calendar.DAY_OF_WEEK); // SUNDAY=1

SimpleDateFormat 格式化與解析

// 常用模式字母
// y=年 M=月 d=日 H=時(24) m=分 s=秒 S=毫秒 E=星期 a=AM/PM z=時區
String pattern = "yyyy-MM-dd HH:mm:ss";
SimpleDateFormat sdf = new SimpleDateFormat(pattern);

// 格式化 Date → String
String formatted = sdf.format(new Date()); // "2023-11-15 14:30:05"

// 解析 String → Date
Date parsed = sdf.parse("2023-11-15 14:30:05"); // 拋 ParseException

// 解析特定模式 (如只有年月周)
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM w E");
Date d = sdf2.parse("2023-11 3 星期三");

⚠️ 線程安全陷阱與解決方案

SimpleDateFormat 非線程安全!** 多線程共用實例會導致解析錯誤、數據競爭甚至死循環。

方案 1:ThreadLocal (高併發推薦)

public class DateUtils {
    private static final ThreadLocal SDF_YYYYMMDD =
        ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

    public static String format(Date date) {
        return SDF_YYYYMMDD.get().format(date);
    }
    public static Date parse(String str) throws ParseException {
        return SDF_YYYYMMDD.get().parse(str);
    }
}

方案 2:局部變數 (低併發/請求範圍)

// 每次使用 new 實例,無共享風險
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(date);

方案 3:Java 8+ java.time (根治方案)


// 線程安全、不可變、API 清晰
LocalDate date = LocalDate.now();
LocalDateTime ldt = LocalDateTime.now();
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String str = ldt.format(fmt);
LocalDate parsed = LocalDate.parse("2023-11-15", fmt);

// 期間運算
LocalDate nextWeek = date.plusWeeks(1);
Period p = Period.between(date, nextWeek); // P1W

實用工具方法集錦

// 1. 獲取本月第一天/最後一天
Calendar cal = Calendar.getInstance();
cal.set(Calendar.DAY_OF_MONTH, 1); // 第一天
cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH)); // 最後一天

// 2. 判斷閏年
boolean leap = cal.getActualMaximum(Calendar.DAY_OF_YEAR) > 365;

// 3. 日期區間迭代
for (Date d = start; !d.after(end); d = addDays(d, 1)) { ... }

// 4. 時間戳轉換 (Java 8)
Instant instant = Instant.now();
long ms = instant.toEpochMilli();
Instant instant2 = Instant.ofEpochMilli(ms);

時區處理重點

  • `Date` 本身無時區概念,`toString()` 使用 JVM 默認時區顯示。
  • `Calendar` / `SimpleDateFormat` 可 `setTimeZone(TimeZone.getTimeZone("UTC"))`。
  • 資料庫儲存建議統一 **UTC** (`TIMESTAMP WITH TIME ZONE` 或 `BIGINT` 毫秒)。
  • 前端顯示時再轉換為用戶時區 (`Intl.DateTimeFormat` / `moment.tz` / `ZonedDateTime`)。

遷移建議:全面擁抱 java.time (JSR-310)

舊類新類 (java.time)優勢
DateInstant / LocalDateTime不可變、納秒精度、清晰語義
CalendarZonedDateTime / LocalDate線程安全、流式 API、時區分離
SimpleDateFormatDateTimeFormatter線程安全、預編譯、支援解析嚴格模式
TimeZoneZoneId / ZoneOffsetIANA 時區數據庫、歷史規則支援

Java 8+ 專案**強制禁用** `SimpleDateFormat` 共享實例,新代碼優先使用 `java.time`;舊代碼逐步重構或用 `ThreadLocal` 過渡。

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