Различное поведение WeekFields на JVM 8 и JVM 10

У меня есть действительно простая программа здесь:

 public static void main(String[] args) {
        LocalDate year = LocalDate.ofYearDay(2022, 100);
        System.out.println(year);

        System.out.println(WeekFields.of(Locale.GERMAN).weekOfYear());

        System.out.println(year.with(WeekFields.of(Locale.GERMAN).weekOfYear(), 0));
        System.out.println(year.with(WeekFields.of(Locale.GERMAN).weekOfYear(), 0).with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)));
    }

Но он ведет себя по-разному в JVM 8 и JVM 10. Похоже, проблема заключается в реализации WeekFields.of(Locale.GERMAN).weekOfYear().

На JVM 10 я получаю следующие результаты:

JVM 10

2022-04-10
WeekOfYear[WeekFields[SUNDAY,1]]
2021-12-19
2021-12-13

тогда как на JVM 8:

JVM 8

2022-04-10
WeekOfYear[WeekFields[MONDAY,4]]
2022-01-02
2021-12-27

Почему это происходит? Я делаю что-то, что потенциально может вызвать неопределенное поведение? Или это изменение в поведении где-то указано?

JVM10:

$ java -version
openjdk version "10.0.2" 2018-07-17
OpenJDK Runtime Environment (build 10.0.2+13-Ubuntu-1ubuntu0.18.04.4)
OpenJDK 64-Bit Server VM (build 10.0.2+13-Ubuntu-1ubuntu0.18.04.4, mixed mode)

JVM8

$ java -version
openjdk version "1.8.0_191"
OpenJDK Runtime Environment (build 1.8.0_191-8u191-b12-2ubuntu0.18.04.1-b12)
OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)

РЕДАКТИРОВАТЬ: JVM 9 ведет себя так же, как JVM 8 а JVM 11 ведет себя как JVM 10

РЕДАКТИРОВАТЬ 2: Я на самом деле нашел коммит, который изменил поведение → здесь, на github, и мне любопытно, почему это было изменено.

Ответ 1

Такие недельные поля сильно локализованы и, следовательно, зависят от локализованных ресурсов базовой JVM, которые могут изменяться от одного выпуска к другому.

Я думаю, что JVM10 является более правильным, потому что Locale.GERMAN не относится ни к одной стране, поэтому Java просто предполагает США (как-то сомнительно, чтобы обрабатывать эту страну как мировой стандарт, как и Java).

Вам лучше использовать Locale.GERMANY. Эта страна действительно использует понедельник в качестве первого дня недели (в отличие от США, начинающихся в воскресенье, которые используются в качестве запасного варианта для GERMAN который является просто языком, а не страной).

Обновление - мое исследование о данных CLDR:

Текущий список данных CLDR для резервной страны/территории "001" (= во всем мире) определений недели (понедельник - первый день недели и 1 = минимальные дни первой недели в календарном году). Удивительно, но это отличается от определения в США (воскресенье, 1). Я думаю, что Oracle только что сделал свое дело. Лично я согласен с @Holger и скорее ожидаю ISO-8601 как запасной вариант (понедельник, 4).

Однако вы можете восстановить поведение Java-8 на вашем компьютере JVM-10, установив следующее системное свойство (не проверено):

java.locale.providers=COMPAT,CLDR,SPI

Ответ 2

Locale различает экземпляры, полезные для языка (например, GERMAN) и экземпляры, полезные для страны (например, GERMANY). Используйте первое, если вы хотите установить другое значение языка и сохранить локальный Locale, с другой стороны используйте второе, чтобы установить время и языковые настройки.

Ответ 3

Как исправить

Следующие два варианта эквивалентны. Выберите тот, который вы считаете наиболее подходящим для вашей ситуации.

  • WeekFields.ISO
  • WeekFields.of(Locale.GERMANY) используя страну, Германия, вместо языка, немецкий.

Почему это происходит? CLDR и страна против языка

Здесь есть два отличия:

  1. Различные данные локали по умолчанию в разных версиях Java.
  2. Как уже говорили другие, разница между языковым языком и языком, который включает страну.

Определение недельных схем в разных локалях является частью данных локали. Java может получать данные о локали из четырех источников. Java включала свои собственные данные локали из ранних версий, и они были по умолчанию вплоть до Java 8. Из Java 8 также были включены данные CLDR (Unicode Common Locale Data Repository), и они стали значениями по умолчанию из Java 9. Что, очевидно, изменило некоторые функциональность и сломал какой-то старый код, как вы уже испытали. Точнее, по умолчанию:

  • Java 8: JRE, SPI, где JRE ссылается на собственные данные локали Javas.
  • Java 9, 10 и 11: CLDR, COMPAT, где CLDR - это то, что говорит, а COMPAT - просто новое имя для данных JRE.

Значения по умолчанию можно java.locale.providers установив системное свойство java.locale.providers. Таким образом, мы можем получить поведение Java 8 в Java 9 и более поздних версиях, установив для этого свойства значение COMPAT,SPI. И наоборот, мы можем получить поведение Java 10 в Java 8, установив его в CLDR,JRE. Таким образом, в основе лежит не столько разница между версиями Java, сколько их значения по умолчанию.

Переход от данных Java к CLDR заключается в следующем: данные локали Java назначают определения недели для локалей только для языка (например, немецкого) в зависимости от того, на каком языке в основном говорят. В отличие от этого, философия CLDR заключается в том, что вы можете говорить на любом языке в любой стране мира и предпочитаете выбирать недельную схему в зависимости от страны, а не от языка. Как следствие, языковые стандарты, в которых не указана страна (например, немецкая), используют всемирное определение недели по умолчанию.

Почему во всем мире определение недели по умолчанию - "воскресенье, 1" в CLDR, я не понимаю. Как и другие, я бы ожидал и предпочел ISO международный стандарт "Понедельник, 4". Как я сказал в комментарии, я также обнаружил заметку о том, что так и должно быть, но это все еще не так (по крайней мере, в версиях CLDR, используемых в Java 8-11).

Java 9 особенная

Как вы заметили, в Java 9 с данными локали по умолчанию вы получаете "Monday 4" от Locale.GERMAN хотя CLDR должен быть первым по умолчанию. Если, с другой стороны, я устанавливаю java.locale.providers для CLDR, я получаю "Sunday 1", как в Java 10 и 11.

Возможное объяснение состоит в том, что версия CLDR, используемая в Java 9, не включает определение недели для немецкого языка. Таким образом, с провайдерами по умолчанию, CLDR, COMPAT, Java возвращается к COMPAT, который предоставляет "Понедельник, 4" для немецкого языка. Когда я использую только CLDR, вместо этого используется стандартное базовое значение по всему миру "Sunday, 1". Если это объяснение верное (что я не могу гарантировать), может показаться, что версии данных CLDR, используемые в Java 10 и 11, содержат недельное определение для немецкого языка.

связи

Документация LocaleServiceProvider с информацией о поставщиках данных локали и спецификации поставщиков по умолчанию:

Ссылки CLDR: