我需要一个 java.time 解析器,它可以处理任何有效的 W3C ISO 8601 日期/时间字符串

问题描述

似乎没有内置的 java.time 解析器可以处理任何有效的 W3C ISO 8601 日期/时间字符串。

它应该返回一个 zoneddatetime,其中缺失的字段按照 W3C 规范中的规定设置为 0。

这似乎是一个很常见的用例,我很惊讶它不是标准库的一部分,而且我想它在库外已经多次解决了。有人有解决方案吗?

解决方法

W3C ISO 8601

That documentISO 8601 标准的自我声明“配置文件”,而不是标准本身。

这似乎是一个很常见的用例,我很惊讶它不是标准库的一部分

java.time 类在解析文本时确实默认使用 ISO 8601 格式。

没有通用解析器,因为上下文很重要。年、年-月、日期、时间、带时间的日期、带偏移的日期、带时区的日期、都是不同的动物。您通常不会期望不同类型的值任意到达。

如果您确实有这样一个任意类型的集合,那么只需执行一系列解析尝试。 DateTimeParseException 异常的陷阱。如果使用一个 java.time 类解析失败,请转到下一个 java.time 类。

你说:

它应该返回一个 ZonedDateTime,其中缺失的字段按照 W3C 规范中的规定设置为 0。

如何将仅限年份的值转换为 ZonedDateTime 对象?月和日的值应该是多少?零在那里是不可接受的。什么时区?您需要指定所需的区域。什么时间?时间为零并不适用,因为在某些区域,并非所有天都是从 00:00:00 开始的。

由于这些原因,通用解析器是不可能的。您需要根据特定应用的业务规则对特定输入数据进行解析。

通过使用 DateTimeFormatterBuilder 类建立默认值,您可以更接近所需的通用解析器。见Answer by Avinash

,

DateTimeFormatterBuilder#parseDefaulting

链接中的示例 W3C ISO 8601OffsetDateTime 而不是 ZonedDateTimeZonedDateTime 需要时区 ID,例如Europe/London。此外,JDBC 4.2 支持 OffsetDateTime,而不是 ZonedDateTime

您可以使用 DateTimeFormatterBuilder#parseDefaulting 将字符串解析为具有默认值的 OffsetDateTime

演示:

import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Locale;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        ZoneId zoneId = ZoneId.systemDefault();
        LocalDate now = LocalDate.now(zoneId);
        
        DateTimeFormatter dtf = new DateTimeFormatterBuilder()
                .appendPattern("uuuu[-MM[-dd]]['T'HH[:mm[:ss[.SSSSSSSSS][.SSSSSSSS][.SSSSSSS][.SSSSSS][.SSSSS][.SSSS][.SSS][.SS][.S]]]][XXX]")
                .parseDefaulting(ChronoField.MONTH_OF_YEAR,now.getMonthValue())
                .parseDefaulting(ChronoField.DAY_OF_MONTH,now.getDayOfMonth())
                .parseDefaulting(ChronoField.HOUR_OF_DAY,0)
                .parseDefaulting(ChronoField.MINUTE_OF_HOUR,0)
                .parseDefaulting(ChronoField.SECOND_OF_MINUTE,0)
                .parseDefaulting(ChronoField.NANO_OF_SECOND,0)     
                .parseDefaulting(ChronoField.OFFSET_SECONDS,0)
                .toFormatter(Locale.ENGLISH);
        
        //Test          
        Stream.of(
                    "1997","1997-07","1997-07-16","1997-07-16T19:20+01:00","1997-07-16T19:20:30+01:00","1997-07-16T19:20:30.45+01:00"
        ).forEach(s -> System.out.println(OffsetDateTime.parse(s,dtf)));
    }
}

输出:

1997-05-21T00:00Z
1997-07-21T00:00Z
1997-07-16T00:00Z
1997-07-16T19:20+01:00
1997-07-16T19:20:30+01:00
1997-07-16T19:20:30.450+01:00

输出中的 Z 是零时区偏移的 timezone designator。它代表祖鲁语并指定 Etc/UTC 时区(时区偏移为 +00:00 小时)。

modern date-time API 中详细了解 java.timeTrail: Date Time*


* 出于任何原因,如果您必须坚持使用 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