将XMLGregorianCalendar转换为LocalDateTime时区不一致

时间:2019-01-23 11:20:33

标签: java datetime time timezone xmlgregoriancalendar

因此我有一个带有日期/时间字段的XML Soap响应,其表示如下:

<BusStopTime>
    <BusStopId>1023</BusStopId>
    <Order>1</Order>
    <PassingTime>1899-12-30T07:20:00</PassingTime>
</BusStopTime>

我对日期不感兴趣(因为这是一些我无法控制的传统表示),但对时间不感兴趣。 WS工具将该字段转换为XMLGregorianCalendar,我的目标是进行转换。

var date = DatatypeFactory.newInstance()
    .newXMLGregorianCalendar("1899-12-30T07:20:00")
    .toGregorianCalendar().toInstant()

转换为LocalDateTime是siLocalimple。我正在明确设置TimeZone以避免位置混淆

LocalDateTime.ofInstant(date, ZoneId.of("Europe/Warsaw"))

这将导致 1899-12-30T07:44

LocalDateTime.ofInstant(date, ZoneId.of("Europe/Berlin"))

为我提供了不同的输出 1899-12-30T07:20

当日期从现代开始(1900年及之后)时,一切正常。所以问题是:十九世纪初,柏林和华沙之间到底发生了什么?或者说得更清楚-为什么时间变化如此古怪

我正在JDK8和JDK11上运行(观察到相同的行为)

{ ~ }  » java -version                                                                                                                                              
openjdk version "11.0.1" 2018-10-16
OpenJDK Runtime Environment 18.9 (build 11.0.1+13)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.1+13, mixed mode)

java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)

1 个答案:

答案 0 :(得分:2)

LocalDateTime.parse()

如果您可以轻松地从XML中提取字符串,请使用可预测的结果

    LocalDateTime.parse("1899-12-30T07:20:00")

编辑:我建议不直接访问字符串,建议的解决方案是在XMLGregorianCalendar上设置GMT / UTC的偏移量,以避免依赖于JVM的默认时区:

    XMLGregorianCalendar xgc = DatatypeFactory.newInstance()
            .newXMLGregorianCalendar("1899-12-30T07:20:00");
    xgc.setTimezone(0);
    LocalTime time = xgc.toGregorianCalendar()
            .toZonedDateTime()
            .toLocalTime();
    System.out.println(time);

由于XMLGregorianCalendar的所谓“时区”实际上只是固定的偏移量,因此设置哪个值都无所谓。该代码段的输出始终是:

  

07:20

我已经用九个不同的默认时区进行了测试,包括欧洲/华沙。

由于您说的是您只对一天中的时间感兴趣,而不对日期感兴趣,所以我已转换为LocalTime。如果您要在问题中使用LocalDateTime,只需使用toLocalDateTime而不是toLocalTime

或者,这是从您的评论中得出的简单解决方案:

    LocalDateTime.parse(xmllGregoriaCalendar.toXMLFormat​())

toXMLFormat​()从创建XMLGregorianCalendar对象的XML中重新创建字符串(文档保证返回相同的字符串)。因此,这种方式也可以避免所有时区问题。

编辑:新旧班级之间的分歧

在我看来,问题的核心在于古老而过时的TimeZone类和现代ZoneId类,它们与GMT / UTC的历史性偏差并不一致。

我做了几个实验。首先,让我们尝试一下似乎可以正常工作的时区,柏林。从1894年到1915年,柏林的偏移量为+01:00。Java知道:

    LocalDate baseDate = LocalDate.of(1899, Month.DECEMBER, 30);

    ZoneId berlin = ZoneId.of("Europe/Berlin");
    TimeZone tzb = TimeZone.getTimeZone(berlin);
    GregorianCalendar gcb = new GregorianCalendar(tzb);
    gcb.set(1899, Calendar.DECEMBER, 30);
    ZonedDateTime zdtb = baseDate.atStartOfDay(berlin);
    System.out.println("" + berlin + ' ' + tzb.getOffset(gcb.getTimeInMillis())
            + ' ' + berlin.getRules().getOffset(zdtb.toInstant())
            + ' ' + berlin.getRules().getOffset(zdtb.toInstant()).getTotalSeconds());

此代码段的输出为:

  

欧洲/柏林3600000 +01:00 3600

1899年12月30日的偏移量正确指定为+01:00。 TimeZone类说3600万毫秒,ZoneId类说3600秒,所以他们同意。

麻烦出在华沙。直到1915年,华沙一直处于GMT偏移+01:24的时间。让我们看看Java是否可以找出:

    ZoneId warsaw = ZoneId.of("Europe/Warsaw");
    TimeZone tzw = TimeZone.getTimeZone(warsaw);
    GregorianCalendar gcw = new GregorianCalendar(tzw);
    gcw.set(1899, Calendar.DECEMBER, 30);
    ZonedDateTime zdtw = baseDate.atStartOfDay(warsaw);
    System.out.println("" + warsaw + ' ' + tzw.getOffset(gcw.getTimeInMillis())
            + ' ' + warsaw.getRules().getOffset(zdtw.toInstant())
            + ' ' + warsaw.getRules().getOffset(zdtw.toInstant()).getTotalSeconds());
  

欧洲/华沙3600000 +01:24 5040

ZoneId正确地说是+01:24或5040秒,但是这里TimeZone说了3 600 000毫秒,与柏林的情况相同。这是不正确的。

旧的GregorianCalendar类依赖于旧的TimeZone类,因此在使用欧洲/华沙时区(显式或默认)时会产生错误的结果。特别是您从Instant那里得到了错误的Calendar.toInstant()。正是因为LocalDateTime.ofInstant使用了现代的ZoneId,所以错误会持续到您的LocalDateTime中。

我也从欧洲/都柏林,欧洲/巴黎,欧洲/莫斯科和亚洲/加尔各答时区得到了矛盾的结果。

我已经在Java 1.8.0_131,Java 9.0.4和Java 11上运行了代码片段。结果在所有版本上都是相同的。

链接