为了账号安全,请及时绑定邮箱和手机立即绑定

解析 RFC 3339 日期时间时不考虑区域调整

解析 RFC 3339 日期时间时不考虑区域调整

动漫人物 2023-06-21 13:58:02
(从CodeReview迁移)我正在尝试并试图更好地理解 Java Time。我的代码没有按预期工作,我想问问原因,可能是因为我对 JSR-310 时区处理有误解。我有一个时间戳字符串要解析DateTimeFormatterString text = "2019-08-28T10:39:57+02:00";DateTimeFormatter ISO_RFC_3339_PARSER = DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.of("Europe/Paris"));上述值是在服务器当地时间为中欧上午 10:39 时生成的。由于夏令时,UTC 时间为早上 8:39。您可以自己计算一下您所在区域的时间。当计算机的时区与时间戳中显示的时区相同(+2)时,以下测试有效@Testpublic void testTimestamp(){    LocalDateTime time = ZonedDateTime.from(ISO_RFC_3339_PARSER.parse(text)).toLocalDateTime();    errorCollector.checkThat(time.getYear(), is(2019));    errorCollector.checkThat(time.getMonthValue(), is(8));    errorCollector.checkThat(time.getDayOfMonth(), is(28));    errorCollector.checkThat(time.getHour(), is(10));    errorCollector.checkThat(time.getMinute(), is(39));    errorCollector.checkThat(time.getSecond(), is(57));}我尝试修改输入字符串以更好地理解 Java 时间如何调整时区。但结果却大错特错!我尝试反复更改输入字符串中的时区,因此测试失败。例如,如果我将字符串更改为,2019-08-28T10:39:57+08:00则表示现在是巴黎凌晨 2 点。但在检查一天中的小时时,上述测试代码继续通过。即结果中的LocalDateTime时间仍然是上午 10 点。问题为什么我的代码仍然返回 10 作为当地时间,尽管我不断更改源字符串中的时区?那么解析 RFC 3339 字符串(表示嵌入偏移中的瞬间)并将其转换为LocalDateTime可能的区域调整对象的正确方法是什么?假设机器在 CE[S]T 时区运行。环境我需要将时间戳与计算机时间进行比较,并检查它是否太旧。这意味着如果在欧洲评估美国时间,它可能不会“太旧”。
查看完整描述

2 回答

?
元芳怎么了

TA贡献1798条经验 获得超7个赞

LocalDateTime从ZonedDateTime时区的简单切割中获取。它的价值无关紧要。您需要做的是将您的ZonedDateTime区域从+8 区转换为+2 区,这将相应地改变时间。然后就可以LocalDateTime从你的修改中ZonedDateTime得到想要的效果了。这是一个演示它的示例:


private static void testDateParsing() {

    ZonedDateTime zdt = ZonedDateTime.parse("2019-08-28T10:39:57+08:00", DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZZZZZ"));

    ZonedDateTime modifiedZdt = zdt.withZoneSameInstant(ZoneId.systemDefault());

    System.out.println(zdt);

    System.out.println(modifiedZdt);

    System.out.println(LocalDateTime.from(zdt));

    System.out.println(LocalDateTime.from(modifiedZdt));

}

输出是:


2019-08-28T10:39:57+08:00

2019-08-28T05:39:57+03:00[Asia/Jerusalem]

2019-08-28T10:39:57

2019-08-28T05:39:57

请注意,我的本地时区是+03,而不是您的+02。


查看完整回答
反对 回复 2023-06-21
?
慕的地10843

TA贡献1785条经验 获得超8个赞

    String text = "2019-08-28T10:39:57+02:00";

    OffsetDateTime time = OffsetDateTime.parse(text);

    System.out.println("Hour of day is " + time.getHour());

输出是(如您所料):


一天中的小时是 10


尝试不同的偏移量:


    String text = "2019-08-28T10:39:57+08:00";

一天中的小时是 10


所以仍然相同,仍然是字符串中给出的小时。这就是 10 的由来。我从你的代码中选择了一个稍微简单的代码示例,但它演示的行为与你的代码相同,没有区别,也足以解释。


需要注意的一点是,代码不依赖于 JVM 的时区设置(您报告为欧洲/巴黎)。那挺好的。这意味着,如果我们相信代码是正确的,我们也可以相信它能够在所有时区的所有 JVM 上正确运行。


LocalDateTime在您的代码中,您通过调用转换为toLocalDateTime(). 通常这种转换没有任何充分的理由,因为OffsetDateTime或ZonedDateTime很好地实现了目的。OffsetDateTime转换的作用是保留或中的日期和时间(挂钟时间)ZonedDateTime并丢弃偏移量或时区信息。因此,如果一天中的小时在转换前为 10,则转换后也将为 10。如果您期望转换为当地时间(欧洲/巴黎),您可能会感到困惑。不执行此类转换。Local在 java.time 名称中意味着:没有时区或偏移量。有人认为该词的使用Local因为这个含义具有误导性(从某种意义上说,它是历史性的,因为它是从 java.time 的先驱 Joda-Time 接管的)。


那么解析 RFC 3339 字符串(表示嵌入偏移中的瞬间)并将其转换为 LocalDateTime可能的区域调整对象的正确方法是什么?假设机器在 CE[S]T 时区运行。


解析已经可以正确地为您工作。您可能不需要转换。可以直接比较。和的和方法在isBefore比较时会考虑不同的偏移量或时区,并将为您提供所需的结果。如果您确实想转换:isEqualisAfterOffsetDateTimeZonedDateTime


    String text = "2019-08-28T10:39:57+08:00";

    OffsetDateTime time = OffsetDateTime.parse(text);

    System.out.println("Hour of day is " + time.getHour());


    ZoneId myTimeZone = ZoneId.of("Europe/Paris");

    ZonedDateTime timeInFrance = time.atZoneSameInstant(myTimeZone);

    System.out.println("Hour of day in France is " + timeInFrance.getHour());

Hour of day is 10

Hour of day in France is 4

同样,您可以转换为LocalDateTime并保留 4 点,但我不明白您为什么应该这样做。


关于我对代码的简化:您的 RFC 3339 字符串包含与 UTC (+02:00) 的偏移量,而不是时区(如欧洲/巴黎或欧洲/罗马)。所以用ZonedDateTime它来解析就有点矫枉过正了;OffsetDateTime是一个更好的选择。字符串格式符合 ISO 8601。java.time 类可以直接解析最常见的 ISO 8601 格式变体,无需任何显式格式化程序,因此我们在这里不需要格式化程序。顺便说一句,您分配给格式化程序的区域没有任何区别,因为使用了字符串中的偏移量。


查看完整回答
反对 回复 2023-06-21
  • 2 回答
  • 0 关注
  • 299 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信