java.text.ParseException: 不可解析的日期"yyyy-MM-dd'T'HH:mm:ss.SSSZ" - SimpleDateFormat



我将不胜感激任何帮助来查找此异常的错误:

java.text.ParseException: Unparseable date: "2007-09-25T15:40:51.0000000Z"

和以下代码:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
Date date = sdf.parse(timeValue);
long mills = date.getTime();
this.point.time = String.valueOf(mills);

它抛出Date date = sdf.parse(timeValue); .

timeValue = "2007-09-25T15:40:51.0000000Z";,如例外。

谢谢。

Z表示时区字符。需要引用:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");

(答案现已广泛修改,感谢您在评论中的更正(

在 Java 7 中,您可以使用X模式来匹配ISO8601时区,其中包括特殊的Z (UTC( 值。

X模式还支持显式时区,例如 +01:00

这种方法正确遵循时区指示符,避免了仅将其视为字符串的问题,从而错误地解析本地时区而不是 UTC 或其他时区的时间戳。

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
Date date = sdf.parse("2007-09-25T15:40:51Z");
Date date2 = sdf.parse("2007-09-25T15:40:51+01:00");

这也可以用毫秒使用:

SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
Date date3 = sdf2.parse("2007-09-25T15:40:51.500Z");

但是,正如其他人指出的那样,您的格式有 7 位小数秒,大概是十分之一微秒。如果是这样,SimpleDateFormat无法处理此问题,并且您将获得不正确的结果,因为每 0.1 微秒将被解释为一毫秒,从而产生长达 10,000 秒(几个小时(的潜在总体误差。

在极端情况下,如果秒的小数部分值为 0.99999999 秒,则会被错误地解释为 9999999 毫秒,即大约 167 分钟或 2.8 小时。

// Right answer, error masked for zero fractional seconds 
Date date6 = sdf2.parse("2007-09-25T15:40:51.0000000Z");
// Tue Sep 25 15:40:51 GMT 2007
// Error - wrong hour
// Should just half a second different to the previous example
Date date5 = sdf2.parse("2007-09-25T15:40:51.5000000Z");
// Tue Sep 25 17:04:11 GMT 2007

当秒的小数部分为零时,此错误是隐藏的,如您的示例所示,但只要它们不为零,就会显现出来。

在许多情况下,可以通过关闭"宽松"解析来检测到此错误,并减少其影响,默认情况下,该解析将接受超过一秒的小数部分并将其转移到秒/分钟/小时部分:

sdf2.setLenient(false);
sdf2.parse("2007-09-25T15:40:51.5000000Z");
// java.text.ParseException: Unparseable date: "2007-09-25T15:40:51.5000000Z"

这将捕获 millis 值大于 999 的情况,但不检查位数,因此它只是针对毫秒/微秒不匹配的部分和间接保护措施。但是,在许多实际数据集中,这将捕获大量错误,从而指示根本问题,即使某些值漏掉也是如此。

我建议始终禁用宽松解析,除非您有特定的需要,因为它会捕获许多错误,否则这些错误将被静默隐藏并传播到下游数据中。

如果您的小数

秒数始终为零,那么您可以使用此处的解决方案之一,但如果以后将代码用于非零小数秒,则存在它们不起作用的风险。您可能希望记录此值和/或断言该值为零,以避免以后的错误。

否则,您可能需要将秒的小数部分转换为毫秒,以便SimpleDateFormat可以正确解释它们。或者使用较新的日期时间 API 之一。

java.time

我建议您使用 java.time,即现代 Java 日期和时间 API,用于您的日期和时间工作。您的字符串采用 ISO 8601 格式,可以通过 java.time.Instant 类直接解析,而无需我们指定任何格式化程序:

    String timeValue = "2007-09-25T15:40:51.0000000Z";
    
    Instant i = Instant.parse(timeValue);
    long mills = i.toEpochMilli();
    String time = String.valueOf(mills);
    
    System.out.println(time);

输出:

1190734851000

如果需要,可以使用格式化程序进行输出

如果我们知道毫秒值永远不会是负数,java.time 可以为我们将其格式化为字符串。这会首先将显式转换保存到毫秒。

private static final DateTimeFormatter EPOCH_MILLI_FORMATTER
        = new DateTimeFormatterBuilder().appendValue(ChronoField.INSTANT_SECONDS)
                .appendValue(ChronoField.MILLI_OF_SECOND, 3)
                .toFormatter(Locale.ROOT);

现在格式化是微不足道的:

    assert ! i.isBefore(Instant.EPOCH) : i;
    String time = EPOCH_MILLI_FORMATTER.format(i);

输出仍然相同:

1190734851000

特别是如果您需要在程序中的更多位置将Instant对象格式化为字符串,我推荐后一种方法。

您的代码中出了什么问题?

首先,SimpleDateFormat不可能正确解析秒的 7 位小数。只要分数为零,结果无论如何都会正确,但想象一下一整秒后只有十分之一秒的时间,例如,2007-09-25T15:40:51.1000000Z .在这种情况下,SimpleDateFormat会将分数解析为一百万毫秒,而您的结果将超过一刻钟。对于较大的分数,误差可能是几个小时。

其次,正如其他人所说,格式模式字母Z与表示 UTC 或 UTC 的偏移量零Z不匹配。这导致了您观察到的异常。按照接受答案中的建议将Z放在引号中也是错误的,因为它会导致您错过字符串中的这些关键信息,再次导致几个小时的错误(在大多数时区(。

链接

Oracle 教程:日期时间解释如何使用 java.time。

最新更新