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

时间:2019-08-28 10:30:43

标签: java datetime datetime-parsing jsr310 rfc3339

(从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点。但是当检查一天中的小时时,上面的测试代码会继续通过。即在产生的LocalDateTime中,一天中的时间仍然是上午10点。

问题

为什么无论我在源字符串中不断更改其时区,我的代码仍会返回10作为当地时间?

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

环境

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

2 个答案:

答案 0 :(得分: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上正确运行。

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

  

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

解析已经对您正确。您可能不需要转换。您可以直接比较。 isBeforeisEqual的{​​{1}},isAfterOffsetDateTime方法在比较时会考虑不同的偏移量或时区,并会为您提供所需的结果。如果您要转换:

ZonedDateTime
    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 并保留4小时,但我不知道为什么。

关于我的代码简化:您的RFC 3339字符串包含UTC的偏移量(+02:00),而不是时区(例如,欧洲/巴黎或欧洲/罗马)。因此,使用LocalDateTime进行解析是过大的; ZonedDateTime比较合适。字符串格式与ISO 8601一致。java.time类无需使用任何显式格式化程序即可直接解析最常见的ISO 8601格式变体,因此此处不需要格式化程序。顺便说一句,分配给格式化程序的区域没有区别,因为使用了字符串中的偏移量。

链接: Wikipedia article: ISO 8601

答案 1 :(得分:1)

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。