I have a Calendar instance that starts with the first day in December. I am trying to set that Calendar instance to the last Tuesday of the year. Somewhere between setting the last day of the year and the day of week, the Calendar instance reverts back to the original time:
Calendar cal = ....;
Log.d(TAG, String.format("Starting here %d-%d-%d", cal.get(Calendar.YEAR), cal.get(Calendar.MONTH)+1, cal.get(Calendar.DAY_OF_MONTH)));
cal.set(Calendar.DAY_OF_YEAR, cal.getActualMaximum(Calendar.DAY_OF_YEAR));
cal.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY);
Log.d(TAG, String.format("Ending here %d-%d-%d", cal.get(Calendar.YEAR), cal.get(Calendar.MONTH)+1, cal.get(Calendar.DAY_OF_MONTH)));
/* Other stuff */
Gives me this output for 2017 and 2018:
2017:
Starting here 2017-12-1
Ending here 2017-11-28
2018:
Starting here 2018-12-1
Ending here 2018-11-27
However, if I retrieve data from the Calendar instance, it works fine:
Calendar cal = ....;
Log.d(TAG, String.format("Starting here %d-%d-%d", cal.get(Calendar.YEAR), cal.get(Calendar.MONTH)+1, cal.get(Calendar.DAY_OF_MONTH)));
cal.set(Calendar.DAY_OF_YEAR, cal.getActualMaximum(Calendar.DAY_OF_YEAR));
// Ask the Calendar instance for information
long dummyValue = cal.getTimeInMillis();
cal.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY);
Log.d(TAG, String.format("Ending here %d-%d-%d", cal.get(Calendar.YEAR), cal.get(Calendar.MONTH)+1, cal.get(Calendar.DAY_OF_MONTH)));
/* Other stuff */
Gives me this output for 2017 and 2018:
2017:
Starting here 2017-12-1
Ending here 2018-1-2
2018:
Starting here 2018-12-1
Ending here 2019-1-1
And yes, I know the dates for the second set of values are not the last Tuesday. That's what /* Other stuff */
is for.
LocalDate myLocalDate = LocalDate.of(2017, Month.DECEMBER, 1);
System.out.format("Starting here: %s%n", myLocalDate);
LocalDate lastTuesdayOfYear = myLocalDate
.with(TemporalAdjusters.lastDayOfYear())
.with(TemporalAdjusters.previousOrSame(DayOfWeek.TUESDAY));
System.out.format("Ending here: %s%n", lastTuesdayOfYear);
Output for 2017:
Starting here: 2017-12-01 Ending here: 2017-12-26
For 2018:
Starting here: 2018-12-01 Ending here: 2018-12-25
This is giving you the last Tuesday of the year. Nothing further is needed for that.
Yes, java.time works nicely on older and newer Android devices. It just requires at least Java 6.
org.threeten.bp
with subpackages.The Calendar
class is just confusing. In your case it is behaving according to the documentation. Allow me to quote:
Calendar Fields Resolution
When computing a date and time from the calendar fields, … there may be inconsistent information (such as Tuesday, July 15, 1996 (Gregorian) -- July 15, 1996 is actually a Monday).
Calendar
will resolve calendar field values to determine the date and time in the following way.If there is any conflict in calendar field values,
Calendar
gives priorities to calendar fields that have been set more recently. The following are the default combinations of the calendar fields. The most recent combination, as determined by the most recently set single field, will be used.For the date fields:
YEAR + MONTH + DAY_OF_MONTH YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK YEAR + DAY_OF_YEAR YEAR + DAY_OF_WEEK + WEEK_OF_YEAR
…
In your case DAY_OF_WEEK
was set most recently. DAY_OF_WEEK
is in three of the five combinations above. Which one it picks I don’t know. None of them also include DAY_OF_YEAR
, the other field you set, so DAY_OF_YEAR
is not being used. Which explains the behaviour you saw. If you insist on using Calendar
, you should be able to get somewhere with cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, -1);
since negative values count backward from the end of the month.
However, Calendar
is not only poorly and confusingly designed, it is also long outdated. So consider using java.time instead.
java.time
was first described.java.time
to Java 6 and 7 (ThreeTen for JSR-310).