Несоответствия часовых поясов при преобразовании XMLGregorianCalendar в LocalDateTime - PullRequest
0 голосов
/ 23 января 2019

Итак, у меня есть ответ XML Soap с полями даты / времени, которые представлены следующим образом:

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

Меня не интересует дата (поскольку это какое-то устаревшее представление, которое я не могу контролировать), но время. Поле преобразуется в XMLGregorianCalendar с помощью инструментов WS, и я собираюсь выполнить преобразование.

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 года и после) - все работает отлично. Итак, вопрос: что именно произошло между Берлином и Варшавой на рубеже XIX века? Или, если выразить это более четко - почему изменение во времени так странно ?

Я работаю на 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 голосов
/ 24 января 2019

LocalDateTime.parse ()

Если вы можете получить строку из XML без особых проблем, для предсказуемого результата используйте

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

Редактировать: Без прямого доступа к строке я полагаю, что решение состоит в том, чтобы установить смещение от GMT / UTC на вашем XMLGregorianCalendar, чтобы избежать любой зависимости от часового пояса 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​() воссоздает строку из XML, из которой был создан объект XMLGregorianCalendar (документы гарантируют, что вы получите ту же строку обратно). Таким образом, этот способ также избегает всех проблем с часовыми поясами.

Редактировать: разногласия между старым и новым классами

Мне кажется, что суть проблемы заключается в старом и устаревшем TimeZone классе и современном ZoneId классе, не согласном с историческими смещениями из GMT / UTC.

Я провел пару экспериментов. Давайте сначала попробуем часовой пояс, который, кажется, работает правильно, Берлин. Берлин был по смещению +01: 00 с 1894 по 1915 год. Ява знает, что:

    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

Смещение на 30 декабря 1899 года дается правильно как +01: 00. Класс TimeZone говорит 3 600 000 миллисекунд, ZoneId говорит 3600 секунд, поэтому они согласны.

Беда с Варшавой. Варшава все время находилась в смещении по Гринвичу +01: 24 до 1915 года. Посмотрим, сможет ли 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. Результаты были одинаковыми для всех версий.

Ссылки

...