Java 日期字符串万能解析工具类(支持多种日期格式智能转换)
在日常开发中,经常遇到 不同格式的日期字符串,例如:
2025-09-08
2025/09/08
25-09-08
09/08/2025
20250908
2025年09月08日
如果我们在业务中需要统一转成 LocalDateTime
,再存入数据库,就不得不写一堆 DateTimeFormatter
去 try-catch,代码既冗余又不优雅。
为了解决这个问题,我写了一个工具类 DateStringSwitchUtils,它可以自动识别各种常见日期格式,并转换为 LocalDateTime
。(结尾附完整代码)
一、设计思路
多格式支持
通过正则Pattern
结合DateTimeFormatter
,支持常见的中/美/欧日期写法。灵活解析
对一些 格式不严格的输入(比如2025.9.8
、25/9/8
),尝试灵活匹配。智能识别
对于250908
、20250908
这种无分隔符日期,自动识别年月日。统一输出
最终都转换为LocalDateTime
,默认时分秒为00:00:00
,并提供方法输出 SQL 标准格式。
二、核心实现
核心是一个 Map<Pattern, Function<String, LocalDateTime>>
,将 正则匹配规则 与 转换方法 绑定:
// 注册 yyyy-MM-dd 格式
registerPattern("^\\d{4}-\\d{2}-\\d{2}$",
s -> parseWithFormat(s, "yyyy-MM-dd"));
// 注册 yyyy/MM/dd 格式
registerPattern("^\\d{4}/\\d{2}/\\d{2}$",
s -> parseWithFormat(s, "yyyy/MM/dd"));
// 注册 yyyy年MM月dd日
registerPattern("^\\d{4}年\\d{2}月\\d{2}日$",
s -> parseWithFormat(s, "yyyy年M月d日"));
当调用 parse(String dateString)
时,会自动匹配正则并执行对应转换器。
1. 基础解析(常见格式)
利用 DateTimeFormatterBuilder
保证解析的健壮性:
private LocalDateTime parseWithFormat(String dateString, String pattern) {DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendPattern(pattern).parseDefaulting(ChronoField.HOUR_OF_DAY, 0).parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0).parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0).toFormatter().withResolverStyle(ResolverStyle.SMART);return LocalDate.parse(dateString, formatter).atTime(LocalTime.MIN);
}
2. 灵活解析(不规范输入)
有时候用户输入可能是 2025.9.8
、9/8/25
之类的,无法直接匹配。
这时候调用 parseFlexibleDate
进行兜底处理:
private LocalDateTime parseFlexibleDate(String dateString) {String[] formats = {"d.M.yyyy", "d.M.yy", "M.d.yyyy", "M.d.yy","yyyy.M.d", "yy.M.d", "d/M/yyyy", "M/d/yyyy"};for (String format : formats) {try {return parseWithFormat(dateString, format);} catch (Exception ignore) {}}// 实在不行交给智能解析return smartParse(dateString).atTime(LocalTime.MIN);
}
2. 灵活解析(不规范输入)
有时候用户输入可能是 2025.9.8
、9/8/25
之类的,无法直接匹配。
这时候调用 parseFlexibleDate
进行兜底处理:
private LocalDateTime parseFlexibleDate(String dateString) {String[] formats = {"d.M.yyyy", "d.M.yy", "M.d.yyyy", "M.d.yy","yyyy.M.d", "yy.M.d", "d/M/yyyy", "M/d/yyyy"};for (String format : formats) {try {return parseWithFormat(dateString, format);} catch (Exception ignore) {}}// 实在不行交给智能解析return smartParse(dateString).atTime(LocalTime.MIN);
}
3. 智能解析(极端情况)
对于输入 20250908
或 250908
这种无分隔符字符串,先拆分数值,再推断哪个是 年份/月份/日期:
private LocalDate smartParse(String dateString) {String[] parts = dateString.split("[-./]");if (parts.length != 3) {throw new IllegalArgumentException("无法解析的日期格式: " + dateString);}int[] values = Arrays.stream(parts).mapToInt(Integer::parseInt).toArray();int year = 0, month = 0, day = 0;for (int value : values) {if (value > 31) year = value; // 最大值一般是年份}if (year == 0) year = values[2]; // 没识别到就默认最后一位为年份if (year < 100) year = (year < 70) ? 2000 + year : 1900 + year;for (int value : values) {if (value != year) {if (month == 0 && value <= 12) month = value;else day = value;}}if (month == 0) month = 1;if (day == 0) day = 1;return LocalDate.of(year, month, day);
}
三、使用示例
public static void main(String[] args) {DateStringSwitchUtils utils = DateStringSwitchUtils.INSTANCE;System.out.println(utils.parse("2025-09-08")); // 2025-09-08T00:00System.out.println(utils.parse("2025/09/08")); // 2025-09-08T00:00System.out.println(utils.parse("25-09-08")); // 2025-09-08T00:00System.out.println(utils.parse("09/08/2025")); // 2025-09-08T00:00System.out.println(utils.parse("20250908")); // 2025-09-08T00:00System.out.println(utils.parse("2025年9月8日")); // 2025-09-08T00:00System.out.println(utils.toLocalDateTimeString("2025-09-08")); // 输出:2025-09-08 00:00:00
}
四、总结与扩展
该工具类已经覆盖了 常见的日期格式(中式、美式、欧式、无分隔符、简写等)。
对一些 极端模糊输入,通过
smartParse
尝试智能识别。最终都能统一转为
LocalDateTime
,方便数据库存储或业务逻辑处理。
扩展思路:
可以新增对 时间部分(如
2025-09-08 14:30:00
)的支持。可以结合
ThreadLocal
缓存DateTimeFormatter
,进一步优化性能。如果对时区有需求,可以结合
ZonedDateTime
处理。
⚡️ 有了这个工具类,我们在做 批量数据导入 / 跨系统对接 / 用户输入处理 时,就不需要再写一堆 if-else 来判断日期格式了。
import org.apache.tika.utils.StringUtils;import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.ResolverStyle;
import java.time.temporal.ChronoField;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;/*** 日期字符串转换工具类**/
public final class DateStringSwitchUtils {public final static DateStringSwitchUtils INSTANCE = new DateStringSwitchUtils();private final Map<Pattern, Function<String, LocalDateTime>> dateFormatPatterns = new HashMap<>();public DateStringSwitchUtils() {initPatterns();}private void initPatterns() {// 四位年份格式registerPattern("^\\d{4}-\\d{2}-\\d{2}$", s -> parseWithFormat(s, "yyyy-MM-dd"));registerPattern("^\\d{4}/\\d{2}/\\d{2}$", s -> parseWithFormat(s, "yyyy/MM/dd"));registerPattern("^\\d{4}\\.\\d{2}\\.\\d{2}$", s -> parseWithFormat(s, "yyyy.MM.dd"));registerPattern("^\\d{4}年\\d{2}月\\d{2}日$", s -> parseWithFormat(s, "yyyy年M月d日"));// 两位年份格式registerPattern("^\\d{2}-\\d{2}-\\d{2}$", s -> parseWithFormat(s, "yy-MM-dd"));registerPattern("^\\d{2}\\.\\d{2}\\.\\d{2}$", s -> parseWithFormat(s, "yy.MM.dd"));registerPattern("^\\d{2}/\\d{2}/\\d{2}$", s -> parseWithFormat(s, "yy/MM/dd"));// 美式格式(月/日/年)registerPattern("^(0?[1-9]|1[0-2])[-/.](0?[1-9]|[12]\\d|3[01])[-/.]\\d{4}$",s -> parseWithFormat(s, "MM/dd/yyyy"));registerPattern("^(0?[1-9]|1[0-2])[-/.](0?[1-9]|[12]\\d|3[01])[-/.]\\d{2}$",s -> parseWithFormat(s, "MM/dd/yy"));// 欧式格式(日/月/年)registerPattern("^(0?[1-9]|[12]\\d|3[01])[-/.](0?[1-9]|1[0-2])[-/.]\\d{4}$",s -> parseWithFormat(s, "dd/MM/yyyy"));registerPattern("^(0?[1-9]|[12]\\d|3[01])[-/.](0?[1-9]|1[0-2])[-/.]\\d{2}$",s -> parseWithFormat(s, "dd/MM/yy"));// 灵活日期格式(处理不严谨格式)registerPattern("^\\d{1,4}[-/.]\\d{1,2}[-/.]\\d{1,4}$", this::parseFlexibleDate);// 中文日期格式(简写年份)registerPattern("^\\d{1,2}年\\d{1,2}月\\d{1,2}日$", s -> parseWithFormat(s, "yy年M月d日"));// 极端日期格式(无分隔符专供)registerPattern("^\\d{8}$",p -> parseWithFormat(p, "yyyyMMdd"));registerPattern("^\\d{6}$",p -> parseWithFormat(p, "yyMMdd"));}private void registerPattern(String regex, Function<String, LocalDateTime> converter) {dateFormatPatterns.put(Pattern.compile(regex), converter);}// 基础解析方法(返回当天00:00的LocalDateTime)private LocalDateTime parseWithFormat(String dateString, String pattern) {DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendPattern(pattern).parseDefaulting(ChronoField.ERA, 1).parseDefaulting(ChronoField.HOUR_OF_DAY, 0).parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0).parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0).parseLenient().toFormatter().withResolverStyle(ResolverStyle.SMART);return LocalDate.parse(dateString, formatter).atTime(LocalTime.MIN);}// 灵活日期解析(处理不严谨格式)private LocalDateTime parseFlexibleDate(String dateString) {String[] formats = {"d.M.yyyy", "d.M.yy", "M.d.yyyy", "M.d.yy","yyyy.M.d", "yy.M.d", "d/M/yyyy", "d/M/yy","M/d/yyyy", "M/d/yy"};for (String format : formats) {try {// 统一替换分隔符String normalizedFormat = format.replace("/", "[-/.]").replace(".", "[-/.]");Pattern pattern = Pattern.compile("\\d{1,4}" + normalizedFormat + "\\d{1,4}" +normalizedFormat + "\\d{1,4}");if (pattern.matcher(dateString).matches()) {return parseWithFormat(dateString, format);}} catch (Exception ignore) {// 尝试下一个格式}}// 尝试智能解析return smartParse(dateString).atTime(LocalTime.MIN);}// 智能解析(处理极端模糊情况)private LocalDate smartParse(String dateString) {String[] parts = dateString.split("[-./]");if (parts.length != 3) {throw new IllegalArgumentException("无法解析的日期格式: " + dateString);}int[] values = new int[3];for (int i = 0; i < 3; i++) {values[i] = Integer.parseInt(parts[i]);}// 智能识别日期组成部分int year = 0, month = 0, day = 0;// 识别年份(最大的值或大于31的值)for (int value : values) {if (value > 31 || (year == 0 && value > 12)) {year = value;}}// 如果没找到年份,使用最后一个值作为年份if (year == 0) year = values[2];// 处理两位年份if (year < 100) {year = (year < 70) ? 2000 + year : 1900 + year;}// 识别月份和日期for (int value : values) {if (value != year) {if (month == 0 && value <= 12) {month = value;} else {day = value;}}}// 如果仍然缺少月份或日期,使用默认值if (month == 0) month = 1;if (day == 0) day = 1;return LocalDate.of(year, month, day);}/*** String date to {@link LocalDateTime}* @param dateString string date* @return {@link LocalDateTime}*/public LocalDateTime parse(String dateString) {dateString = dateString.trim();for (Map.Entry<Pattern, Function<String, LocalDateTime>> entry : dateFormatPatterns.entrySet()) {if (entry.getKey().matcher(dateString).matches()) {return entry.getValue().apply(dateString);}}throw new IllegalArgumentException("未知的日期格式: " + dateString);}/*** 将日期字符串的格式转换为能直接插入数据库的格式** @param date 日期字符串* @return {@link java.sql.Date}*/public String toLocalDateTimeString(String date) {if (StringUtils.isEmpty( date)) return null;return this.parse(date).toString().replace("T", " ").split("\\.")[0];}
}