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

Java 日期時間 API 完全指南:java.util.Date、sql.Date、Timestamp 與格式化轉換實戰

     編輯:Java 相關知識

從 JDBC 時代的 java.sql.Date 到 JSR-310 (java.time) 現代 API,Java 日期時間處理經歷了漫長演進。本文系統梳理舊版核心類別 (Date, Calendar, Timestamp, SimpleDateFormat) 的關係、轉換陷阱、格式化模式,並給出遷移至 java.time 的最佳實踐,解決「插入資料庫時間卻只有日期」、「字串解析拋異常」等經典痛點。

一、核心類別關係圖與職責

java.util.Date (毫秒精度、含日期時間、可變、非線程安全)
    ↑ extends
java.sql.Date (僅保留日期部分、toString() 格式 yyyy-MM-dd、JDBC DATE 對應)
    ↑ extends
java.sql.Timestamp (納秒精度、含日期時間、JDBC TIMESTAMP 對應)

關鍵:sql.Date 與 Timestamp 皆繼承 util.Date,但 sql.Date 強制將時分秒歸零 (標準化至 00:00:00),導致若直接用 new java.sql.Date(System.currentTimeMillis()) 插入資料庫,時間資訊會丟失。

二、常見建構與轉換模式 (舊版 API)

1. 取得當前時間

long nowMs = System.currentTimeMillis();           // 毫秒時間戳
java.util.Date utilNow = new java.util.Date();     // 完整日期時間
java.sql.Date sqlDate = new java.sql.Date(nowMs);  // 僅日期 (時分秒歸零)
java.sql.Timestamp ts = new Timestamp(nowMs);      // 日期時間 + 納秒

2. 相互轉換

來源目標代碼注意
util.Datesql.Datenew java.sql.Date(utilDate.getTime())時間部分會被截斷
util.DateTimestampnew Timestamp(utilDate.getTime())保留毫秒,納秒補 0
sql.DateTimestampnew Timestamp(sqlDate.getTime())時間為 00:00:00.0
Timestamputil.Datenew java.util.Date(ts.getTime())納秒精度損失
Stringutil.Datesdf.parse(str)拋 ParseException,格式必須完全匹配
Stringsql.Datenew java.sql.Date(sdf.parse(str).getTime())不可直接轉型 (java.sql.Date)sdf.parse()
util.DateStringsdf.format(utilDate)SimpleDateFormat 非線程安全

3. 完整可執行範例

import java.sql.Timestamp;
import java.text.*;
import java.util.Date;

public class LegacyDateTimeDemo {
    public static void main(String[] args) throws ParseException {
        long now = System.currentTimeMillis(); // 1244711626453
        
        Date utilDate = new Date();                    // Thu Jun 11 16:27:57 CST 2009
        java.sql.Date sqlDate = new java.sql.Date(now); // 2009-06-11
        Timestamp ts = new Timestamp(now);             // 2009-06-11 16:27:57.75
        
        // 格式化
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        String formatted = sdf.format(ts);             // 2009/06/11 16:27:57
        
        // 解析
        Date parsed = sdf.parse("2009/12/10 05:12:02"); // Thu Dec 10 05:12:02 CST 2009
        java.sql.Date parsedSql = new java.sql.Date(parsed.getTime()); // 2009-12-10
        Timestamp parsedTs = new Timestamp(parsedSql.getTime()); // 2009-12-10 05:12:02.0
        
        System.out.println("utilDate: " + utilDate);
        System.out.println("sqlDate: " + sqlDate);
        System.out.println("timestamp: " + ts);
        System.out.println("formatted: " + formatted);
        System.out.println("parsedSql: " + parsedSql);
        System.out.println("parsedTs: " + parsedTs);
    }
}

三、SimpleDateFormat 模式字元完整表

符號含義範例
y年1996, 96
M月July, Jul, 07
d日10
H時 (0-23)0
h時 (1-12)12
m分30
s秒55
S毫秒978
E星期Tuesday, Tue
aAM/PMPM
z時區名稱Pacific Standard Time
ZRFC 822 時區-0800

線程安全警告:SimpleDateFormat 非線程安全,多執行緒共用實例會導致解析錯誤或崩潰。解決方案:

  • 方法區域變數建立 (每次新建)
  • ThreadLocal<SimpleDateFormat> 快取
  • 改用 java.time.format.DateTimeFormatter (不可變、線程安全)

四、JDBC 實戰:PreparedStatement 設定時間參數

// 錯誤:stmt.setDate(1, new java.util.Date()) // 類型不匹配
// 錯誤:stmt.setDate(1, new java.sql.Date(new Date().getTime())) // 時間歸零!

// 正確:插入 TIMESTAMP (含時間)
PreparedStatement ps = conn.prepareStatement("INSERT INTO events(event_time) VALUES(?)");
ps.setTimestamp(1, new Timestamp(System.currentTimeMillis()));
ps.executeUpdate();

// 正確:插入 DATE (僅日期)
ps.setDate(1, new java.sql.Date(System.currentTimeMillis()));

Oracle 特例:DATE 型別含時間,若用 setDate 會截斷時間,必須用 setTimestamp 或 oracle.sql.DATE。

五、現代化遷移:java.time (JSR-310, JDK 8+)

舊版 API 設計缺陷:可變、非線程安全、月份 0-based、無時區概念、API 不一致。java.time 全面修正:

舊版新版 (java.time)特性
java.util.DateInstant (時間戳)、LocalDateTime (本地日期時間)不可變、納秒精度
java.sql.DateLocalDate僅日期
java.sql.TimestampLocalDateTime / OffsetDateTime含時區偏移
CalendarZonedDateTime完整時區規則
SimpleDateFormatDateTimeFormatter不可變、線程安全、預定義常量

遷移代碼對照

// 當前時間
LocalDateTime now = LocalDateTime.now();              // 2024-01-15T14:30:45.123
Instant instant = Instant.now();                      // 2024-01-15T06:30:45.123Z (UTC)

// 格式化 (線程安全)
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
String str = now.format(fmt);                         // 2024/01/15 14:30:45

// 解析
LocalDateTime parsed = LocalDateTime.parse(str, fmt);

// 與舊版互轉 (JDBC 4.2+ 驅動支援直接 setObject/getObject)
java.sql.Timestamp legacyTs = Timestamp.valueOf(now); // LocalDateTime → Timestamp
LocalDateTime modern = legacyTs.toLocalDateTime();    // Timestamp → LocalDateTime

// 資料庫操作 (JDBC 4.2+)
ps.setObject(1, now);           // 直接寫入 TIMESTAMP
LocalDateTime dbTime = rs.getObject(1, LocalDateTime.class); // 直接讀取

六、遷移清單與最佳實踐

  1. 新專案:全面使用 java.time,拒絕 java.util.Date/Calendar。
  2. 舊專案:邊界層 (Controller/DAO) 做雙向轉換,核心領域模型逐步替換為 LocalDateTime 等。
  3. 資料庫:升級 JDBC 驅動至 4.2+ (支援 setObject/getObject 直接綁定 java.time 類型)。
  4. 序列化:Jackson 2.10+ 預設支援 java.time 模組 (JavaTimeModule),日期格式統一 ISO-8601。
  5. 時區:系統預設時區改用 ZoneId.systemDefault(),業務邏輯顯式指定 ZoneId.of("Asia/Taipei")。

總結

理解 java.util.Date、java.sql.Date、Timestamp 繼承關係與 SimpleDateFormat 線程不安全本質,是修復舊系統時間 Bug 的鑰匙。但長遠來看,全面遷移至 java.time 才是根治之道:不可變物件消除併發隱患、清晰的類別職責消除語義混淆、標準化格式化/解析 API 提升開發體驗。JDK 8 已發布十年,java.time 早已生產就緒,請立即啟動遷移計畫。

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