我需要一个可以处理任何有效 W3C ISO 8601 date/time 字符串的 java.time 解析器

I need a java.time parser that can handle any valid W3C ISO 8601 date/time string

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

它应该return 一个 ZonedDateTime,缺失的字段设置为 0,如 W3C 规范中所指定。

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

W3C ISO 8601

That document is a self-declared “profile” of the ISO 8601 标准,而不是标准本身。

This seems to be such a common use case, im surprised its not part of the standard library

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

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

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

你说:

It should return a ZonedDateTime, with missing fields set to 0 as specified in the W3C spec.

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

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

您可以通过使用 DateTimeFormatterBuilder class 建立默认值来更接近您想要的通用解析器。参见

DateTimeFormatterBuilder#parseDefaulting

您 link、W3C ISO 8601 中的示例是 OffsetDateTime 而不是 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 小时)。

详细了解 java.timemodern date-time API* from Trail: Date Time


* 无论出于何种原因,如果您必须坚持Java 6 或Java 7,您可以使用ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and