【问题标题】:Zone adjustment not taken into account when parsing RFC 3339 date time解析 RFC 3339 日期时间时未考虑区域调整
【发布时间】:2020-01-01 13:27:44
【问题描述】:

(从CodeReview迁移而来)

我正在试验并试图更好地理解 Java Time。我的代码没有按预期工作,我想问一下原因,可能是因为我对 JSR-310 时区处理存在误解。

我有一个时间戳字符串可以用DateTimeFormatter解析

String 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)中显示的时区相同时,以下测试有效

@Test
public 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 Time 如何调整时区。但结果出乎意料地错误!

我尝试反复更改输入字符串中的时区,以使测试失败。例如,如果我将字符串更改为2019-08-28T10:39:57+08:00,则表示现在是巴黎凌晨 2 点。但是在检查一天中的时间时,上面的测试代码继续通过。 IE。在生成的LocalDateTime 中,一天中的时间仍然是上午 10 点。

问题

为什么我的代码仍然返回 10 作为一天中的 local 时间,而不管我在源字符串中不断更改其时区这一事实?

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

环境

我需要将时间戳与计算机时间进行比较,并检查它是否太旧。这意味着如果在欧洲评估美国时间,它可能不会“太旧”。

【问题讨论】:

    标签: java datetime datetime-parsing jsr310 rfc3339


    【解决方案1】:
        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 上正确运行。

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

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

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

        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 格式变体开箱即用,无需任何显式格式化程序,因此我们在这里不需要格式化程序。顺便说一句,您分配给格式化程序的区域没有任何区别,因为使用了字符串中的偏移量。

    链接: Wikipedia article: ISO 8601

    【讨论】:

      【解决方案2】:

      ZonedDateTime 获取LocalDateTime 只是削减时区。它的价值无关紧要。您需要做的是将您的ZonedDateTime 从 +8 区转换为 +2 区,这将相应地改变时间。然后您可以从修改后的ZonedDateTime 中获取LocalDateTime 以获得所需的效果。这是一个演示它的示例:

      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。

      【讨论】:

        猜你喜欢
        • 2011-08-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多