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)) diff --git a/frontend/src/components/widgets/DateInput/DateInput.test.tsx b/frontend/src/components/widgets/DateInput/DateInput.test.tsx index 31bf427a28d7..18261d0b7841 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", + min: "1970/1/1", ...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({ + max: "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..1efc449bf3cd 100644 --- a/frontend/src/components/widgets/DateInput/DateInput.tsx +++ b/frontend/src/components/widgets/DateInput/DateInput.tsx @@ -52,35 +52,42 @@ 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 })) } + private getMaxDate = (): Date | undefined => { + const { element } = this.props + const maxDate = element.get("max") + + return maxDate && maxDate.length > 0 ? new Date(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 = new Date(element.get("min")) + const maxDate = this.getMaxDate() return (
) } } -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 27e5f5f419b5..7cc050c51ec2 100644 --- a/lib/streamlit/DeltaGenerator.py +++ b/lib/streamlit/DeltaGenerator.py @@ -2351,16 +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, 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 ---------- 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_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 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 @@ -2397,6 +2401,20 @@ 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_value, datetime): + min_value = min_value.date() + + element.date_input.min = date.strftime(min_value, "%Y/%m/%d") + + 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") + 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..b46fe128c6ea 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 = 4; + string max = 5; }