Java 中的 ISO 8601 时间间隔解析

Posted

技术标签:

【中文标题】Java 中的 ISO 8601 时间间隔解析【英文标题】:ISO 8601 Time Interval Parsing in Java 【发布时间】:2013-04-05 08:25:57 【问题描述】:

ISO 8601 定义了表示时间间隔的语法。

时间间隔有四种表示方式:

开始和结束,例如“2007-03-01T13:00:00Z/2008-05-11T15:30:00Z” 开始和持续时间,例如“2007-03-01T13:00:00Z/P1Y2M10DT2H30M” 持续时间和结束时间,例如“P1Y2M10DT2H30M/2008-05-11T15:30:00Z” 仅持续时间,例如“P1Y2M10DT2H30M”,带有其他上下文信息

如果结束值中缺少任何元素,则假定它们与包括时区在内的开始值相同。该标准的这一特性允许简明地表示时间间隔。例如,包括开始和结束时间在内的两小时会议的日期可以简单地显示为“2007-12-14T13:30/15:30”,其中“/15:30”表示“/2007-12- 14T15:30”(与开始日期相同),或每月计费周期的开始日期和结束日期为“2008-02-15/03-14”,其中“/03-14”表示“/2008-03” -14"(与开始的同一年)。

此外,通过在区间表达式的开头添加“R[n]/”来形成重复区间,其中R用作字母本身,[n]替换为重复次数。省略 [n] 的值意味着无限次数的重复。因此,要从“2008-03-01T13:00:00Z”开始重复“P1Y2M10DT2H30M”间隔五次,请使用“R5/2008-03-01T13:00:00Z/P1Y2M10DT2H30M”。

我正在寻找一个好的 Java 解析器(如果可能与 Joda-Time 库兼容)来解析这个语法。任何指向一个好的库的指针?

【问题讨论】:

【参考方案1】:

java.time

Java 8 及更高版本中内置的java.time 框架具有用于解析ISO 8601 formatted duration 的Duration.parse 方法:

java.time.Duration d = java.time.Duration.parse("PT1H2M34S");
System.out.println("Duration in seconds: " + d.get(java.time.temporal.ChronoUnit.SECONDS));

打印Duration in seconds: 3754

【讨论】:

另外,读者可能对ThreeTen-Extra 项目中提供的Interval 类感兴趣。该项目扩展了 java.time,由同一个人编写。该项目还充当了未来可能添加到 java.time 的试验场。 Interval 类将时间跨度定义为一对Instant 对象。该类解析 ISO 8601 字符串,例如 "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z" 不幸的是 java.time 区分了 Duration(基于时间)和 Periods(基于日期),因此 Duration.parse() 和 Period.parse() 都不能处理第四种情况(“P1Y2M10DT2H30M ") 有没有人为@tobias_ 描述的案例找到解决方案?已经有几年了,所以我应该认为存在这样的工具,但我找不到它。【参考方案2】:

对于项目中可能被限制使用 3rd 方库(许可原因或其他原因)的任何人,Java 本身至少提供了此功能的一部分,因为 Java 1.6(或更早版本?),使用 javax.xml.datatype.DatatypeFactory.newDuration(String)方法和Duration 类。 DatatypeFactory.newDuration(String) 方法将解析“PnYnMnDTnHnMnS”格式的字符串。这些类旨在用于 XML 操作,但由于 XML 使用 ISO 8601 时间表示法,它们也可用作方便的持续时间解析实用程序。

例子:

import javax.xml.datatype.*;

Duration dur = DatatypeFactory.newInstance().newDuration("PT5H12M36S");
int hours = dur.getHours(); // Should return 5

除了你列出的第 4 个,我个人没有使用任何持续时间格式,所以我不能保证它是否成功解析它们。

【讨论】:

对于这个例子我得到了异常:java.lang.IllegalArgumentException: PT5h12m36s at com.sun.org.apache.xerces.internal.jaxp.datatype.DurationImpl.organizeParts(DurationImpl.java:612) @takacsot 你是对的!示例字符串中的“m”和“s”是小写的,应该是大写的以满足 ISO 8601 规范。固定。 注意 - 这些类不提供一种方法来获取持续时间的长度,而无需将所有组件相加。 最有可能不支持像P1W 这样的周,只允许YMD:github.com/JetBrains/jdk8u_jaxp/blob/…【参考方案3】:

我认为您已经尝试过 Joda-Time?通过Interval.parse(Object) 提供您问题中的示例字符串表明它可以处理“开始和结束”、“开始和持续时间”以及“持续时间和结束”,但不能处理隐含字段或重复。

2007-03-01T13:00:00Z/2008-05-11T15:30:00Z => from 2007-03-01T13:00:00.000Z to 2008-05-11T15:30:00.000Z
2007-03-01T13:00:00Z/P1Y2M10DT2H30M       => from 2007-03-01T13:00:00.000Z to 2008-05-11T15:30:00.000Z
P1Y2M10DT2H30M/2008-05-11T15:30:00Z       => from 2007-03-01T13:00:00.000Z to 2008-05-11T15:30:00.000Z
2007-12-14T13:30/15:30                    => java.lang.IllegalArgumentException: Invalid format: "15:30" is malformed at ":30"
R5/2008-03-01T13:00:00Z/P1Y2M10DT2H30M    => java.lang.IllegalArgumentException: Invalid format: "R5"

据我所知,唯一的其他综合日期/时间库是 JSR-310,它似乎无法处理此类间隔。

此时,在 Joda-Time 之上构建自己的改进可能是您的最佳选择,抱歉。除了 Joda-Time 已经支持的格式之外,您还需要处理任何特定的 ISO 间隔格式吗?

【讨论】:

谢谢巴伦德。 Joda-Time 的文档在此功能上确实很差。我很高兴看到他们支持这种格式。我会使用 Joda。 在我得到 15 分之前我不能投票,但一旦我得到它们就会投票:)【参考方案4】:

唯一能够为你想要的区间解析的所有特征建模的库实际上是我的库Time4J(范围模块)。例子:

// case 1 (start/end)
System.out.println(MomentInterval.parseISO("2012-01-01T14:15Z/2014-06-20T16:00Z"));
// output: [2012-01-01T14:15:00Z/2014-06-20T16:00:00Z)

// case 1 (with some elements missing at end component and different offset)
System.out.println(MomentInterval.parseISO("2012-01-01T14:15Z/08-11T16:00+00:01"));
// output: [2012-01-01T14:15:00Z/2012-08-11T15:59:00Z)

// case 1 (with missing date and offset at end component)
System.out.println(MomentInterval.parseISO("2012-01-01T14:15Z/16:00"));
// output: [2012-01-01T14:15:00Z/2012-01-01T16:00:00Z)

// case 2 (start/duration)
System.out.println(MomentInterval.parseISO("2012-01-01T14:15Z/P2DT1H45M"));
// output: [2012-01-01T14:15:00Z/2012-01-03T16:00:00Z)

// case 3 (duration/end)
System.out.println(MomentInterval.parseISO("P2DT1H45M/2012-01-01T14:15Z"));
// output: [2011-12-30T12:30:00Z/2012-01-01T14:15:00Z)

// case 4 (duration only, in standard ISO-format)
Duration<IsoUnit> isoDuration = Duration.parsePeriod("P2DT1H45M");

// case 4 (duration only, in alternative representation)
Duration<IsoUnit> isoDuration = Duration.parsePeriod("P0000-01-01T15:00");
System.out.println(isoDuration); // output: P1M1DT15H

一些备注:

存在具有类似解析能力的其他区间类,例如 net.time4j.range 包中的 DateIntervalTimestampInterval

仅用于处理持续时间(也可以跨越日历和时钟单位),另请参阅javadoc。还有格式化功能,见嵌套类Duration.Formatter或本地化版本net.time4j.PrettyTime(实际上是86种语言)。

Java-8 (java.time-package) 提供互操作性,但 Joda-Time 不提供。例如:MomentInterval 的开始或结束组件可以通过getStartAsInstant()getEndAsInstant() 轻松查询。

IsoRecurrence 类支持重复间隔。示例:

IsoRecurrence<MomentInterval> ir =
    IsoRecurrence.parseMomentIntervals("R5/2008-03-01T13:00:00Z/P1Y2M10DT2H30M");
ir.intervalStream().forEach(System.out::println);

输出:

[2008-03-01T13:00:00Z/2009-05-11T15:30:00Z)
[2009-05-11T15:30:00Z/2010-07-21T18:00:00Z)
[2010-07-21T18:00:00Z/2011-10-01T20:30:00Z)
[2011-10-01T20:30:00Z/2012-12-11T23:00:00Z)
[2012-12-11T23:00:00Z/2014-02-22T01:30:00Z)

【讨论】:

以上是关于Java 中的 ISO 8601 时间间隔解析的主要内容,如果未能解决你的问题,请参考以下文章

在 Excel 中解析 ISO8601 日期/时间(包括 TimeZone)

识别 ISO 8601 中的时区

java 转储TimeZone信息,以ISO8601 FULL中的当前时间为例

Tabulator 5.0 - 解析日期时间 luxon - 日期时间 ISO 8601

无法解析ISO 8601格式的字符串,缺少冒号的冒号,到Java 8 Date

使用 Java 1.6 接收带有包含 ISO 8601 格式化日期的字符串的 ParseException