it-swarm.com.ru

SimpleDateFormat с немецким языковым стандартом - Java 8 против Java 10+

У меня есть код и контрольный пример в унаследованном приложении, которое можно обобщить следующим образом:

@Test
public void testParseDate() throws ParseException {
    String toParse = "Mo Aug 18 11:25:26 MESZ +0200 2014";
    String pattern = "EEE MMM dd HH:mm:ss z Z yyyy";

    DateFormat dateFormatter = new SimpleDateFormat(pattern, Locale.GERMANY);
    Date date = dateFormatter.parse(toParse);

    //skipped assumptions
}

Этот тест проходит в Java 8 и ниже. Однако с Java 10 и выше это приводит к Java.text.ParseException: Unparseable date: "Mo Aug 18 11:25:26 MESZ +0200 2014".

Для записи: Кроме de_DE, исключение также выдается для локалей de_CH, de_AT, de_LU.

Мне известно о том, что форматирование даты было изменено в JDK 9 ( JEP 252 ). Однако я считаю, что это разрушительное изменение, нарушающее обратную совместимость. Выдержки:

В JDK 9 данные репозитория общих локали (CLDR) Консорциума Unicode включены в качестве данных локали по умолчанию, так что вы можете использовать стандартные данные локали без каких-либо дополнительных действий.

В JDK 8, хотя данные локали CLDR связаны с JRE, по умолчанию они не включены.

Код, использующий службы, чувствительные к локали, такие как форматирование даты, времени и чисел, может давать разные результаты с данными локали CLDR.

Добавление . для дня недели (Mo.) компенсирует это, и тест будет пройден. Однако это не является реальным решением для старых данных (в сериализованной форме, такой как XML).

При проверке этого сообщение stackoverflow кажется, что поведение является преднамеренным для немецкого языка и его можно смягчить, указав Java.locale.providers с режимом COMPAT. Однако мне не нравится идея полагаться на какое-то значение системного свойства по двум причинам:

  1. изменить в следующих выпусках JDK.
  2. быть забытым в разных средах.

Мой вопрос:

  • Как я могу поддерживать обратную совместимость унаследованного кода с этим конкретным шаблоном даты, без перезаписи/изменения существующих сериализованных данных или добавления/изменения системных свойств (например, Java.locale.providers), что может быть забыто в разных средах (серверы приложений, автономные банки, ...)?
49
rzo

Я не говорю, что это хорошее решение, но, похоже, оно проходит.

_    Map<Long, String> dayOfWeekTexts = Map.of(1L, "Mo", 2L, "Di", 
            3L, "Mi", 4L, "Do", 5L, "Fr", 6L, "Sa", 7L, "So");
    Map<Long, String> monthTexts = Map.ofEntries(Map.entry(1L, "Jan"), 
            Map.entry(2L, "Feb"), Map.entry(3L, "Mär"), Map.entry(4L, "Apr"),
            Map.entry(5L, "Mai"), Map.entry(6L, "Jun"), Map.entry(7L, "Jul"),
            Map.entry(8L, "Aug"), Map.entry(9L, "Sep"), Map.entry(10L, "Okt"),
            Map.entry(11L, "Nov"), Map.entry(12L, "Dez"));

    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .appendText(ChronoField.DAY_OF_WEEK, dayOfWeekTexts)
            .appendLiteral(' ')
            .appendText(ChronoField.MONTH_OF_YEAR, monthTexts)
            .appendPattern(" dd HH:mm:ss z Z yyyy")
            .toFormatter(Locale.GERMANY);

    String toParse = "Mo Aug 18 11:25:26 MESZ +0200 2014";
    OffsetDateTime odt = OffsetDateTime.parse(toParse, formatter);
    System.out.println(odt);
    ZonedDateTime zdt = ZonedDateTime.parse(toParse, formatter);
    System.out.println(zdt);
_

Выход работает на моем Oracle JDK 10.0.1:

_2014-08-18T11:25:26+02:00
2014-08-18T11:25:26+02:00[Europe/Berlin]
_

Опять же, не может быть хорошего решения.

_Java.time_, современный Java API даты и времени, позволяет нам указывать тексты, используемые для полей как для форматирования, так и для синтаксического анализа. Поэтому я использую это как для дня недели, так и для месяца , указав аббревиатуры без точки, которые использовались со старыми данными локали COMPAT или JRE. Я использовал Java 9 _Map.of_ и _Map.ofEntries_ для построения карт, которые нам нужны) Если это должно работать в Java 8 тоже, вы должны найти какой-то другой способ заполнить две карты, я надеюсь, вы это сделаете.

Если вам нужен старомодный _Java.util.Date_ (вероятно, в устаревшей базе кода), выполните преобразование следующим образом:

_    Date date = Date.from(odt.toInstant());
    System.out.println("As legacy Date: " + date);
_

Вывод в моем часовом поясе (Европа/Копенгаген, вероятно, примерно совпадает с вашим):

_As legacy Date: Mon Aug 18 11:25:26 CEST 2014
_

Предложение по стратегии

Я думаю, что если бы это был я, я бы подумал:

  1. Wait . Установите соответствующее системное свойство из Java: System.setProperty("Java.locale.providers", "COMPAT,CLDR");, чтобы оно не было забыто ни в одной среде. Данные локали COMPAT существуют примерно с 1.0 (я думаю, по крайней мере, близко), поэтому от них зависит много кода (не только вашего). Название было изменено с JRE на COMPAT в Java 9. Для меня это может звучать как план по хранению данных в течение довольно долгого времени. Согласно документация раннего доступа он все еще будет доступен в Java 11 (следующая "долгосрочная поддержка") Java версия) и без предупреждения об устаревании или тому подобного. если он будет удален в будущем Java версия, вы, вероятно, скоро узнаете, что можете решить проблему до обновления.
  2. Используйте мое решение выше .
  3. Используйте интерфейс поставщика услуг локали, с которым связан Василий Бурк. Нет сомнений в том, что это решение Nice, если данные COMPAT должны быть удалены в неизвестное время в будущем. Возможно, вы даже сможете скопировать данные языкового стандарта COMPAT в свои собственные файлы, чтобы они не могли отобрать их у вас, только проверьте, есть ли проблемы с авторским правом, прежде чем вы это сделаете. Причина, по которой я в последний раз упоминаю решение Nice, заключается в том, что вы не удовлетворены необходимостью устанавливать системное свойство во всех возможных средах, в которых может работать ваша программа. Насколько я могу судить, использование ваших собственных данных локали через интерфейс поставщика услуг локали все равно потребует от вас установки того же системного свойства (только для другого значения).
19
Ole V.V.

Просто упомянуть: SimpleDateFormat - это старый способ форматирования дат, который, кстати, не является поточно-ориентированным. Так как Java 8 есть новые пакеты с именем Java.time а также Java.time.format и ​​вы должны использовать их для работы с датами. Для ваших целей вы должны использовать class DateTimeFormatter .

2
Michael Gantman

Отформатированное значение в Java 8 было Fr Juni 15 00:20:21 MESZ +0900 2018, но оно было изменено на Fr. Juni 15 00:20:21 MESZ +0900 2018 EEE включает. THIS = IS ВОПРОС СОВМЕСТИМОСТИ, и не имеет значения, что более старые версии кода не работают в более новых версиях. (Извините за переводчик). Если строка даты - ваша, вы должны добавить точку для пользователей новой версии. Или сделать пользователи используют Java 8, чтобы использовать ваше программное обеспечение.

Это может замедлить работу программного обеспечения, также хорошо использовать метод подстроки.

    String toParse = "Mo Aug 18 11:25:26 MESZ +0200 2014";
    String str = toParse.substring(0, 2) + "." + toParse.substring(2);
    String pattern = "EEE MMM dd HH:mm:ss z Z yyyy";

    DateFormat dateFormatter = new SimpleDateFormat(pattern, Locale.GERMANY);
    System.out.println(dateFormatter.format(System.currentTimeMillis()));
    Date date = dateFormatter.parse(str);

Извините за мой плохой английский.

0
dhkim0800