Bad Dates with DatePicker

Chris Fry
Livefront
Published in
4 min readJun 8, 2021
Photo by Claudio Schwarz | @purzlbaum on Unsplash

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:

Notice a bad month AND day value appear available.

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.

Success! No more bad dates displayed.

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.

Yay! Bad dates are still gone.

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.

Oh no! The bad dates are back.

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.

--

--