The code below gives a different result on Nougat and pre-Nougat. Take a look and try it yourself if you want. I would appreciate it if someone could explain me why and give a solution.
I want the correct WEEK_OF_YEAR value, dependent on the first day of the week, on all Android versions. I have a time sheet app and i'm using the gregorianCalendar a lot, so I don't feel like switching to another class/lib.
//default first day of the week is Monday for replication. I live in the Netherlands, it's 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???
If you look at the release notes for Nougat you can see that there is enhanced support for Locales.
Especially,
Prior to Android 7.0, Android could not always successfully match app and system locales.
I too have noticed this on my own device. My phone is set with English (Australia) Locale. Prior to Nougat,
DateTimeFormat.forPattern("dd MMMM").print(new LocalDate(2017,1,29));
would print 29 Jan
(no full-stop/period) but after Nougat it prints 29 Jan.
(with period).
Although it's hard for me to give exact specifics, it seems as if this is what is happening in your case. Post-Nougat, the phone is able to better match the app and system locales, including the first day of the week for your Locale. In any case, stepping through with the debugger to find the root cause won't work because the call is handled inside libcore
, rather than a Java class exposed in the source code.
If an Android device is incorrectly reporting the first day of year/first week of year, there would be little you can do besides work around it:
if (android.os.Build.VERSION.SDK_INT < 24) {
//pre-Nougat logic
}
else {
//Nougat/post-Nougat logic
}
You could also perhaps try using the enhanced replacement for GregorianCalendar (added in SDK 24) to fix your problem.
If you use classes like Joda-time or use the new JSR-310 classes (through the ThreeTen backport) you might be able to get what you want without having to work around GregorianCalendar
. In general these classes are much easier to use and less bug-prone. Many developers have already given up on java.util.Calendar
and java.util.Date
because of issues like this. Please see the answers to this canonical question for details
If you were to use Joda, you can use LocalDate.fromCalendarFields(test)
to convert your GregorianCalendar
object into a LocalDate
. These classes use the ISO standard where the first day of week is always Monday. You would then write the logic you want on top of them. Then the issue with GregorianCalendar
would be "isolated" into a mere issue with retrieving the first week of the year for a given Locale. If your app incorporates calls to a server, you could serve the first day of week from an API call instead.
Update:
Note timezone behaviour has been updated in Android O:
Additional locale and internationalization-related changes are as follows:
Time zone name parsing has changed. Previously, Android devices used the system clock value sampled at boot time to cache the time zone names used for parsing date times. As a result, parsing could be negatively affected if the system clock was wrong at boot time or in other, rarer cases. Now, in common cases the parsing logic uses ICU and the current system clock value when parsing time zone names. This change provides more correct results, which may differ from earlier Android versions when your app uses classes like SimpleDateFormat. Android O updates the version of ICU to version 58.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With