在Java中使用DateTimeFormatter
并啟用嚴格模式(ResolverStyle.STRICT
)時,解析日期字符串"2025-06-01"
報錯的根本原因是:模式字符串中的年份格式yyyy
被解釋為YearOfEra
(紀元年份),而非Year
(公歷年份)。在嚴格模式下,解析器要求所有字段必須完整且一致,而LocalDate
需要Year
字段(而非YearOfEra
)來構建對象,導致轉換失敗。
解決方案:使用uuuu
代替yyyy
將模式中的年份占位符從yyyy
改為uuuu
,明確指定使用公歷年份(Year
字段):
java
復制
下載
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd").withResolverStyle(ResolverStyle.STRICT); // 嚴格模式
原因詳解
-
字段差異:
-
yyyy
?→ 解析為?YearOfEra
(紀元年份),例如?2025
?表示“公元2025年”。 -
uuuu
?→ 解析為?Year
(公歷年份),直接對應ISO年份體系。
-
-
嚴格模式的要求:
-
在
ResolverStyle.STRICT
下,解析器不會自動補全缺失字段(如紀元信息)。 -
錯誤信息中的
TemporalAccessor
包含:plaintext
復制
下載
{DayOfMonth=1, MonthOfYear=6, YearOfEra=2025}, ISO
缺少
Year
字段,只有YearOfEra
,無法直接構建LocalDate
。
-
-
uuuu
的優勢:-
直接生成
Year
字段,與LocalDate
所需字段一致。 -
避免紀元歧義,尤其處理公元前/后的日期更安全。
-
驗證修復后的代碼
java
復制
下載
import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.ResolverStyle;public class StrictDateParsing {private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd").withResolverStyle(ResolverStyle.STRICT); // 使用 uuuupublic static void main(String[] args) {String dateStr = "2025-06-01";LocalDate date = LocalDate.parse(dateStr, DATE_FORMATTER); // 解析成功System.out.println(date); // 輸出: 2025-06-01} }
關鍵結論
占位符 | 含義 | 嚴格模式下的行為 |
---|---|---|
yyyy | 紀元年份 | 解析為YearOfEra ,導致構建失敗 |
uuuu | 公歷年份 | 解析為Year ,與LocalDate 兼容 |
最佳實踐:
在嚴格模式中始終使用uuuu
表示年份,確保日期字段與LocalDate
的要求完全匹配,避免解析錯誤。
package com.weiyu.utils;import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
import java.util.ArrayList;
import java.util.List;public class DateUtils {// 標準模式// private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd")// 啟用嚴格模式,使用 ResolverStyle.STRICT 嚴格模式,拒絕無效日期(如 2023-02-29),自動處理閏年、月份天數等復雜邏輯// 啟用嚴格模式,ofPattern必設置為"uuuu-MM-dd").withResolverStyle(ResolverStyle.STRICT);/*** 有效檢查* @param dateStr 字符串* @return 提示信息*/public static String isValidDate(String dateStr) {// 1. 空值檢查if (dateStr == null) return "日期字符串不能為null";// 2. 空白檢查if (dateStr.isEmpty()) {return "日期字符串不能為空";}return "";}/*** 轉換為 LocalDate 格式的日期* @param dateStr 字符串* @return LocalDate 格式的日期*/public static LocalDate parseDate(String dateStr) {// 檢查日期字符串格式String msg = isValidDate(dateStr);if (!msg.isEmpty()) {throw new RuntimeException(msg);}try {// 解析日期return LocalDate.parse(dateStr, DATE_FORMATTER);} catch (DateTimeParseException e) {// 格式錯誤處理throw new IllegalArgumentException("日期格式錯誤,應為 yyyy-MM-dd,并且為有效日期", e);}}/*** 轉換為開始時間(當天的開始時刻 00:00:00)* @param dateStr 字符串* @return LocalDateTime 格式的日期時間*/public static LocalDateTime parseBeginDateTime(String dateStr) {return parseDate(dateStr).atStartOfDay();}/*** 轉換為結束時間(當天的最后一刻 23:59:59.999999999)* @param dateStr 字符串* @return LocalDateTime 格式的日期時間*/public static LocalDateTime parseEndDateTime(String dateStr) {return parseDate(dateStr).atTime(LocalTime.MAX);}/*** 將字符數組轉換為日期數組,如:["2025-06-01", "2026-06-10"] 轉換為 [beginDate, endDate]* @param dateStrList 字符數組* @return 日期數組,只限開始日期和結束日期,[beginDate, endDate]*/public static List<LocalDate> parseDateRange(List<String> dateStrList) {List<LocalDate> dateList = new ArrayList<>();if (dateStrList == null || dateStrList.size() != 2) {dateList.add(LocalDate.now());dateList.add(LocalDate.now());} else {try {dateList.add(parseDate(dateStrList.get(0)));dateList.add(parseDate(dateStrList.get(1)));} catch (DateTimeParseException e) {dateList.add(LocalDate.now());dateList.add(LocalDate.now());}}// 檢查日期邏輯關系,開始時間大于結束時間if (dateList.get(0).isAfter(dateList.get(1))) {LocalDate maxDate = dateList.get(0);dateList.set(0, dateList.get(1));dateList.set(1,maxDate);}return dateList;}/*** 將字符數組轉換為日期數組,如:["2025-06-01", "2026-06-10"] 轉換為 [beginDateTime, endDateTime]* @param dateStrList 字符數組* @return 時間數組,只限開始時間和結束時間,[beginDateTime, endDateTime]*/public static List<LocalDateTime> parseDateTimeRange(List<String> dateStrList) {List<LocalDate> dateList = parseDateRange(dateStrList);List<LocalDateTime> dateTimeList = new ArrayList<>();dateTimeList.add(dateList.get(0).atStartOfDay());dateTimeList.add(dateList.get(1).atTime(LocalTime.MAX));return dateTimeList;}
}