sunny123456

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

java 使用 SimpleDateFormat 的坑

        System.out.println("d 是 "+ d);
        long l = transformDate(d);
        System.out.println("l 是 "+ String.valueOf(l));
public static long transformDate(String d){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        try {
            Date date = sdf.parse(d);
            System.out.println("date 是 "+ date.toString());
            return date.getTime();
        } catch (ParseException e) {
            return 0;
        }
    }
请解析 sdf.parse(d) 的源码,解释 为什么 结果是
d 是 20250616111027
date 是 Mon Nov 28 00:00:00 CST 46135
l 是 1393741353600000
并没有转换成正确的时间戳

问题分析:为什么 sdf.parse(d) 解析出错误的日期?

您的问题在于 格式不匹配解析规则的特殊性。让我们一步步分析:

1. 格式不匹配

  • 输入字符串 d20250616111027 (14位,隐含格式 yyyyMMddHHmmss)
  • 解析格式 sdf"yyyyMMdd" (8位,只包含年月日)

2. SimpleDateFormat.parse() 的解析机制

当格式长度 < 输入字符串长度时,解析器会:

  1. 按格式解析前 N 个字符(这里是前 8 位 20250616
  2. 将剩余字符(111027)解析为毫秒数
  3. 将毫秒数加到基础日期上

源码关键逻辑 (SimpleDateFormat.parse() 底层调用):

public Date parse(String source) throws ParseException {
    ParsePosition pos = new ParsePosition(0);
    Date result = parse(source, pos); // 核心解析方法
    if (result == null) {
        throw new ParseException("Unparseable date", pos.getErrorIndex());
    }
    return result;
}

private Date parse(String text, ParsePosition pos) {
    // 解析格式定义的字段(年月日)
    Calendar cal = calendar;
    // ...
    
    // 当格式用完但字符串还有剩余时
    if (pos.getIndex() < text.length()) {
        // 将剩余字符串解析为数字 ➡️ 这就是问题根源!
        long millis = Long.parseLong(text.substring(pos.getIndex()));
        cal.set(Calendar.MILLISECOND, (int) millis);
    }
    
    return cal.getTime();
}

3. 具体解析过程

输入字符串: "20250616111027"
解析格式: "yyyyMMdd"

步骤:
1. 解析前8位 "20250616" → 日期:2025年6月16日
2. 剩余6位 "111027" 被解释为毫秒数:111,027 毫秒
3. 计算最终时间:
   基础日期:2025-06-16 00:00:00.000
   加上毫秒:111,027 毫秒 = 1分51秒27毫秒
   最终时间:2025-06-16 00:01:51.027

4. 为什么输出是 Mon Nov 28 00:00:00 CST 46135

您看到的奇怪输出是因为:

  1. 日期计算溢出:111,027 毫秒被错误加到年份字段
  2. 内部日历系统错误
    • 解析器尝试将 111,027 毫秒加到 2025-06-16
    • 但实际加到的是年份字段(Calendar.YEAR)
    • 导致年份变成 2025 + 111,027 = 113,052 年
  3. Date.toString() 方法无法处理超大年份,输出乱码

解决方案:格式必须匹配

修复代码:

public static long transformDate(String d) {
    // 使用与输入字符串匹配的格式
    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
    try {
        Date date = sdf.parse(d);
        System.out.println("date 是 "+ date.toString());
        return date.getTime();
    } catch (ParseException e) {
        return 0;
    }
}

验证:

输入: d = "20250616111027"
解析格式: "yyyyMMddHHmmss"

解析结果:
  年 = 2025
  月 = 06 → 实际6月 (Calendar.JUNE)
  日 = 16
  时 = 11
  分 = 10
  秒 = 27

输出: Mon Jun 16 11:10:27 CST 2025
时间戳: 1750024227000 (正确值)

关键教训

  1. 格式必须严格匹配:解析格式长度 == 输入字符串长度
  2. 避免隐式解析:未定义格式的部分会被解释为毫秒数
  3. 使用现代APISimpleDateFormat 有诸多陷阱,推荐替代方案:
// Java 8+ 推荐方案
public static long parseWithJavaTime(String d) {
    DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
    LocalDateTime ldt = LocalDateTime.parse(d, fmt);
    return ldt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
}

为什么原始输出是 46135 年?

这是日历系统溢出后的伪值:

  1. 111,027 毫秒被加到年份字段
  2. 年份 = 2025 + 111,027 = 113,052
  3. Date.toString() 使用 (year + 1900) 的显示规则
  4. 113,052 - 1900 = 111,152 年 ➡️ 显示系统无法处理超大年份,输出乱码

⚠️ 这再次证明:日期解析必须格式精确匹配,否则会产生灾难性错误。

posted on 2025-06-16 18:34  sunny123456  阅读(50)  评论(0)    收藏  举报