ZonedDateTime toString 与 ISO 8601 的兼容性

Posted

技术标签:

【中文标题】ZonedDateTime toString 与 ISO 8601 的兼容性【英文标题】:ZonedDateTime toString compatability with ISO 8601 【发布时间】:2017-09-23 20:36:00 【问题描述】:

我正在努力确保在我的 ZonedDateTime 对象上调用 toString() 将符合 ISO-8601 格式。

toString() 方法的文档指出:

...如果偏移量和 ID 是 一样的

这是否意味着存在调用 zdt.getOffset() 将返回不同于 zdt.getZone().getRules().getOffset(zdt.toInstant()) ?

这似乎没有意义。

谁能提供一个偏移量和 ID 不相同的例子(即:toString() 不符合 ISO-8601),以便我更好地理解文档中的描述。

【问题讨论】:

【参考方案1】:

这是完整的规范:

 * Outputs this date-time as a @code String, such as
 * @code 2007-12-03T10:15:30+01:00[Europe/Paris].
 * <p>
 * The format consists of the @code LocalDateTime followed by the @code ZoneOffset.
 * If the @code ZoneId is not the same as the offset, then the ID is output.
 * The output is compatible with ISO-8601 if the offset and ID are the same.

Javadoc 规范指的是ZonedDateTime 是用ZoneOffset 而不是命名ZoneId 构造的情况,因此偏移量和ID 相同:

System.out.println(ZonedDateTime.now(ZoneId.of("Europe/Paris")));
// 2017-04-26T15:13:12.006+02:00[Europe/Paris]

System.out.println(ZonedDateTime.now(ZoneOffset.ofHours(2)));
// 2017-04-26T15:13:12.016+02:00

可以看出,在第二种情况下,使用ZoneOffsettoString() 格式省略了末尾的方括号部分。通过省略该部分,结果与 ISO-8601 兼容。

boolean iso8601Compatible = zdt.getZone() instanceof ZoneOffset;

为保证输出与 ISO-8601 兼容,请使用 toOffsetDateTime()

String isoCompatible = zdt.toOffsetDateTime().toString();

或格式化程序。

【讨论】:

“或格式化程序”:DateTimeFormatter.ISO_OFFSET_DATE_TIME 浮现在脑海中。 插入 toOffsetDateTime() 效果很好。【参考方案2】:

the documentation 中的示例是2007-12-03T10:15:30+01:00[Europe/Paris]。这恰好不符合 ISO 标准,因为 ISO-8601 不包括 [Europe/Paris] 部分。这是java.time 开发人员添加的,目的是在尽可能接近标准和仍然以明确的方式提供时区信息之间进行折衷。

所以真正的问题实际上可能是相反的:如果ZonedDateTime.toString() 包含 ISO 不包含的时区信息,那么 结果何时完全符合 ISO 标准? “如果偏移量和 ID 相同”是什么意思?这里我们必须记住ZoneOffsetZoneID 的子类,并且可以用作ZonedDateTime 中的区域ID。在这种情况下,偏移量和 ID 相同。否则他们不是。对于特定示例,ZonedDateTime.now(ZoneOffset.ofHours(+2)).toString() 可能会产生 2017-04-26T15:04:59.828+02:00。这与 ISO 完全兼容,因为该区域仅以 +02:00 给出,与偏移量相同。 ZonedDateTime.now(ZoneOffset.UTC).toString() 也给出了 2017-04-26T13:04:59.828Z 格式的内容。由于Z 算作偏移量,所以这也是兼容的。

我认为在大多数情况下它不会很有用。如果您的区域只是一个偏移量,您通常更喜欢使用OffsetDateTime 而不是ZonedDateTime,如果是这样,您当然不在乎ZonedDateTime.toString() 是否兼容ISO。

【讨论】:

【参考方案3】:

根据官方文档:

获取瞬间的偏移量很简单,因为每个瞬间都有一个有效的偏移量。相比之下,获取本地日期时间的偏移量并不简单。分三种情况:

正常,只有一个有效偏移量。对于一年中的大部分时间,正常情况适用,本地日期时间有一个有效的偏移量。 间隙,有效偏移量为零。这是由于春季夏令时从“冬季”变为“夏季”,时钟通常会向前跳动。在间隙中存在没有有效偏移的本地日期时间值。 重叠,有两个有效偏移。这是由于秋季夏令时从“夏季”变为“冬季”,时钟通常会被调慢。在重叠中,有两个有效偏移量的本地日期时间值。

所以第 2 和第 3 种情况是 toString() 不符合 ISO-8601 的情况。

【讨论】:

问题是关于ZonedDateTime,而不是LocalDateTimeZonedDateTime 唯一标识一瞬间,所以没看出问题?另一方面,正如您在案例 2 和 3 中所解释的那样,LocalDateTime 和时区 (ZoneId) 一起并不总是唯一标识一个瞬间,这是正确的。 @OleV.V.你已经回答了你自己的问题,我的朋友。对于无法识别唯一 Instant 的情况,toString() 将不符合 ISO-8601。 虽然此信息很有趣,但不幸的是,它是该问题的错误答案。在这种情况下,间隙和重叠与 ISO-8601 无关。【参考方案4】:

我喜欢上面 JodaStephen 的最后一条评论'or a formatter',因为无论数据如何,使用 formatter 都会更加健壮。 原因是 OffsetDateTime toString() 在第二个单位及以下没有值时跳过第二个单位部分,所以它最终给出 yyyy-MM-ddTHH:mmZ 而不是 yyyy-MM- ddTHH:mm:ssZ。如果其他系统需要静态格式并且没有适应性,这可能会导致麻烦。

下面是我用来模拟没有时间部分和有时间部分的两种情况的代码。

/**
 * This function is design to convert Date object from other system to ISO dateTime String 
 * which will be sent to another system. and using formatter to lock up the format
 * @param date java.util.Date
 * @return 'yyyy-MM-ddTHH:mm:ssZ' format ISO dateTime string
 */
public String formatIsoUtcDateTime(Date date) 
    if(null == date) 
        return null;
    
    return ZonedDateTime.ofInstant(date.toInstant(), ZoneId.of("UTC"))
                        .format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"));


// no time portion with using formatter
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
Date date = sdf.parse("20171010");
System.out.println(formatIsoUtcDateTime(date));

// no time portion with using OffsetDateTime toString
System.out.println(ZonedDateTime.ofInstant(date.toInstant(), ZoneId.of("UTC")).toOffsetDateTime().toString());

// just current date and probably has at least millisecond, using formatter
System.out.println(formatIsoUtcDateTime(new Date()));

// just current date and probably has at least millisecond, using OffsetDateTime toString
// this will print yyyy-MM-ddTHH:mm:ss.SSSZ format
System.out.println(ZonedDateTime.ofInstant(new Date().toInstant(), ZoneId.of("UTC")).toOffsetDateTime().toString());

【讨论】:

以上是关于ZonedDateTime toString 与 ISO 8601 的兼容性的主要内容,如果未能解决你的问题,请参考以下文章

Java - 比较两个 ZonedDateTime 时的结果与预期不符

即时与 ZonedDateTime

《日期与时间》第7节:ZonedDateTime与OffsetDateTime

Java8- ZonedDateTime 与 DateTimeFormatter 无法识别格式

Java日期时间API系列19-----Jdk8中java.time包中的新的日期时间API类,ZonedDateTime与ZoneId和LocalDateTime的关系,ZonedDateTime格

JAVA知识盲区整理2