SimpleDateFormat 无法解析自己格式化的源

问题描述

我有一个示例代码如下

final String pattern = "dd-MMM-yy hh.mm.ss.SSS000 a";
final SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
final String formatted = dateFormat.format(new Date());
final Date date = dateFormat.parse(formatted); // throws ParseException 

errorIndex 是 25,它是最后一个零和“AM/PM”短语之间的空格。

Exception in thread "main" java.text.ParseException: Unparseable date: "09-Jun-21 04.40.45.898000 PM"
    at java.text.DateFormat.parse(DateFormat.java:366)

但是当我改变如下模式时(这不适合我的情况),一切正常。

final String pattern = "dd-MMM-yy hh.mm.ss.SSSSSS a";

谁能告诉我这是怎么回事?

解决方法

我认为这是解析器的问题。它不一定按字面意思使用模式中的字符数。所以它会尝试将 898000 解析为毫秒。

SimpleDateFormat 反正已经过时了,也许你可以试试 java.time 库?

  LocalDateTime date = LocalDateTime.ofInstant((new Date()).toInstant(),ZoneId.systemDefault());
  DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MMM-yy hh.mm.ss.SSS'000' a");
  String text = date.format(formatter);
  Date parsedDate = Date.from(LocalDateTime.parse(text,formatter).atZone(ZoneId.systemDefault()).toInstant());

如果您也可以使用 LocalDateTime,则可以跳过与 Date 的转换:

  LocalDateTime date = LocalDateTime.now();
  DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MMM-yy hh.mm.ss.SSS'000' a");
  String text = date.format(formatter);
  LocalDateTime parsedDate = LocalDateTime.parse(text,formatter);
,

罪魁祸首是格式字符串中的 SSS。打印时,表示要打印至少 3 位毫秒数。但是在解析的时候,它只是表示你要解析毫秒。来自the documentation

对于格式化,模式字母的数量是最少的 数字,较短的数字用零填充到这个数量。为了 解析,除非需要,否则忽略模式字母的数量 分隔两个相邻的字段。

即解析时,SSS模式将整个898000解析为毫秒,之后再找不到零点。

,

java.time

java.util 日期时间 API 及其格式化 API SimpleDateFormat 已过时且容易出错。建议完全停止使用它们并切换到 modern Date-Time API*,它于 2014 年 3 月作为 Java SE 8 标准库的一部分发布。

使用 java.time(现代日期时间 API)的解决方案:

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        String strDateTime = "09-Jun-21 04.40.45.898000 PM";
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("d-MMM-uu h.m.s.SSSSSS a",Locale.ENGLISH);
        LocalDateTime ldt = LocalDateTime.parse(strDateTime,dtf);
        System.out.println(ldt);
    }
}

输出:

0021-06-09T16:40:45.898

ONLINE DEMO

在这里,您可以使用 y 代替 u,而是使用 I prefer u to y

Trail: Date Time 了解有关现代 Date-Time API 的更多信息。

使用旧 API 的解决方案:

SimpleDateFormat does not handle fraction-of-second beyond three digits in the millisecond part correctly。您需要调整日期时间字符串以符合此限制,即您需要从毫秒部分中删除数字以使其最多保持三位数。一种简单的方法是使用 Regex 替换。

演示:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

public class Main {
    public static void main(String[] args) throws ParseException {
        String strDateTime = "09-Jun-21 04.40.45.898000 PM";
        strDateTime = strDateTime.replaceAll("(.*(?<=\\.)\\d{3})(\\d+)(\\s[AP]M)","$1$3");

        SimpleDateFormat sdf = new SimpleDateFormat("d-MMM-y h.m.s.SSS a",Locale.ENGLISH);
        Date date = sdf.parse(strDateTime);
        System.out.println(date);
    }
}

在我的时区输出:

Wed Jun 09 16:40:45 BST 2021

ONLINE DEMO


* 出于任何原因,如果您必须坚持使用 Java 6 或 Java 7,您可以使用 ThreeTen-Backport,它将大部分 java.time 功能向后移植到 Java 6 & 7. 如果您正在为 Android 项目工作并且您的 Android API 级别仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaringHow to use ThreeTenABP in Android Project