Различное поведение 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 и страна против языка
Здесь есть два отличия:
- Различные данные локали по умолчанию в разных версиях Java.
- Как уже говорили другие, разница между языковым языком и языком, который включает страну.
Определение недельных схем в разных локалях является частью данных локали. 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: