Skip to content

Commit

Permalink
Supporting datetime bounds for DatetimePicker and DatetimeRangePicker (
Browse files Browse the repository at this point in the history
…#3788)

* Supporting datetime bounds for DatetimePicker and DatetimeRangePicker

* Updating unit test with datetime instead of date
  • Loading branch information
hoxbro committed Aug 29, 2022
1 parent d42abb7 commit 7550f75
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 37 deletions.
4 changes: 2 additions & 2 deletions examples/reference/widgets/DatetimePicker.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
"##### Core\n",
"\n",
"* **``value``** (datetime): The selected value as a datetime type\n",
"* **``start``** (date or datetime): Inclusive lower bound of the allowed date selection. Note that while the parameter accepts datetimes the time portion of the range is ignored.\n",
"* **``end``** (date or datetime): Inclusive upper bound of the allowed date selection. Note that while the parameter accepts datetimes the time portion of the range is ignored.\n",
"* **``start``** (date or datetime): Inclusive lower bound of the allowed date selection.\n",
"* **``end``** (date or datetime): Inclusive upper bound of the allowed date selection.\n",
"* **``disabled_dates``** (list): Dates to make unavailable for selection; others will be available\n",
"* **``enabled_dates``** (list): Dates to make available for selection; others will be unavailable\n",
"* **``enable_time``** (boolean): Enable editing of the time in the widget, default is True\n",
Expand Down
4 changes: 2 additions & 2 deletions examples/reference/widgets/DatetimeRangePicker.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
"##### Core\n",
"\n",
"* **``value``** (tuple): Tuple of upper and lower bounds of the selected range expressed as datetime types\n",
"* **``start``** (date or datetime): Inclusive lower bound of the allowed date selection. Note that while the parameter accepts datetimes the time portion of the range is ignored.\n",
"* **``end``** (date or datetime): Inclusive upper bound of the allowed date selection. Note that while the parameter accepts datetimes the time portion of the range is ignored.\n",
"* **``start``** (date or datetime): Inclusive lower bound of the allowed date selection. \n",
"* **``end``** (date or datetime): Inclusive upper bound of the allowed date selection. \n",
"* **``disabled_dates``** (list): Dates to make unavailable for selection; others will be available\n",
"* **``enabled_dates``** (list): Dates to make available for selection; others will be unavailable\n",
"* **``enable_time:``** (boolean): Enable editing of the time in the widget, default is True\n",
Expand Down
6 changes: 4 additions & 2 deletions panel/models/datetime_picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ export class DatetimePickerView extends InputWidgetView {
this.group_el.appendChild(this.input_el)
this._picker = flatpickr(this.input_el, {
defaultDate: this.model.value,
minDate: this.model.min_date ?? undefined,
maxDate: this.model.max_date ?? undefined,
minDate: this.model.min_date ? new Date(this.model.min_date) : undefined,
maxDate: this.model.max_date ? new Date(this.model.max_date) : undefined,
inline: this.model.inline,
position: this.model.position,
disable: _convert_date_list(this.model.disabled_dates),
Expand All @@ -82,6 +82,8 @@ export class DatetimePickerView extends InputWidgetView {
mode: this.model.mode,
onClose: (selected_dates, date_string, instance) => this._on_close(selected_dates, date_string, instance),
})
this._picker.maxDateHasTime = true
this._picker.minDateHasTime = true
}

protected _on_close(_selected_dates: Date[], date_string: string, _instance: flatpickr.Instance): void {
Expand Down
30 changes: 20 additions & 10 deletions panel/tests/widgets/test_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,16 @@ def test_date_picker(document, comm):
def test_datetime_picker(document, comm):
datetime_picker = DatetimePicker(
name='DatetimePicker', value=datetime(2018, 9, 2, 1, 5),
start=date(2018, 9, 1), end=date(2018, 9, 10)
start=datetime(2018, 9, 1), end=datetime(2018, 9, 10)
)

widget = datetime_picker.get_root(document, comm=comm)

assert isinstance(widget, datetime_picker._widget_type)
assert widget.title == 'DatetimePicker'
assert widget.value == '2018-09-02 01:05:00'
assert widget.min_date == '2018-09-01'
assert widget.max_date == '2018-09-10'
assert widget.min_date == '2018-09-01T00:00:00'
assert widget.max_date == '2018-09-10T00:00:00'

datetime_picker._process_events({'value': '2018-09-03 03:00:01'})
assert datetime_picker.value == datetime(2018, 9, 3, 3, 0, 1)
Expand All @@ -85,24 +85,29 @@ def test_datetime_picker(document, comm):
assert datetime_picker._deserialize_value(value) == '2018-09-04 12:01:00'
assert datetime_picker._serialize_value(datetime_picker._deserialize_value(value)) == value

# Check start value
with pytest.raises(ValueError):
datetime_picker._process_events({'value': '2018-08-31 23:59:59'})

# Check end value
datetime_picker._process_events({'value': '2018-09-10 03:00:01'})
assert datetime_picker.value == datetime(2018, 9, 10, 0, 0, 0)
with pytest.raises(ValueError):
datetime_picker._process_events({'value': '2018-09-10 00:00:01'})



def test_datetime_range_picker(document, comm):
datetime_range_picker = DatetimeRangePicker(
name='DatetimeRangePicker', value=(datetime(2018, 9, 2, 1, 5), datetime(2018, 9, 2, 1, 6)),
start=date(2018, 9, 1), end=date(2018, 9, 10)
start=datetime(2018, 9, 1), end=datetime(2018, 9, 10)
)

widget = datetime_range_picker.get_root(document, comm=comm)

assert isinstance(widget, datetime_range_picker._widget_type)
assert widget.title == 'DatetimeRangePicker'
assert widget.value == '2018-09-02 01:05:00 to 2018-09-02 01:06:00'
assert widget.min_date == '2018-09-01'
assert widget.max_date == '2018-09-10'
assert widget.min_date == '2018-09-01T00:00:00'
assert widget.max_date == '2018-09-10T00:00:00'

datetime_range_picker._process_events({'value': '2018-09-03 03:00:01 to 2018-09-04 03:00:01'})
assert datetime_range_picker.value == (datetime(2018, 9, 3, 3, 0, 1), datetime(2018, 9, 4, 3, 0, 1))
Expand All @@ -116,9 +121,14 @@ def test_datetime_range_picker(document, comm):
assert datetime_range_picker._deserialize_value(value) == '2018-09-04 12:01:00 to 2018-09-04 12:01:10'
assert datetime_range_picker._serialize_value(datetime_range_picker._deserialize_value(value)) == value

# Check start value
with pytest.raises(ValueError):
datetime_range_picker._process_events({'value': '2018-08-31 23:59:59'})

# Check end value
datetime_range_picker._process_events({'value': '2018-09-09 03:00:01 to 2018-09-10 03:00:01'})
assert datetime_range_picker.value == (datetime(2018, 9, 9, 3, 0, 1), datetime(2018, 9, 10, 0, 0, 0))
with pytest.raises(ValueError):
datetime_range_picker._process_events({'value': '2018-09-10 00:00:01'})



def test_file_input(document, comm):
Expand Down
22 changes: 1 addition & 21 deletions panel/widgets/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,17 +327,9 @@ def __init__(self, **params):
super().__init__(**params)
self._update_value_bounds()

@staticmethod
def _date_to_datetime(x):
if isinstance(x, date):
return datetime(x.year, x.month, x.day)

@param.depends('start', 'end', watch=True)
def _update_value_bounds(self):
self.param.value.bounds = (
self._date_to_datetime(self.start),
self._date_to_datetime(self.end),
)
self.param.value.bounds = (self.start, self.end)
self.param.value._validate(self.value)

def _process_property_change(self, msg):
Expand Down Expand Up @@ -378,11 +370,6 @@ def _serialize_value(self, value):
if isinstance(value, str) and value:
value = datetime.strptime(value, r'%Y-%m-%d %H:%M:%S')

# Hour, minute and seconds can be increased after end is reached.
# This forces the hours, minute and second to be 0.
end = self._date_to_datetime(self.end)
if end is not None and value > end:
value = end

return value

Expand Down Expand Up @@ -421,13 +408,6 @@ def _serialize_value(self, value):
for value in value.split(' to ')
]

# Hour, minute and seconds can be increased after end is reached.
# This forces the hours, minute and second to be 0.
end = self._date_to_datetime(self.end)
if end is not None and value[0] > end:
value[0] = end
if end is not None and value[1] > end:
value[1] = end

value = tuple(value)

Expand Down

0 comments on commit 7550f75

Please sign in to comment.