Simple web component for selecting ranges of local dates.
Note: Documentation written by Anders Gustafson, revised .
When I needed a solution for easily picking ranges of local dates, I could not find any perfect alternatives out in the Internet jungle. So I decided to build my own web component,
nnm-local-date-range-picker. Of course, it would also be a fun exercise building your own solution, trying to learn various things about web components along the road.
During my investigations of time zones, I have come to the conclusion that many parts of date handling are often way too complex. Selecting dates within a web page should be simple, whether it is a single date or a date range.
Give this NNM Local date range picker a try. On first look, you may think that this component is not simple and contains too much information and details, but I truly think that I can prove you wrong and that you will find the concepts and user experiences quite convenient, intuitive, helpful and appealing.
I have been inspired from some already existing date (range) pickers, especially the Date Range Picker component which I think is among the best of them. Some existing solutions are good (for example DateRangePicker (React Spectrum) is promising), but everyone lacks some functionality that I needed and many of them also felt a bit too complex or are ambiguous and confusing.
My guiding principles for this project have been don't complicate things and don't make me think. Hopefully this will be seen in the result.
Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.
You should be able to select a range of local dates. The most important word in the previous sentence is "local" and you should pronounce local date range picker as "local-date range-picker" and not "local date-range-picker". We should also not complicate things by handling time zones.
The resulting range of local dates, should consist of simple strings, defining the local dates in the ISO 8601 extended format (
yyyy-mm-dd). This format is used in order to avoid ambiguity and for easy reading. There is no need for handling complex date objects, strange epoch values (sometimes defined in milliseconds and sometimes defined in seconds) or different string formats. No time part is needed in the string, we only need the date part (a local datetime range picker will be another project).
The primary purpose of the component should be to easily help the user, with as few clicks (or keystrokes) as possible, to pick a local date range, for example
2021-11-20 - 2022-01-05, and to display the selected range in an understandable way. Everything else is secondary.
Note: I emphasize, that the result returned from the component should be two simple strings (with format
yyyy-mm-dd) defining the local date endpoints. If returning anything else you only make things way more complicated than needed.
The component should be focused on selecting ranges in the "near" past/future. Selecting dates years away from today (for example "date of birth") is not key functionality. It is more important that you can select dates in the nearest weeks/months/quarters in a convenient way.
I advocate that we should distinguish local dates from zoned dates. You can read more about my reasons in the section about converting date and time values. A date range picker should be used to select local dates. In some applications, these local dates may sometimes have to be converted to zoned dates, but that complexity should not be part of a picker component, that is application specific functionality.
A local date is also known as a plain date.
Absolutely not! The reason is that such functionality will make the component more complex, but with no benefits at all. If you, for some strange reason, want to use other formats, that added complexity should be your problem.
Note: You can read more details about my reasoning in the "Formats for clients" section in The golden timestamp rules.
At the moment, the currently selected range is displayed in a hardcoded format (eg
Wed 23 March). In future versions this may be a configurable option, but only for the actual user and not for the developer or product owner.
Note: The format that I as a user think is most suitable for different things, is not something that should be decided by anyone else. If I think that it is easier to understand
Wed 23 March,
23/3 or something else, should always be my own decision. If there is a possibility to define formats for how date and time are presented to the user, that choice should only be visible for the user.
Secondary functionality that I aimed for in the first version.
It would be enough to support English as language. Trying to support several languages often ends up in bad user experience and bugs.
Supports ISO calendar. There is a standard, let us use that one. No need to complicate things more than necessary.
Since we are using ISO, a week starts on Mondays and ends on Sundays.
An easy and convenient way of selecting the most common ranges, that probably will be used more than 95% of the time. It should also be explicitly indicated when your selected range matches one of these common ranges.
Each button in a specific row should define roughly the same amount of days. There should be three columns where the left define ranges in the past, the right defines ranges in the future, and the middle defines ranges containing current date. The left and right buttons may sometimes include current date, the middle button always includes current date.
The common ranges to be in a quick list could be things like:
Ranges that should be indicated, should be the ones in the quick list plus things like Q1, Q2, Q3, Q4, H1 and H2.
Yesterday, today and tomorrow contain one day.
Previous week, current week and next week contain seven days.
Previous month, current month and next month are dynamic and contain 28-31 days.
Since the "-", "±" and "+" ranges should always include current date, they are a bit special. The "±" range should contain the same number of days before and after current date, and therefore the number of days for these ranges must be an odd number (so "-2 weeks" contains 14 days, but "±1 week" contains 13 days).
Displaying three consecutive months, so it will be easier to pick longer ranges (especially when picking ranges crossing months). You will avoid many extraneous clicks when displaying three months, and in my humble opinion you also get a more appealing and balanced user interface compared to showing two months.
Under normal circumstances, you will always see at least four weeks before and four weeks after current date, when three months are displayed. If you only display two months, you may sometimes have zero(!) days before or after current date, which I think is quite annoying.
Yes, when selecting single local dates, seeing one month may be sufficient, since it is a single value. But when selecting ranges, I truly think that you benefit of seeing as much of your range as possible. Using three months feels like a good compromise for ranges.
By displaying three months instead of two, you actually double the number of days that a range can contain and always be visible.
When you display one month, you can only be certain that ranges of one day can be fully shown. For example, the range 31 January to 1 February, which is only two days, cannot be fully shown. The max number of days that can be shown is 31.
When you display two months, you can only be certain that ranges of 29 days can be fully shown. For example, the range 31 January to 1 March, which is 30 days, cannot be fully shown. The max number of days that can be shown is 62 (for example July and August).
When you display three months, you can always be certain that ranges of 60 days can be fully shown. For example, the range 31 January to 1 April, which is 61 days, cannot be fully shown. The max number of days that can be shown is 92 (for example June, July and August).
Note: The examples above are valid during non-leap years, i.e. when February contains 28 days.
The actual number of days in the selected range should be explicitly displayed, so the user directly understands how long the range is.
When using a date range picker for selecting hotel nights, it may be better to show the difference in days (which equals the number of nights). But for most use cases, I would argue that it is better to indicate the actual number of days.
When seeing date pickers that count
2022-01-22 - 2022-01-23 as one day, I always get confused and many questions arise:
2022-01-22really be represented as zero days?
If you have a selected range of local dates, and you are doing database queries, you should include the complete days in the range (otherwise your component should be a datetime range picker instead). So if you select the date range
2021-11-20 - 2022-01-05, you should interpret it as
2021-11-20T00:00 - 2022-01-05T23:59 when you search. Of course, you may have to interpret this range to be in a specific time zone, and then you need to document this in a clear and concise way to avoid confusion for the user.
What time zone is used for the selected dates? The short answer is none. A little longer answer in the next paragraph.
The component should not have to care about time zone complexity. The local date matching "today" should be decided from the browser settings, but from that moment on, it is all about displaying "dumb" calendars. Displaying calendars to a user, has nothing to do with time zones, it is just an endless list of consecutive months.
As soon as you mix in time zones, you only complicate things and add problems, without getting any benefits. Take my word for it.
Note: I have seen so many complex solutions where you need to keep track of confusing time zones and/or strange epoch values, just to be able to use a date (range) picker. I truly think that is the completely wrong way to go.
Support for min/max configurations, so out-of-range dates can be disabled.
Using min/max can also be a workaround to easily make it possible to select dates in the "far" past/future.
No annoying "overlapping" of days between calendar months. A month should only display days belonging to that month. It is not smart to clutter the user interface with those "overlaps". I have not yet seen a good solution with this type of duplication.
Note: The only mitigating circumstance may be when scrolling vertical through months.
Every month should always have room for exactly six weeks, even if a month sometimes just consists of four weeks.
Note: It is really no problem since the picker displays three months, but even if it should have displayed only one month, I would go for six weeks, to avoid a flickering interface.
Day of week for the endpoints of the selected range should be explicitly indicated. It is often of great help when you immediately can see the day of week and not just the date.
Do not waste space displaying year for dates in current year, but be explicit for other years.
Some way of using keys to change months.
It would also be nice to have the possibility to modify the selected range in some way. It can be to move, widen or narrow the range. Currently, trying different solutions for this.
I think that one important thing is to easily understand the selected date range value. To help the user I think the following is needed:
So if you select the date range 1 December to 31 December, and that happens to also be previous month, you will need to display the following information
Wed 1 December 2021 - Fri 31 December 2021 (31 days) Previous month
Note: At the moment the displayed value is written in a single row, but perhaps it will be divided in several rows in future versions.
Some functionality that hopefully will be implemented in the coming versions:
Showing the progress and enhancements of the component.
Feedback is always appreciated. If you have questions, suggestions for improvements, find errors, please see the contact details at information page.
As always, delivered as is, with the best of intentions, but no guarantees.