当前位置: 首页 > news >正文

Java 日期字符串万能解析工具类(支持多种日期格式智能转换)

在日常开发中,经常遇到 不同格式的日期字符串,例如:

  • 2025-09-08

  • 2025/09/08

  • 25-09-08

  • 09/08/2025

  • 20250908

  • 2025年09月08日

如果我们在业务中需要统一转成 LocalDateTime,再存入数据库,就不得不写一堆 DateTimeFormatter 去 try-catch,代码既冗余又不优雅。

为了解决这个问题,我写了一个工具类 DateStringSwitchUtils,它可以自动识别各种常见日期格式,并转换为 LocalDateTime。(结尾附完整代码)


一、设计思路

  1. 多格式支持
    通过正则 Pattern 结合 DateTimeFormatter,支持常见的中/美/欧日期写法。

  2. 灵活解析
    对一些 格式不严格的输入(比如 2025.9.825/9/8),尝试灵活匹配。

  3. 智能识别
    对于 25090820250908 这种无分隔符日期,自动识别年月日。

  4. 统一输出
    最终都转换为 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.89/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.89/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. 智能解析(极端情况)

对于输入 20250908250908 这种无分隔符字符串,先拆分数值,再推断哪个是 年份/月份/日期

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];}
}


文章转载自:

http://Zt8VrRbU.nqpxs.cn
http://pii5KvFL.nqpxs.cn
http://h4s0falj.nqpxs.cn
http://THTJE8an.nqpxs.cn
http://jhW2nilO.nqpxs.cn
http://WpH3TbIi.nqpxs.cn
http://sClMhQeu.nqpxs.cn
http://V3yz4YPH.nqpxs.cn
http://5RaHSPOG.nqpxs.cn
http://kcX2lP4T.nqpxs.cn
http://mqJl0WbF.nqpxs.cn
http://JN74ej4p.nqpxs.cn
http://7smi3DUN.nqpxs.cn
http://lajn7dJC.nqpxs.cn
http://dXkSYTcG.nqpxs.cn
http://yhMC5lSd.nqpxs.cn
http://oDaNv9YS.nqpxs.cn
http://ZN5eZlO1.nqpxs.cn
http://ieiSj1Py.nqpxs.cn
http://3CAmpq9D.nqpxs.cn
http://MboGYXLi.nqpxs.cn
http://NI5dDpm2.nqpxs.cn
http://HKQZZwNg.nqpxs.cn
http://iGbHoS0g.nqpxs.cn
http://guXWfllb.nqpxs.cn
http://9ZcmIb6y.nqpxs.cn
http://mRCkGJi6.nqpxs.cn
http://Ol3drNOF.nqpxs.cn
http://v68tB63z.nqpxs.cn
http://NzYjaJ9Z.nqpxs.cn
http://www.dtcms.com/a/377916.html

相关文章:

  • 在VS2022的WPF仿真,为什么在XAML实时预览点击 ce.xaml页面控件,却不会自动跳转到具体代码,这样不方便我修改代码,
  • 【数组】区间和
  • Qt 基础编程核心知识点全解析:含 Hello World 实现、对象树、坐标系及开发工具使用
  • 解决推理能力瓶颈,用因果推理提升LLM智能决策
  • 【大前端】常用 Android 工具类整理
  • Gradle Task的理解和实战使用
  • 强大的鸿蒙HarmonyOS网络调试工具PageSpy 介绍及使用
  • C++/QT 1
  • 软件测试用例详解
  • 【ROS2】基础概念-进阶篇
  • 三甲地市级医院数据仓湖数智化建设路径与编程工具选型研究(上)
  • 利用Rancher平台搭建Swarm集群
  • BRepMesh_IncrementalMesh 重构生效问题
  • VRRP 多节点工作原理
  • 运行 Ux_Host_HUB_HID_MSC 通过 Hub 连接 U 盘读写不稳定问题分析 LAT1511
  • Oracle体系结构-控制文件(Control Files)
  • 0303 【软考高项】项目管理概述 - 组织系统(项目型组织、职能型组织、矩阵型组织)
  • Spark-SQL任务提交方式
  • 10、向量与矩阵基础 - 深度学习的数学语言
  • 开发避坑指南(45):Java Stream 求两个List的元素交集
  • React19 中的交互操作
  • 阿里云ECS vs 腾讯云CVM:2核4G服务器性能实测对比 (2025)
  • 网络编程;TCP多进程并发服务器;TCP多线程并发服务器;TCP网络聊天室和UDP网络聊天室;后面两个还没写出来;0911
  • STM32项目分享:基于stm32的室内环境监测装置设计与实现
  • 利用归并算法对链表进行排序
  • GPU 服务器压力测试核心工具全解析:gpu-burn、cpu-burn 与 CUDA Samples
  • Power Automate List Rows使用Fetchxml查询的一个bug
  • Zynq开发实践(FPGA之ddr sdram读写)
  • LeetCode 热题 160.相交链表(双指针)
  • 西门子 S7-200 SMART PLC 编程:转换 / 定时器 / 计数器指令详解 + 实战案例(案例篇)