Java 日期解析三大工具對比:ISO8601 格式完美處理指南
測試場景
三種常見日期字符串格式:
2016-06-07T14:08:09.235+08:00 — 標準 ISO8601 (含毫秒、時區)
2016-06-07 14:08:09 — 無毫秒、無時區
2016-06-07 14:08:09.235 — 含毫秒、無時區
依賴坐標
<!-- Joda-Time (Java 8 前標準) -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.12.7</version>
</dependency>
<!-- Apache Commons Lang3 (含 DateUtils) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
核心代碼對比
import java.util.Date;
import java.text.SimpleDateFormat;
import org.apache.commons.lang3.time.DateUtils;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
public class DateParseComparison {
public static void main(String[] args) throws Exception {
String iso8601 = "2016-06-07T14:08:09.235+08:00";
String noMillis = "2016-06-07 14:08:09";
String withMillis = "2016-06-07 14:08:09.235";
// ========== 1. Joda-Time ==========
// 默認支持 ISO8601,無需格式串
Date d1 = new DateTime(iso8601).toDate();
// 手動指定格式 (較繁瑣)
Date d2 = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZZ").parseDateTime(iso8601).toDate();
Date d3 = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss").parseDateTime(noMillis).toDate();
Date d4 = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS").parseDateTime(withMillis).toDate();
// ========== 2. JDK SimpleDateFormat ==========
// ⚠️ 關鍵坑:時區模式是 XXX (大寫),非 ZZ!
Date d5 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX").parse(iso8601);
Date d6 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(noMillis);
Date d7 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").parse(withMillis);
// ========== 3. Apache DateUtils ==========
// 多格式嘗試,代碼最簡潔
Date d8 = DateUtils.parseDate(iso8601, "yyyy-MM-dd'T'HH:mm:ss.SSSZZ");
Date d9 = DateUtils.parseDate(noMillis, "yyyy-MM-dd HH:mm:ss");
Date d10 = DateUtils.parseDate(withMillis, "yyyy-MM-dd HH:mm:ss.SSS");
System.out.println("All parsed successfully!");
}
}
關鍵差異總結
| 特性 | SimpleDateFormat | Joda-Time | Apache DateUtils |
| ISO8601 原生支持 | ❌ 需手寫格式 | ✅ new DateTime(str) | ❌ 需手寫格式 |
| 時區模式字母 | XXX (Java 7+) | ZZ | ZZ |
| 線程安全 | ❌ 非線程安全 | ✅ DateTime 不可變 | ✅ 靜態方法無狀態 |
| API 設計 | 繁瑣、易錯 | 豐富、鏈式、領域模型 | 工具類、多格式自動嘗試 |
| 性能 | 基準 | 略慢 (對象創建) | 快 (內部緩存格式化器) |
| 維護狀態 | JDK 內建 | 維護模式 (建議遷移 java.time) | 活躍維護 |
⚠️ SimpleDateFormat 最大坑:XXX vs ZZ
ZZ / Z:輸出 +0800 (無冒號),解析 ISO8601 +08:00 會失敗
XXX:輸出 +08:00 (帶冒號),正確解析 ISO8601 時區
- Java 7+ 支持
XXX,Java 6 需預處理字符串插入冒號
最佳實踐建議
- Java 8+ 項目:直接使用
java.time 包 (Instant.parse, OffsetDateTime.parse, DateTimeFormatter.ISO_OFFSET_DATE_TIME),原生支持 ISO8601、線程安全、不可變
- 舊項目 (Java 6/7):
- 首選 Joda-Time:API 完善、
java.time 設計藍本、遷移平滑
- 輕量場景:Apache DateUtils.parseDate 多格式自動嘗試,代碼極簡
- 必須用 JDK:定義
ThreadLocal<SimpleDateFormat> 保證線程安全,格式串用 XXX
- 序列化框架:Jackson/Gson 默認 ISO8601,配置
DateFormat 時同樣注意 XXX
遷移對照表 (Joda-Time → java.time)
| Joda-Time | java.time (Java 8+) |
DateTime | ZonedDateTime / OffsetDateTime |
LocalDateTime | LocalDateTime |
DateTimeFormat.forPattern | DateTimeFormatter.ofPattern |
new DateTime(str) | OffsetDateTime.parse(str) / Instant.parse(str) |
Period/Duration | Period/Duration |