问题描述
似乎没有内置的 java.time 解析器可以处理任何有效的 W3C ISO 8601 日期/时间字符串。
它应该返回一个 zoneddatetime,其中缺失的字段按照 W3C 规范中的规定设置为 0。
这似乎是一个很常见的用例,我很惊讶它不是标准库的一部分,而且我想它在库外已经多次解决了。有人有解决方案吗?
解决方法
W3C ISO 8601
That document 是 ISO 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 8601 是 OffsetDateTime
而不是 ZonedDateTime
。 ZonedDateTime
需要时区 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.time
,Trail: Date Time*。
* 出于任何原因,如果您必须坚持使用 Java 6 或 Java 7,您可以使用 ThreeTen-Backport,它将大部分 java.time 功能向后移植到 Java 6 & 7. 如果您正在为 Android 项目工作并且您的 Android API 级别仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaring 和 How to use ThreeTenABP in Android Project。