GregorianCalendar setFirstDayOfWeek не влияет на WEEK_OF_YEAR на pre Nougat

Приведенный ниже код дает другой результат для Nougat и pre-Nougat. Взгляните и попробуйте сами, если хотите. Я был бы признателен, если бы кто-нибудь мог объяснить мне, почему и дать решение.

Я хочу правильное значение WEEK_OF_YEAR, зависящее от первого дня недели, на всех версиях Android. У меня есть приложение с временными таблицами, и я часто использую gregorianCalendar, поэтому мне не хочется переключаться на другой класс /lib.

    //default first day of the week is Monday for replication. I live in the Netherlands, it weird.
    Locale l = new Locale("nl", "NL");

    GregorianCalendar test = new GregorianCalendar(l);
    test.set(Calendar.YEAR, 2017);
    test.set(Calendar.MONTH, 0);
    test.set(Calendar.DAY_OF_MONTH, 29);//this is a Sunday

    int week = test.get(Calendar.WEEK_OF_YEAR);//should be 4
    test.setFirstDayOfWeek(1);//Set it to Sunday
    int week2 = test.get(Calendar.WEEK_OF_YEAR);//should be 5 but is 4 below nougat???

Ответ 1

Если вы посмотрите на примечания к выпуску для Nougat, вы увидите, что улучшена поддержка Locales.

Особенно,

До Android 7.0 Android не всегда мог успешно соответствовать локалям приложений и систем.

Я тоже это заметил на своем собственном устройстве. Мой телефон настроен на английский (Австралия). До Нуги,

DateTimeFormat.forPattern("dd MMMM").print(new LocalDate(2017,1,29));

будет печатать 29 Jan (без полной остановки/периода), но после Nougat он печатает 29 Jan. (с периодом).

Хотя мне трудно дать точную информацию, кажется, что это то, что происходит в вашем случае. Post-Nougat, телефон может лучше соответствовать местным приложениям и системам, включая первый день недели для вашего Locale. В любом случае переход с отладчиком для поиска основной причины не будет работать, потому что вызов обрабатывается внутри libcore, а не класс Java, открытый в исходном коде.

Если Android-устройство неправильно сообщает о первом дне года/первой неделе года, вы можете сделать что-то мало, кроме работы вокруг него:

if (android.os.Build.VERSION.SDK_INT < 24) {
    //pre-Nougat logic
}
else {
    //Nougat/post-Nougat logic
}

Возможно, вы также попытаетесь использовать расширенную замену для GregorianCalendar (добавлена ​​в SDK 24), чтобы исправить вашу проблему.

Если вы используете классы типа Joda-time или используйте новый JSR-310 (через обратный порт ThreeTen) вы можете получить то, что хотите, без необходимости работать вокруг GregorianCalendar. В целом эти классы намного проще в использовании и менее подвержены ошибкам. Многие разработчики уже отказались от java.util.Calendar и java.util.Date из-за таких проблем. Подробнее об этом

см. В ответах на этот канонический вопрос.

Если вы собираетесь использовать Joda, вы можете использовать LocalDate.fromCalendarFields(test) для преобразования вашего объекта GregorianCalendar в LocalDate. Эти классы используют стандарт ISO, где первый день недели всегда в понедельник. Затем вы должны написать нужную вам логику. Тогда проблема с GregorianCalendar была бы "изолирована" в простой проблеме с получением первой недели года для данного локали. Если ваше приложение включает вызовы на сервер, вы можете использовать первый день недели вместо вызова API.

Update:

Поведение часового пояса в примечании обновлено в Android O:

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

Разбор имени часового пояса изменился. Раньше на устройствах Android использовалось системное значение часов, отобранное во время загрузки, для кэширования имен часовых поясов, используемых для разбора даты. В результате разбор может отрицательно сказаться, если системные часы были неправильными во время загрузки или в других, более редких случаях. Теперь в обычных случаях логика синтаксического анализа использует ICU и текущее значение системного такта при разборе имен часовых поясов. Это изменение дает более правильные результаты, которые могут отличаться от предыдущих версий Android, когда ваше приложение использует такие классы, как SimpleDateFormat. Android O обновляет версию ICU до версии 58.

Ответ 2

Из того, что я думаю, проблема заключается в том, где вы устанавливаете месяц.

//Здесь в месяце Параметр вы передаете 0 как значение, которое может создать проблему, потому что, хотя массив начинается с 0, вход, который этот Calendar.MONTH принимает, составляет от 1 до 12. Поэтому попробуйте изменить это.

Или Вы также можете изменить его на это для установки его в январе, когда используется месяц

test.set(Calendar.MONTH, Calendar.JANUARY);

//Также здесь, в этой строке, попробуйте и измените на

test.setFirstDayOfWeek(Calendar.SUNDAY);//Set it to Sunday

Это должно решить вашу проблему.