Article
Bad Dates with DatePicker
June 8, 2021
When using Android’s DatePicker class with a max date, the last thing you want to look at is a date that falls outside your restriction. So why is it possible for this class to display a “bad” value when in spinner mode? In order to see this, first you’ll need to set the DatePicker’s max date:
Then observe the resulting picker values:
Although June 8th and July 7th are not technically usable, (attempting to select one of these dates jumps back to June 7, 2020) it is jarring to a user to see dates that they can’t use. Luckily, there is a simple fix to ensure that these invalid dates are not displayed.
The Problem
The root of the problem stems from the DatePickerSpinnerDelegate class and its updateSpinners
method.
As you can see above, restrictions on the day and month spinners are only enforced if mCurrentDate
is equal to mMaxDate
or mMinDate
. Any mCurrentDate
value falling between mMaxDate
and mMinDate
will display January-December in the month spinner and all dates (depending on the month length) in the day spinner. These are all instances of the Calendar class. This means that in order to strictly enforce our upper limit, mMaxDate
and mCurrentDate
must have the same exact value down to the MILLISECOND. mCurrentDate
’s Calendar instance is created when DatePickerSpinnerDelegate is constructed during view creation. This makes it nearly impossible to know the time value (hour, minute, and millisecond fields) contained by mCurrentDate
because DatePicker only allows you to set/get the day of month, month, and year fields.
The Fix
Instead of attempting to match the time value in mCurrentDate
, we instead will focus on eliminating time from the comparison altogether. We can do this by taking advantage of logic within the DatePickerSpinnerDelegate setMaxDate
method.
Upon inspecting this method, we can see that if mCurrentDate
falls after the new max date, mCurrentDate
will be set to match mMaxDate
. Assuming that the DatePicker has yet to be interacted with, setting a max date value of the current system date at exactly midnight guarantees that mCurrentDate
will be after the new date.
But what if you want to enforce a max date that is in the future? Easy! Just use the same trick from before.
Below is the result of using the day spinner to get to June 20th starting on June 7th.
This still works because as the DatePicker changes it calls DatePickerSpinnerDelegate’s setDate
method shown below:
This method will set mCurrentDate
date back to mMaxDate
or mMinDate
if it falls outside the min/max restriction (which is why the bad dates aren’t selectable). Therefore, when the DatePicker reaches June 20th, mCurrentDate
is guaranteed to be after mMaxDate
(because mMaxDate
represents midnight of June 20th), and will be set to match mMaxDate
.
BE WARNED: If you don’t clear the time value from your max date restriction a user may still see bad values once they hit your max date.
Again, below is the result of using the day spinner to get to June 20th starting from June 7th. Because mMaxDate
and mCurrentDate
still have their original time values, they are not EXACTLY equal even though the date portion of both Calendars is aligned.
In Conclusion
By removing time from the max date comparison, you’ll ensure that no matter what time values Calendar.getInstance
happens to give you it won’t interfere with properly displaying your max date.
Chris ensures that only good dates are had at Livefront .