SimpleDateFormat 多格式兼容解析原始碼提供了一個實用的工具類,嘗試按順序嘗試多種日期格式解析字串,解析成功即返回,失敗則嘗試下一個格式。這在對接多系統、用戶輸入格式不固定的場景很常見。
package cn.zwq.convert;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateConvert {
// 1. 靜態常量:預定義支援的輸入格式(按優先級排序)
private static final String[] INPUT_PATTERNS = {
"yyyy-MM-dd", // 標準 ISO 日期
"yyyyMMdd", // 緊湊格式
"yyyy.MM.dd", // 點分隔
"yyyy/MM/dd", // 斜線分隔
"yyyy年MM月dd日" // 中文格式
};
// 2. 輸出格式統一為中文長格式
private static final String OUTPUT_PATTERN = "yyyy年MM月dd日";
// 3. 核心解析方法:順序嘗試,成功即返回
public static Date stringToDate(String source) {
if (source == null || source.trim().isEmpty()) {
return null;
}
String trimmed = source.trim();
for (String pattern : INPUT_PATTERNS) {
try {
// 注意:SimpleDateFormat 非執行緒安全,此處每次 new 實例
return new SimpleDateFormat(pattern).parse(trimmed);
} catch (ParseException ignored) {
// 忽略,嘗試下一個格式
}
}
return null; // 所有格式均失敗
}
// 4. 格式化輸出
public static String dateToString(Date date) {
if (date == null) return null;
return new SimpleDateFormat(OUTPUT_PATTERN).format(date);
}
// 測試主方法
public static void main(String[] args) {
String[] tests = {"1979-01-01", "19790101", "1979.01.01", "1979/01/01", "1979年01月01日"};
for (String s : tests) {
Date d = stringToDate(s);
System.out.println(s + " -> " + dateToString(d));
}
}
}
SimpleDateFormat 非執行緒安全SimpleDateFormat 實例會導致併發解析/格式化錯誤(數據錯亂、拋異常)。new SimpleDateFormat() 雖安全但效能較差(頻繁創建對象、編譯模式)。java.time API 完整替代方案強烈建議專案遷移至 java.time(JSR-310):不可變、執行緒安全、API 豐富、效能更優。
舊 API (java.util/java.sql) | 新 API (java.time) | 說明 |
|---|---|---|
Date | Instant / LocalDateTime / ZonedDateTime | 時間線上的瞬間 / 無時區日期時間 / 有時區日期時間 |
Calendar | LocalDate / LocalTime / LocalDateTime | 日期 / 時間 / 日期時間 |
SimpleDateFormat | DateTimeFormatter | 執行緒安全、不可變、可預編譯常量 |
TimeZone | ZoneId / ZoneOffset | 時區規則 / 固定偏移量 |
package cn.zwq.convert;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.Locale;
public class ModernDateConvert {
// 1. 預編譯 Formatter 常量(執行緒安全、零開銷共享)
private static final DateTimeFormatter[] INPUT_FORMATTERS = Arrays.stream(new String[]{
"yyyy-MM-dd",
"yyyyMMdd",
"yyyy.MM.dd",
"yyyy/MM/dd",
"yyyy年MM月dd日"
}).map(pattern -> DateTimeFormatter.ofPattern(pattern, Locale.CHINA))
.toArray(DateTimeFormatter[]::new);
private static final DateTimeFormatter OUTPUT_FORMATTER =
DateTimeFormatter.ofPattern("yyyy年MM月dd日", Locale.CHINA);
// 2. 解析為 LocalDate(無時區、不可變、推薦用於業務邏輯)
public static LocalDate parseToLocalDate(String source) {
if (source == null || source.trim().isEmpty()) return null;
String trimmed = source.trim();
for (DateTimeFormatter fmt : INPUT_FORMATTERS) {
try {
return LocalDate.parse(trimmed, fmt);
} catch (DateTimeParseException ignored) {}
}
return null;
}
// 3. 兼容舊 API:轉換為 java.util.Date(如需對接舊庫)
public static java.util.Date parseToDate(String source) {
LocalDate ld = parseToLocalDate(source);
return ld == null ? null : java.util.Date.from(ld.atStartOfDay(java.time.ZoneId.systemDefault()).toInstant());
}
// 4. 格式化輸出
public static String format(LocalDate date) {
return date == null ? null : OUTPUT_FORMATTER.format(date);
}
public static String format(java.util.Date date) {
if (date == null) return null;
return OUTPUT_FORMATTER.format(date.toInstant()
.atZone(java.time.ZoneId.systemDefault())
.toLocalDate());
}
// 測試
public static void main(String[] args) {
String[] tests = {"1979-01-01", "19790101", "1979.01.01", "1979/01/01", "1979年01月01日", "invalid"};
for (String s : tests) {
LocalDate ld = parseToLocalDate(s);
System.out.printf("%-20s -> %s%n", s, ld == null ? "解析失敗" : format(ld));
}
}
}
DateTimeFormatterBuilder)// 使用可選區段 [] 合併多種分隔符模式
DateTimeFormatter flexible = new DateTimeFormatterBuilder()
.appendPattern("yyyy")
.optionalStart().appendPattern("[-[.][/][年]]").optionalEnd() // 年後分隔符可選
.appendPattern("MM")
.optionalStart().appendPattern("[-[.][/][月]]").optionalEnd() // 月後分隔符可選
.appendPattern("dd")
.optionalStart().appendPattern("[日]").optionalEnd() // 可選「日」
.toFormatter(Locale.CHINA);
// 可一次解析 "2023-10-15", "2023.10.15", "2023/10/15", "2023年10月15日", "20231015"
LocalDate date = LocalDate.parse("2023年10月15日", flexible); // 成功
java.time,停止引入 SimpleDateFormat、Calendar、Date(除非對接強制要求的舊 API)。static final 常量,全局共享,零同步開銷。LocalDate/LocalDateTime,邊界層(Controller、DAO、序列化)做轉換。LocalDate/LocalDateTime(需啟用對應 TypeHandler/Converter)。java.time,無需額外模組;舊版本加 jackson-datatype-jsr310 並註冊 JavaTimeModule。ZoneId,避免依賴 JVM 預設時區導致跨環境不一致。原始工具類邏輯清晰、實用性強,但受限於 SimpleDateFormat 非執行緒安全與效能短板。遷移至 Java 8 java.time API 可一舉解決安全、效能、API 易用性三大痛點,並獲得不可變時間物件帶來的領域建模優勢。本文提供的 ModernDateConvert 可直接作為專案通用工具類投入使用,並展示了 DateTimeFormatterBuilder 合併模式的進階技巧,滿足更複雜的兼容性需求。