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:00
    System.out.println(utils.parse("2025/09/08"));   // 2025-09-08T00:00
    System.out.println(utils.parse("25-09-08"));     // 2025-09-08T00:00
    System.out.println(utils.parse("09/08/2025"));   // 2025-09-08T00:00
    System.out.println(utils.parse("20250908"));     // 2025-09-08T00:00
    System.out.println(utils.parse("2025年9月8日")); // 2025-09-08T00:00

    System.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];
    }
}
相关推荐
刘 大 望2 小时前
传输层:UDP/TCP协议
java·网络·网络协议·tcp/ip·udp·信息与通信
小胖墩有点瘦2 小时前
【基于协同过滤的校园二手交易平台】
java·vue·毕业设计·springboot·计算机毕业设计·协同过滤·校园二手交易平台
我真的是大笨蛋2 小时前
G1 垃圾收集器深入解析
java·jvm·笔记·缓存
Dersun2 小时前
python学习进阶之异常和文件操作(三)
开发语言·python·学习·json
我好喜欢你~2 小时前
C#---Expression(表达式)
开发语言·c#
ytadpole2 小时前
揭秘 XXL-JOB 调度:从代码深处看路由策略的精妙设计
java·后端
27^×2 小时前
Linux 常用命令速查手册:从入门到实战的高频指令整理
java·大数据·linux
学Java的bb2 小时前
后端Web实战-Spring原理
java·spring boot·spring
Tiger_shl2 小时前
【.Net技术栈梳理】01-核心框架与运行时(CLR)
开发语言·.net