Java 8 解析 ISO-8601 日期,忽略时区的存在(或不存在)

Java 8 Parse ISO-8601 date ignoring presence (or absence) of timezone

我的应用程序应该能够解析忽略时区的日期(我总是确定它是 UTC)。问题是日期可能有以下两种形式 -

2017-09-11T12:44:07.793Z

0001-01-01T00:00:00

我可以使用 LocalDateTime 解析第一个,使用 Instant class 解析第二个。有没有办法使用单一机制来做到这一点?

P.S。我试图避免在输入字符串

的末尾硬编码 Z

你可以"parseBest",像这样:

DateTimeFormatter parser = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[Z]");

Temporal parsed = parser.parseBest(inputString, Instant::from, LocalDateTime::from);

然后你应该检查解析了什么,并采取相应的行动。 parseBest 方法适用于任何类型的 TemporalQuery,包括 java.time 类 上可用的大多数 from 方法。例如,您可以使用 LocalDate.from 来延长该列表。

您还可以使用该方法和 lambda 将解析结果强制转换为您想要的类型,而无需 instanceof 外部结果解析检查(尽管并非没有一次转换):

Instant parsed = (Instant) parser.parseBest(inputString,
                    Instant::from,
                    interResult -> LocalDateTime.from(interResult).atZone(ZoneOffset.UTC).toInstant())

请注意,第二个选项使用 lambda 将 LocalDateTime 转换为 ZonedDateTime,然后再转换为 Instant,因此解析结果总是强制转换为 Instant.

如果 Z 偏移量是可选的,您可以使用带有可选部分的 java.time.format.DateTimeFormatterBuilder

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // date/time
    .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
    // optional offset
    .optionalStart().appendOffsetId()
    // create formatter
    .toFormatter();

然后您可以使用 parseBest 方法,使用 TemporalQuery 的列表尝试创建相应的对象。然后你检查 return 类型并采取相应的行动:

Instant instant = null;
// tries to create Instant, and if it fails, try a LocalDateTime
TemporalAccessor parsed = fmt.parseBest("2017-09-11T12:44:07.793Z", Instant::from, LocalDateTime::from);
if (parsed instanceof Instant) {
    instant = (Instant) parsed;
} else if (parsed instanceof LocalDateTime) {
    // convert LocalDateTime to UTC instant
    instant = ((LocalDateTime) parsed).atOffset(ZoneOffset.UTC).toInstant();
}
System.out.println(instant); // 2017-09-11T12:44:07.793Z

运行 第二个输入 (0001-01-01T00:00:00) 产生等同于 0001-01-01T00:00:00Z.

Instant

在上面的示例中,我只使用了 Instant::fromLocalDateTime::from,因此格式化程序会尝试先创建一个 Instant。如果不可能,则它会尝试创建 LocalDateTime。您可以向该列表添加任意数量的类型(例如,我可以添加 ZonedDateTime::from,如果创建了 ZonedDateTime,我可以使用 [=28= 转换为 Instant ] 方法)。


你肯定知道输入始终是 UTC,你也可以直接在格式化程序中设置它:

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // date/time
    .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
    // optional offset
    .optionalStart().appendOffsetId()
    // create formatter with UTC
    .toFormatter().withZone(ZoneOffset.UTC);

所以可以直接解析为Instant:

System.out.println(Instant.from(fmt.parse("2017-09-11T12:44:07.793Z"))); // 2017-09-11T12:44:07.793Z
System.out.println(Instant.from(fmt.parse("0001-01-01T00:00:00"))); // 0001-01-01T00:00:00Z