如何使用 Java 时间库将符合 ISO 标准的字符串解析为日期

How to parse ISO compliant string to Date using Java Time library

我有各种格式的时间戳,例如:

2018-07-21T00:50:39
2017-11-20T23:18:27.529Z

我需要将它们解析为 java.util.Date,并尝试了以下方法,它们都适用于第一种格式,但失败了第二种格式。

以下是我试过的方法,但错误:

1.

private Date try3(String dateString) {
        return Date.from(Instant.parse(dateString).atZone(ZoneId.of("UTC")).toInstant());
    }

第一次格式化失败,错误:

java.time.format.DateTimeParseException: Text '2017-11-20T23:18:28' could not be parsed at index 19

2.

private Date try2(String dateString) {
    return Date.from(LocalDateTime.parse(dateString).atZone(ZoneId.of("UTC")).toInstant());
}

对于第二种格式,此方法抛出错误:

java.time.format.DateTimeParseException: Text '2017-11-20T23:18:27.529Z' could not be parsed, unparsed text found at index 23

3:

private Date try1(String dateString) {
    return Date.from(ZonedDateTime.parse(dateString,
                    DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss").withZone(ZoneId.of("UTC"))).toInstant());

}

对于第二种格式,上述方法抛出错误:

java.time.format.DateTimeParseException: Text '2017-11-20T23:18:27.529Z' could not be parsed, unparsed text found at index 19

我最终得到了 Joda 库:

private Date try4(String dateString) {
        return new DateTime(dateString).withZone(DateTimeZone.UTC).toDate();
    }

但是在 Java 时间库中真的有办法做到这一点吗?

java.time

这已经被讨论过无数次了。因此,请搜索 Stack Overflow 以获取更多信息。

切勿使用 DateCalendar。仅使用 java.time。同样,Joda-Time 也被 java.time 取代(两者都由同一个人 Stephen Colebourne 领导)。

您的两个字符串输入都是标准的 ISO 8601 格式。当 parsing/generating 字符串时,java.time classes 默认使用这些格式。

第一个缺少与 UTC 或时区的偏移指示符,因此它代表一个时刻。

LocalDateTime.parse( "2018-07-21T00:50:39" )

Z中的第二个表示 UTC,发音为“Zulu”。

Instant.parse( "2017-11-20T23:18:27.529Z" )

要在一种方法中处理两种格式,请在输入字符串中搜索是否存在 Z 并相应地进行分支。此外,如果您收到意外输入,请捕获异常。

如果您必须有一个 java.util.Date 对象,请参阅 class JavaDoc 以了解新方法 fromto… 旧 class 转换to/from java.time class。正如我所说,所有这些都已多次介绍,因此请搜索更多信息。

你总是想获取相同的类型,我似乎理解如果字符串中没有偏移量,你想要UTC(这一点很重要)。我建议指定一个带有可选 UTC 偏移量和默认 UTC(零偏移量)的格式化程序,以防字符串中没有任何偏移量:

    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
            .appendPattern("[X]")
            .parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
            .toFormatter();

    String withoutZ = "2018-07-21T00:50:39";
    Instant anInstant = formatter.parse(withoutZ, Instant::from);
    System.out.println(anInstant);

    String withZ = "2017-11-20T23:18:27.529Z";
    Instant anotherInstant = formatter.parse(withZ, Instant::from);
    System.out.println(anotherInstant);

输出:

2018-07-21T00:50:39Z
2017-11-20T23:18:27.529Z

格式模式字符串中的方括号[X]表示偏移量X是可选的。 ISO_LOCAL_DATE_TIME.

处理秒中是否存在小数点

我希望您知道 Date class 设计不佳且早已过时,因此通常您不想要一个,您只要求一个,因为您不可或缺需要一个您现在无法更改的遗产 API。在这种情况下,按照您的问题从 Instant 进行最终转换。

通常用于可变 date/time 格式的技术包括:

  1. 依次尝试多种已知格式。
  2. 在确定使用哪种格式之前先了解一下字符串。在你的情况下,你可能已经在 .endsWith("Z") 上分支了,正如 Basil Bourque 的回答中已经建议的那样。
  3. 格式模式中的可选部分。
  4. DateTimeFormatterBuilder.parseDefaulting() 对于解析后的字符串中可能不存在的部分。
  5. DateTimeFormatter.parseBest().

如您所见,我在这里使用的是第 3 项和第 4 项。