Волшебный часовой пояс 00:09 с DatatypeFactory - PullRequest
0 голосов
/ 17 сентября 2018

В java you есть javax.xml.datatype.DatatypeFactory, который можно использовать для импорта и экспорта дат xml следующим образом.

  String xmlDateIn = "1900-01-01T12:00:00";
  DatatypeFactory df = DatatypeFactory.newInstance();
  XMLGregorianCalendar xmlCalendar = df.newXMLGregorianCalendar(xmlDateIn);
  String xmlDateOut = xmlCalendar.toXMLFormat();

В этом простом случае xmlDateIn равно xmlDateOut, как и ожидалось. Но если я хочу это как java.lang.Date, то вещи становятся интересными.

  GregorianCalendar gregorianCalendar = xmlCalendar.toGregorianCalendar();
  Date dts = gregorianCalendar.getTime();
  System.out.println(dts);  // prints Mon Jan 01 12:00:00 CET 1900

На первый взгляд, он все еще работает нормально, но на самом деле что-то внутри кажется что-то сломанным. С помощью своей IDE я вижу, что происходит внутри объекта Date. (Если вам интересно, я живу в часовом поясе CET.) Посмотрите на этот странный часовой пояс.

Timezone is broken

И когда я пытаюсь преобразовать это обратно в XML, 9-минутный часовой пояс также печатается. Так что это не просто внутренняя вещь.

  DatatypeFactory df2 = DatatypeFactory.newInstance();
  GregorianCalendar gc2 = new GregorianCalendar();
  gc2.setTime(dts);
  XMLGregorianCalendar xc2 = df2.newXMLGregorianCalendar(gc2);
  System.out.println(xc2.toXMLFormat()); // prints 1900-01-01T12:00:00.000+00:09 

В попытке исправить это, если я вручную установил часовой пояс, все стало очень плохо. Посмотрите на этот волшебный час:

  String xmlDateIn = "1900-01-01T12:00:00";
  DatatypeFactory df = DatatypeFactory.newInstance();
  XMLGregorianCalendar xmlCalendar = df.newXMLGregorianCalendar(xmlDateIn);
  xmlCalendar.setTimezone(0);  // <--- ONLY CHANGE
  GregorianCalendar gregorianCalendar = xmlCalendar.toGregorianCalendar();
  Date dts = gregorianCalendar.getTime();

Unable to fix.

На самом деле у меня есть обходной путь для моей конкретной программы: у меня сейчас работает то, что я не устанавливаю часовой пояс при импорте xml. Затем Date несет неправильный часовой пояс внутри, то есть 9 минут. Затем, когда я наконец хочу экспортировать Date обратно в xml, я устанавливаю часовой пояс на 0 в григорианском календаре xml, и это волшебным образом исправляет его и снова экспортирует правильный формат xml.

Но на самом деле мне было интересно, есть ли хорошее объяснение этому сумасшедшему поведению.

Ответы [ 2 ]

0 голосов
/ 18 сентября 2018

Благодаря ответу VGR мне удалось выяснить, что пошло не так внутри XMLGregorianCalendarImpl.

Взгляните на конструктор:

public XMLGregorianCalendarImpl(GregorianCalendar cal) {

    int year = cal.get(Calendar.YEAR);
    if (cal.get(Calendar.ERA) == GregorianCalendar.BC) {
        year = -year;
    }
    this.setYear(year);

    // Calendar.MONTH is zero based, XSD Date datatype's month field starts
    // with JANUARY as 1.
    this.setMonth(cal.get(Calendar.MONTH) + 1);
    this.setDay(cal.get(Calendar.DAY_OF_MONTH));
    this.setTime(
            cal.get(Calendar.HOUR_OF_DAY),
            cal.get(Calendar.MINUTE),
            cal.get(Calendar.SECOND),
            cal.get(Calendar.MILLISECOND));

    // Calendar ZONE_OFFSET and DST_OFFSET fields are in milliseconds.
    int offsetInMinutes = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / (60 * 1000);
    this.setTimezone(offsetInMinutes);
}

В основном, что он делает, - сопоставить все внутренние поля GregorianCalendar с внутренними полями.например, год, месяц, день, час, минута, секунда, миллисекунда.

И, наконец, он отображает часовой пояс.Обратите внимание, что объект GregorianCalendar сохраняет свой часовой пояс в миллисекундах, в то время как XMLGregorianCalendarImpl сохраняет его в минутах. Во время этого преобразования он просто отбрасывает оставшиеся секунды и миллисекунды. И вот в чем проблема, у календаря может быть часовой пояс с секундами.

И это подводит нас к следующему примеру:дата "1900-01-01T12:00:00" в часовом поясе ECT.Это фактически имеет смещение зоны 561000 миллисекунд.т.е. 9 минут и 21 секунда.Но xml григорианский просто игнорирует 21 секунду.

Если у часового пояса есть миллисекунды, лучше вывести дату без часового пояса.

private String toXml(Date dts) {

  DatatypeFactory df = DatatypeFactory.newInstance();
  GregorianCalendar gc = new GregorianCalendar();
  gc.setTime(dts);
  XMLGregorianCalendar xc = df.newXMLGregorianCalendar(gc2);

  int zoneOffsetInMillis = gc.get(Calendar.ZONE_OFFSET);
  boolean zoneHasMillis zoneOffsetInMillis % (60 * 1000) != 0;
  if (zoneHasMillis) xc.setTimezone(0);

  return xc.toXMLFormat();
}

Пример:

1900-01-01T12:00:00+02:00 становится 1900-01-01T10:09:21.000

РЕДАКТИРОВАТЬ:

Я на самом деле нашел историческую причину этого изменения времени 9 минут и 21 секунд:

В Британии 'Железнодорожное время »было введено в 1840-х годах для синхронизации местных часов с расписанием поездов, которое в 1880 году было заменено единым временем по Гринвичу. В 1891 году Франция приняла Парижское среднее время в качестве стандартного национального времени.Часы на железнодорожных станциях и расписание поездов были установлены на пять минут позже, чтобы пассажиры не опоздали на свои поезда.

В 1911 году парижское среднее время было изменено на 9 минут 21 секунду для синхронизации со средним временем по Гринвичу.Он по-прежнему назывался Парижским средним временем, в котором не использовалось слово «Гринвич».

источник: https://vanessafrance.wordpress.com/2012/03/25/a-brief-history-of-french-time/

0 голосов
/ 17 сентября 2018

Я не очень много знаю об истории календаря и официальном хронометраже, поэтому я проверил это, сначала убедившись, что я использую ваш часовой пояс:

int offset = (int) TimeUnit.HOURS.toMillis(1);
String[] ids = TimeZone.getAvailableIDs(offset);
TimeZone cet = Arrays.stream(ids).map(TimeZone::getTimeZone)
    .filter(tz -> tz.getDisplayName(false, TimeZone.SHORT).equals("CET"))
    .findFirst().orElseThrow(
        () -> new RuntimeException("No CET timezone found"));

TimeZone.setDefault(cet);

Затем я рассмотрел некоторые внутренние механизмы этогочасовой пояс.В частности, я распечатал его исторические временные переходы:

System.out.println("Transitions:");
cet.toZoneId().getRules().getTransitions().forEach(
    t -> System.out.println("  " + t));

Первые два таких перехода распечатаны как:

Transition[Overlap at 1891-03-15T00:01+00:12:12 to +00:09:21]
Transition[Overlap at 1911-03-11T00:00+00:09:21 to Z]

И за ними следуют различные «ручные» переходы между Зулу(Z) и UTC + 01: 00.

Итак, полночь 1900 года фактически была на 9 минут и 21 секунду позже полуночи соответствующего дня 1912 года.

Действительно, если вы изменитес 1912 года вы не увидите 9-минутного расхождения:

String xmlDateIn = "1912-01-01T12:00:00";

Мне не удалось найти историческую причину переходов 12:12 или 9:21.Я предполагаю, что это был только вопрос науки, поскольку астрономические измерения стали более точными.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...