From 8918971717c8b30969b1055bc49c39ad1afe18d2 Mon Sep 17 00:00:00 2001 From: arraydude Date: Wed, 6 May 2020 18:45:44 -0300 Subject: [PATCH 1/6] Fixing date_input min and max selectable date issues --- .../widgets/DateInput/DateInput.test.tsx | 22 ++++++++++++++++++- .../widgets/DateInput/DateInput.tsx | 22 +++++++++++++++---- lib/streamlit/DeltaGenerator.py | 18 ++++++++++++++- proto/streamlit/proto/DateInput.proto | 2 ++ 4 files changed, 58 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/widgets/DateInput/DateInput.test.tsx b/frontend/src/components/widgets/DateInput/DateInput.test.tsx index 31bf427a28d7..f1ca8ac2aee2 100644 --- a/frontend/src/components/widgets/DateInput/DateInput.test.tsx +++ b/frontend/src/components/widgets/DateInput/DateInput.test.tsx @@ -19,6 +19,7 @@ import React from "react" import { shallow } from "enzyme" import { fromJS } from "immutable" import { WidgetStateManager } from "lib/WidgetStateManager" +import { DateInput as DateInputProto } from "autogen/proto" import DateInput, { Props } from "./DateInput" import { Datepicker as UIDatePicker } from "baseui/datepicker" @@ -27,11 +28,12 @@ jest.mock("lib/WidgetStateManager") const sendBackMsg = jest.fn() -const getProps = (elementProps: object = {}): Props => ({ +const getProps = (elementProps: Partial = {}): Props => ({ element: fromJS({ id: 1, label: "Label", default: "1970/01/01", + minDate: "1970/01/01", ...elementProps, }), width: 0, @@ -102,4 +104,22 @@ describe("DateInput widget", () => { { fromUi: true } ) }) + + it("should have a minDate", () => { + expect(wrapper.find(UIDatePicker).prop("minDate")).toStrictEqual( + new Date("1970/1/1") + ) + expect(wrapper.find(UIDatePicker).prop("maxDate")).toBeUndefined() + }) + + it("should have a maxDate if it is passed", () => { + const props = getProps({ + maxDate: "2030/02/06", + }) + const wrapper = shallow() + + expect(wrapper.find(UIDatePicker).prop("maxDate")).toStrictEqual( + new Date("2030/02/06") + ) + }) }) diff --git a/frontend/src/components/widgets/DateInput/DateInput.tsx b/frontend/src/components/widgets/DateInput/DateInput.tsx index 777f0e4ea1a1..47470a237b28 100644 --- a/frontend/src/components/widgets/DateInput/DateInput.tsx +++ b/frontend/src/components/widgets/DateInput/DateInput.tsx @@ -56,19 +56,33 @@ class DateInput extends React.PureComponent { this.setState({ value }, () => this.setWidgetValue({ fromUi: true })) } + private getMaxDate = (): Date | undefined => { + const { element } = this.props + const maxDate = element.get("maxDate") + + return maxDate && maxDate.length > 0 ? stringToDate(maxDate) : undefined + } + public render = (): React.ReactNode => { - const style = { width: this.props.width } - const label = this.props.element.get("label") + const { width, element, disabled } = this.props + const { value } = this.state + + const style = { width } + const label = element.get("label") + const minDate = element.get("minDate") + const maxDate = this.getMaxDate() return (
) diff --git a/lib/streamlit/DeltaGenerator.py b/lib/streamlit/DeltaGenerator.py index 27e5f5f419b5..b9a14a715acf 100644 --- a/lib/streamlit/DeltaGenerator.py +++ b/lib/streamlit/DeltaGenerator.py @@ -2351,7 +2351,7 @@ def time_input(self, element, label, value=None, key=None): return current_value @_with_element - def date_input(self, element, label, value=None, key=None): + def date_input(self, element, label, value=None, min_date=date(1970, 1, 1), max_date=None, key=None): """Display a date input widget. Parameters @@ -2361,6 +2361,10 @@ def date_input(self, element, label, value=None, key=None): value : datetime.date/datetime.datetime The value of this widget when it first renders. This will be cast to str internally. Defaults to today. + min_date : datetime.date/datetime.datetime + A min date that is selectable. Defaults to 1970/1/1 + max_date : datetime.date/datetime.datetime + A max date that is selectable. key : str An optional string to use as the unique key for the widget. If this is omitted, a key will be generated for the widget @@ -2397,6 +2401,18 @@ def date_input(self, element, label, value=None, key=None): element.date_input.label = label element.date_input.default = date.strftime(value, "%Y/%m/%d") + if isinstance(min_date, datetime): + min_date = min_date.date() + + element.date_input.min_date = date.strftime(min_date, "%Y/%m/%d") + + if max_date is not None: + if isinstance(max_date, datetime): + max_date = max_date.date() + + element.date_input.max_date = date.strftime(max_date, "%Y/%m/%d") + + ui_value = _get_widget_ui_value("date_input", element, user_key=key) current_value = ( datetime.strptime(ui_value, "%Y/%m/%d").date() diff --git a/proto/streamlit/proto/DateInput.proto b/proto/streamlit/proto/DateInput.proto index f51edf0e1ac9..5ce923dfc121 100644 --- a/proto/streamlit/proto/DateInput.proto +++ b/proto/streamlit/proto/DateInput.proto @@ -20,4 +20,6 @@ message DateInput { string id = 1; string label = 2; string default = 3; + string min_date = 4; + string max_date = 5; } From 27ec4f4860a92bcf2355d9468ff2226d74f3bc2b Mon Sep 17 00:00:00 2001 From: arraydude Date: Thu, 7 May 2020 16:52:27 -0300 Subject: [PATCH 2/6] Fixing comments: - min instead of min_date - max instead of max_date - updated docs --- .../widgets/DateInput/DateInput.test.tsx | 10 +++---- .../widgets/DateInput/DateInput.tsx | 4 +-- lib/streamlit/DeltaGenerator.py | 27 +++++++++---------- proto/streamlit/proto/DateInput.proto | 4 +-- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/widgets/DateInput/DateInput.test.tsx b/frontend/src/components/widgets/DateInput/DateInput.test.tsx index f1ca8ac2aee2..c0c84a133284 100644 --- a/frontend/src/components/widgets/DateInput/DateInput.test.tsx +++ b/frontend/src/components/widgets/DateInput/DateInput.test.tsx @@ -33,7 +33,7 @@ const getProps = (elementProps: Partial = {}): Props => ({ id: 1, label: "Label", default: "1970/01/01", - minDate: "1970/01/01", + min: "1970/01/01", ...elementProps, }), width: 0, @@ -106,19 +106,19 @@ describe("DateInput widget", () => { }) it("should have a minDate", () => { - expect(wrapper.find(UIDatePicker).prop("minDate")).toStrictEqual( + expect(wrapper.find(UIDatePicker).prop("min")).toStrictEqual( new Date("1970/1/1") ) - expect(wrapper.find(UIDatePicker).prop("maxDate")).toBeUndefined() + expect(wrapper.find(UIDatePicker).prop("max")).toBeUndefined() }) it("should have a maxDate if it is passed", () => { const props = getProps({ - maxDate: "2030/02/06", + max: "2030/02/06", }) const wrapper = shallow() - expect(wrapper.find(UIDatePicker).prop("maxDate")).toStrictEqual( + expect(wrapper.find(UIDatePicker).prop("max")).toStrictEqual( new Date("2030/02/06") ) }) diff --git a/frontend/src/components/widgets/DateInput/DateInput.tsx b/frontend/src/components/widgets/DateInput/DateInput.tsx index 47470a237b28..24072e21e14f 100644 --- a/frontend/src/components/widgets/DateInput/DateInput.tsx +++ b/frontend/src/components/widgets/DateInput/DateInput.tsx @@ -58,7 +58,7 @@ class DateInput extends React.PureComponent { private getMaxDate = (): Date | undefined => { const { element } = this.props - const maxDate = element.get("maxDate") + const maxDate = element.get("max") return maxDate && maxDate.length > 0 ? stringToDate(maxDate) : undefined } @@ -69,7 +69,7 @@ class DateInput extends React.PureComponent { const style = { width } const label = element.get("label") - const minDate = element.get("minDate") + const minDate = element.get("min") const maxDate = this.getMaxDate() return ( diff --git a/lib/streamlit/DeltaGenerator.py b/lib/streamlit/DeltaGenerator.py index b9a14a715acf..991bf3d94db4 100644 --- a/lib/streamlit/DeltaGenerator.py +++ b/lib/streamlit/DeltaGenerator.py @@ -2351,20 +2351,20 @@ def time_input(self, element, label, value=None, key=None): return current_value @_with_element - def date_input(self, element, label, value=None, min_date=date(1970, 1, 1), max_date=None, key=None): + def date_input(self, element, label, value=None, min_value=date(1970, 1, 1), max_value=None, key=None): """Display a date input widget. Parameters ---------- label : str A short label explaining to the user what this date input is for. - value : datetime.date/datetime.datetime + value : datetime.date or datetime.datetime The value of this widget when it first renders. This will be cast to str internally. Defaults to today. - min_date : datetime.date/datetime.datetime - A min date that is selectable. Defaults to 1970/1/1 - max_date : datetime.date/datetime.datetime - A max date that is selectable. + min_value : datetime.date or datetime.datetime + The minimum selectable date. Defaults to 1970/1/1. + max_value : datetime.date or datetime.datetime + The maximum selectable date. Defaults to 2030/12. key : str An optional string to use as the unique key for the widget. If this is omitted, a key will be generated for the widget @@ -2401,17 +2401,16 @@ def date_input(self, element, label, value=None, min_date=date(1970, 1, 1), max_ element.date_input.label = label element.date_input.default = date.strftime(value, "%Y/%m/%d") - if isinstance(min_date, datetime): - min_date = min_date.date() + if isinstance(min_value, datetime): + min_value = min_value.date() - element.date_input.min_date = date.strftime(min_date, "%Y/%m/%d") + element.date_input.min = date.strftime(min_value, "%Y/%m/%d") - if max_date is not None: - if isinstance(max_date, datetime): - max_date = max_date.date() - - element.date_input.max_date = date.strftime(max_date, "%Y/%m/%d") + if max_value is not None: + if isinstance(max_value, datetime): + max_value = max_value.date() + element.date_input.max = date.strftime(max_value, "%Y/%m/%d") ui_value = _get_widget_ui_value("date_input", element, user_key=key) current_value = ( diff --git a/proto/streamlit/proto/DateInput.proto b/proto/streamlit/proto/DateInput.proto index 5ce923dfc121..b46fe128c6ea 100644 --- a/proto/streamlit/proto/DateInput.proto +++ b/proto/streamlit/proto/DateInput.proto @@ -20,6 +20,6 @@ message DateInput { string id = 1; string label = 2; string default = 3; - string min_date = 4; - string max_date = 5; + string min = 4; + string max = 5; } From af5c5fe7f98cf196e1d220b201f07e7ad7e48603 Mon Sep 17 00:00:00 2001 From: arraydude Date: Thu, 7 May 2020 17:14:24 -0300 Subject: [PATCH 3/6] Using datetime.min --- .../widgets/DateInput/DateInput.tsx | 19 ++++++------------- lib/streamlit/DeltaGenerator.py | 4 ++-- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/widgets/DateInput/DateInput.tsx b/frontend/src/components/widgets/DateInput/DateInput.tsx index 24072e21e14f..1efc449bf3cd 100644 --- a/frontend/src/components/widgets/DateInput/DateInput.tsx +++ b/frontend/src/components/widgets/DateInput/DateInput.tsx @@ -52,7 +52,8 @@ class DateInput extends React.PureComponent { } private handleChange = ({ date }: { date: Date | Date[] }): void => { - const value = dateToString(date as Date) + const value = moment(date as Date).format("YYYY/MM/DD") + this.setState({ value }, () => this.setWidgetValue({ fromUi: true })) } @@ -60,7 +61,7 @@ class DateInput extends React.PureComponent { const { element } = this.props const maxDate = element.get("max") - return maxDate && maxDate.length > 0 ? stringToDate(maxDate) : undefined + return maxDate && maxDate.length > 0 ? new Date(maxDate) : undefined } public render = (): React.ReactNode => { @@ -69,7 +70,7 @@ class DateInput extends React.PureComponent { const style = { width } const label = element.get("label") - const minDate = element.get("min") + const minDate = new Date(element.get("min")) const maxDate = this.getMaxDate() return ( @@ -80,8 +81,8 @@ class DateInput extends React.PureComponent { disabled={disabled} onChange={this.handleChange} overrides={datePickerOverrides} - value={stringToDate(value)} - minDate={stringToDate(minDate)} + value={new Date(value)} + minDate={minDate} maxDate={maxDate} /> @@ -89,12 +90,4 @@ class DateInput extends React.PureComponent { } } -function dateToString(date: Date): string { - return moment(date).format("YYYY/MM/DD") -} - -function stringToDate(value: string): Date { - return moment(value, "YYYY/MM/DD").toDate() -} - export default DateInput diff --git a/lib/streamlit/DeltaGenerator.py b/lib/streamlit/DeltaGenerator.py index 991bf3d94db4..53a938f56923 100644 --- a/lib/streamlit/DeltaGenerator.py +++ b/lib/streamlit/DeltaGenerator.py @@ -2351,7 +2351,7 @@ def time_input(self, element, label, value=None, key=None): return current_value @_with_element - def date_input(self, element, label, value=None, min_value=date(1970, 1, 1), max_value=None, key=None): + def date_input(self, element, label, value=None, min_value=datetime.min, max_value=None, key=None): """Display a date input widget. Parameters @@ -2362,7 +2362,7 @@ def date_input(self, element, label, value=None, min_value=date(1970, 1, 1), max The value of this widget when it first renders. This will be cast to str internally. Defaults to today. min_value : datetime.date or datetime.datetime - The minimum selectable date. Defaults to 1970/1/1. + The minimum selectable date. Defaults to datetime.min. max_value : datetime.date or datetime.datetime The maximum selectable date. Defaults to 2030/12. key : str From bbae70498de65aa636db5526a10cc497fab333dc Mon Sep 17 00:00:00 2001 From: arraydude Date: Thu, 7 May 2020 17:16:21 -0300 Subject: [PATCH 4/6] Updating e2e --- e2e/scripts/st_date_input.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/scripts/st_date_input.py b/e2e/scripts/st_date_input.py index 995fd9094434..b9f0486ca283 100644 --- a/e2e/scripts/st_date_input.py +++ b/e2e/scripts/st_date_input.py @@ -16,7 +16,7 @@ from datetime import datetime from datetime import date -w1 = st.date_input("Label 1", date(1970, 1, 1)) +w1 = st.date_input("Label 1", date(1970, 1, 1), min_value=date(1970, 1, 1)) st.write("Value 1:", w1) w2 = st.date_input("Label 2", datetime(2019, 7, 6, 21, 15)) From d94c809e9a7351cc2056d669e78d3357e9c3fd15 Mon Sep 17 00:00:00 2001 From: arraydude Date: Thu, 7 May 2020 19:22:05 -0300 Subject: [PATCH 5/6] Fixing unit test --- .../src/components/widgets/DateInput/DateInput.test.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/widgets/DateInput/DateInput.test.tsx b/frontend/src/components/widgets/DateInput/DateInput.test.tsx index c0c84a133284..18261d0b7841 100644 --- a/frontend/src/components/widgets/DateInput/DateInput.test.tsx +++ b/frontend/src/components/widgets/DateInput/DateInput.test.tsx @@ -33,7 +33,7 @@ const getProps = (elementProps: Partial = {}): Props => ({ id: 1, label: "Label", default: "1970/01/01", - min: "1970/01/01", + min: "1970/1/1", ...elementProps, }), width: 0, @@ -106,10 +106,10 @@ describe("DateInput widget", () => { }) it("should have a minDate", () => { - expect(wrapper.find(UIDatePicker).prop("min")).toStrictEqual( + expect(wrapper.find(UIDatePicker).prop("minDate")).toStrictEqual( new Date("1970/1/1") ) - expect(wrapper.find(UIDatePicker).prop("max")).toBeUndefined() + expect(wrapper.find(UIDatePicker).prop("maxDate")).toBeUndefined() }) it("should have a maxDate if it is passed", () => { @@ -118,7 +118,7 @@ describe("DateInput widget", () => { }) const wrapper = shallow() - expect(wrapper.find(UIDatePicker).prop("max")).toStrictEqual( + expect(wrapper.find(UIDatePicker).prop("maxDate")).toStrictEqual( new Date("2030/02/06") ) }) From b68a8fd8d259a7dc795056cc5df866c4c38d3b4b Mon Sep 17 00:00:00 2001 From: arraydude Date: Fri, 8 May 2020 12:57:04 -0300 Subject: [PATCH 6/6] Setting default max_value as today+10y --- lib/streamlit/DeltaGenerator.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/streamlit/DeltaGenerator.py b/lib/streamlit/DeltaGenerator.py index 53a938f56923..7cc050c51ec2 100644 --- a/lib/streamlit/DeltaGenerator.py +++ b/lib/streamlit/DeltaGenerator.py @@ -2364,7 +2364,7 @@ def date_input(self, element, label, value=None, min_value=datetime.min, max_val min_value : datetime.date or datetime.datetime The minimum selectable date. Defaults to datetime.min. max_value : datetime.date or datetime.datetime - The maximum selectable date. Defaults to 2030/12. + The maximum selectable date. Defaults to today+10y. key : str An optional string to use as the unique key for the widget. If this is omitted, a key will be generated for the widget @@ -2406,11 +2406,14 @@ def date_input(self, element, label, value=None, min_value=datetime.min, max_val element.date_input.min = date.strftime(min_value, "%Y/%m/%d") - if max_value is not None: - if isinstance(max_value, datetime): - max_value = max_value.date() + if max_value is None: + today = date.today() + max_value = date(today.year+10, today.month, today.day) + + if isinstance(max_value, datetime): + max_value = max_value.date() - element.date_input.max = date.strftime(max_value, "%Y/%m/%d") + element.date_input.max = date.strftime(max_value, "%Y/%m/%d") ui_value = _get_widget_ui_value("date_input", element, user_key=key) current_value = (