将XMLGregorianCalendar转换为LocalDateTime时区不一致 [英] Timezone inconsistencies on converting XMLGregorianCalendar to LocalDateTime

查看:544
本文介绍了将XMLGregorianCalendar转换为LocalDateTime时区不一致的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以我有一个带有日期/时间字段的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版本 11.0.1 2018-10- 16
OpenJDK运行时环境18.9(内部版本11.0.1 + 13)
OpenJDK 64位服务器VM 18.9(内部版本11.0.1 + 13,混合模式)

Java版本 1.8.0_121
Java SE运行时环境(内部版本1.8.0_121-b13)
Java HotSpot(TM)64位服务器VM(内部版本25.121-b13,混合模式)


解决方案

LocalDateTime.parse()



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

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

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

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

因为 XMLGregorianCalendar的所谓时区 实际上只是固定的偏移量,与我们设置的值无关。该代码段的输出始终是:


07:20


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



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



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

  LocalDateTime.parse(xmllGregoriaCalendar.toXMLFormat())

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



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



对我来说,问题的核心在于旧的和过时的 TimeZone 类和现代的 ZoneId



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

  LocalDate baseDate = LocalDate.of(1899,月(12月30日); 

ZoneId柏林= ZoneId.of(欧洲/柏林);
TimeZone tzb = TimeZone.getTimeZone(berlin);
GregorianCalendar gcb =新的GregorianCalendar(tzb);
gcb.set(1899,Calendar.DECEMBER,30);
ZonedDateTime zdtb = baseDate.atStartOfDay(柏林);
System.out.println( +柏林+''+ 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 类说3 600 000毫秒, ZoneId 说3600秒,所以他们同意。



问题出在华沙。华沙在1915年之前一直处于格林尼治标准时间+01:24偏移。让我们看看Java是否可以找出:

  ZoneId华沙= ZoneId.of(欧洲/华沙); 
TimeZone tzw = TimeZone.getTimeZone(warsaw);
GregorianCalendar gcw =新的GregorianCalendar(tzw);
gcw.set(1899,Calendar.DECEMBER,30);
ZonedDateTime zdtw = baseDate.atStartOfDay(warsaw);
System.out.println( +华沙+''+ 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 类,因此在使用欧洲/华沙时区(显式或默认)时会产生错误的结果。特别是,您从 Calendar.toInstant()中得到了错误的 Instant 。正是因为 LocalDateTime.ofInstant 使用了现代的 ZoneId ,错误才会在您的中进行LocalDateTime



同样来自欧洲/都柏林,欧洲/巴黎,欧洲/莫斯科和亚洲/加尔各答时区,我得出的结论也矛盾。 p>

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



链接




So I have an XML Soap response with date / time fields, which are represented as follows:

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

I'm not interested in the date (as this is some legacy representation which I don't have control over) but the time. The field is transformed to XMLGregorianCalendar by the WS tooling and I'm aiming to do the conversion.

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

Converting to LocalDateTime is siLocalimple. I'm setting TimeZone explicitly to avoid locality confuction

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

which results in 1899-12-30T07:44

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

gives me a different output 1899-12-30T07:20

When dates start in modern days (after 1900 and after) - everything works fine. So the question is: what exactly happened between Berlin and Warsaw on the turn of XIX century? Or put it more clearly - why the change in the time is so weird?

I'm running is on both JDK8 and JDK11 (observing the same behavior)

{ ~ }  » 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)

解决方案

LocalDateTime.parse()

If you can get the string out of the XML without too much trouble, for a predictable result use

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

Edit: Without direct access to the string I suggest that the solution is to set an offset from GMT/UTC on your XMLGregorianCalendar to avoid any dependency on the JVM’s default time zone:

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

Since the so-called "timezone" of an XMLGregorianCalendar is really nothing but a fixed offset, it doesn’t matter which value we set. The output from this snippet consistently is:

07:20

I have tested with nine different default time zones including Europe/Warsaw.

Since you said you were only interested in the time of day, not the date, I have converted to LocalTime. If you want a LocalDateTime as in your question, just use toLocalDateTime instead of toLocalTime,

Alternatively, this was your easy solution from your comment:

    LocalDateTime.parse(xmllGregoriaCalendar.toXMLFormat​())

toXMLFormat​() recreates the string from the XML that the XMLGregorianCalendar object was created from (the docs guarantee that you get the same string back). So this way too evades all time zone problems.

Edit: disagreement between old and new classes

It seems to me that the heart of the problem lies in the old and outdated TimeZone class and the modern ZoneId class not agreeing about historic offsets from GMT/UTC.

I did a couple of experiments. Let’s first try the time zone that seems to work correctly, Berlin. Berlin was at offset +01:00 from 1894 to 1915. Java knows that:

    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());

Output from this snippet is:

Europe/Berlin 3600000 +01:00 3600

The offset for 30 December 1899 is given correctly as +01:00. The TimeZone class says 3 600 000 milliseconds, ZoneId says 3600 seconds, so they agree.

The trouble is with Warsaw. Warsaw was at GMT offset +01:24 all the time up to 1915. Let’s see if Java can find out:

    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());

Europe/Warsaw 3600000 +01:24 5040

ZoneId correctly says +01:24 or 5040 seconds, but here TimeZone says 3 600 000 milliseconds, the same as in the Berlin case. This is incorrect.

The old GregorianCalendar class relies on the old TimeZone class and therefore produces wrong results when using Europe/Warsaw time zone (either explicitly or as default). In particular you get the wrong Instant from Calendar.toInstant(). And exactly because LocalDateTime.ofInstant uses a modern ZoneId, the error is carried on into your LocalDateTime.

Also from Europe/Dublin, Europe/Paris, Europe/Moscow and Asia/Kolkata time zones I get contradictory results.

I have run my snippets on Java 1.8.0_131, Java 9.0.4 and Java 11. The results were the same on all versions.

Links

这篇关于将XMLGregorianCalendar转换为LocalDateTime时区不一致的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆