SimpleDateFormat с немецким языком - Java 8 vs 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).

Проверка этого qaru.site/info/387464/..., мне кажется, что поведение является намеренным для немецкой локализации и может быть смягчено путем указания java.locale.providers с COMPAT режимом. Однако мне не нравится идея полагаться на некоторое значение свойства системы по двум причинам:

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

Мой вопрос:

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

Ответ 1

Я не считаю его хорошим решением, но, похоже, это путь.

    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, современный API дат и времени Java, позволяет нам указывать тексты для полей для форматирования и разбора. Поэтому я использую это как для дня недели, так и для месяца, указывая аббревиатуры без точки, которые использовались со старыми данными локали 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

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

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

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

Ответ 2

Отформатированное значение в java 8 было Fr Juni 15 00:20:21 MESZ +0900 2018 Но он изменился на Fr. Juni 15 00:20:21 MESZ +0900 2018 EEE включает. ЭТО ПРОБЛЕМА СОВМЕСТИМОСТИ, и не имеет значения, что более старые версии кода не работают в более новых версиях. (Извините за переводчика). Если строка даты принадлежит вам, вы должны добавить точку для пользователей новой версии. Или попросите пользователей использовать 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);

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

Ответ 3

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