From 5a78f27a3ba13882527dd2b6aa58b896d3a75274 Mon Sep 17 00:00:00 2001 From: Marcus Kamps Date: Tue, 29 Sep 2020 18:11:19 -0400 Subject: [PATCH 1/4] Refactor isTimeDisabled --- src/date_utils.js | 23 ++++++++++++----------- src/time.jsx | 12 ++++-------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/date_utils.js b/src/date_utils.js index 3ec2f2773..5b0fdf1d8 100644 --- a/src/date_utils.js +++ b/src/date_utils.js @@ -475,18 +475,19 @@ export function isOutOfBounds(day, { minDate, maxDate } = {}) { ); } -export function isTimeDisabled(time, disabledTimes) { - const l = disabledTimes.length; - for (let i = 0; i < l; i++) { - if ( - getHours(disabledTimes[i]) === getHours(time) && - getMinutes(disabledTimes[i]) === getMinutes(time) - ) { - return true; - } - } +export function isTimeInList(time, times) { + return times.some(listTime => ( + getHours(listTime) === getHours(time) && + getMinutes(listTime) === getMinutes(time) + )); +} - return false; +export function isTimeDisabled(time, { excludeTimes, includeTimes } = {}) { + return ( + (excludeTimes && isTimeInList(time, excludeTimes)) || + (includeTimes && !isTimeInList(time, includeTimes)) || + false + ); } export function isTimeInDisabledRange(time, { minTime, maxTime }) { diff --git a/src/time.jsx b/src/time.jsx index b6ba2ac50..4d050f821 100644 --- a/src/time.jsx +++ b/src/time.jsx @@ -77,10 +77,8 @@ export default class Time extends React.Component { if ( ((this.props.minTime || this.props.maxTime) && isTimeInDisabledRange(time, this.props)) || - (this.props.excludeTimes && - isTimeDisabled(time, this.props.excludeTimes)) || - (this.props.includeTimes && - !isTimeDisabled(time, this.props.includeTimes)) + ((this.props.excludeTimes || this.props.includeTimes) && + isTimeDisabled(time, this.props)) ) { return; } @@ -105,10 +103,8 @@ export default class Time extends React.Component { if ( ((this.props.minTime || this.props.maxTime) && isTimeInDisabledRange(time, this.props)) || - (this.props.excludeTimes && - isTimeDisabled(time, this.props.excludeTimes)) || - (this.props.includeTimes && - !isTimeDisabled(time, this.props.includeTimes)) + ((this.props.excludeTimes || this.props.includeTimes) && + isTimeDisabled(time, this.props)) ) { classes.push("react-datepicker__time-list-item--disabled"); } From 855d4ffacb960f090b2c7719f4072b4910496022 Mon Sep 17 00:00:00 2001 From: Marcus Kamps Date: Tue, 29 Sep 2020 18:19:58 -0400 Subject: [PATCH 2/4] Add filterTime prop, pass to isTimeDisabled --- docs/datepicker.md | 1 + docs/index.md | 1 + docs/time.md | 1 + src/calendar.jsx | 2 ++ src/date_utils.js | 3 ++- src/index.jsx | 2 ++ src/time.jsx | 5 +++-- 7 files changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/datepicker.md b/docs/datepicker.md index b7195d3b2..c1c668212 100644 --- a/docs/datepicker.md +++ b/docs/datepicker.md @@ -26,6 +26,7 @@ General datepicker component. | `excludeTimes` | `array` | | | | `excludeScrollbar` | `array` | | | | `filterDate` | `func` | | | +| `filterTime` | `func` | | | | `fixedHeight` | `bool` | | | | `forceShowMonthNavigation` | `bool` | | | | `formatWeekNumber` | `func` | | | diff --git a/docs/index.md b/docs/index.md index 6e7b7cd15..42adc9713 100644 --- a/docs/index.md +++ b/docs/index.md @@ -33,6 +33,7 @@ |`excludeScrollbar`|`bool`|`true`|| |`excludeTimes`|`array`||| |`filterDate`|`func`||| +|`filterTime`|`func`||| |`fixedHeight`|`bool`||| |`focusSelectedMonth`|`bool`|`false`|| |`forceShowMonthNavigation`|`bool`||| diff --git a/docs/time.md b/docs/time.md index 63535bb6e..5c7dd7112 100644 --- a/docs/time.md +++ b/docs/time.md @@ -5,6 +5,7 @@ | name | type | default value | description | |---|---|---|---| |`excludeTimes`|`array`||| +|`filterTime`|`func`||| |`format`|`string`||| |`includeTimes`|`array`||| |`injectTimes`|`array`||| diff --git a/src/calendar.jsx b/src/calendar.jsx index da611111d..22881db32 100644 --- a/src/calendar.jsx +++ b/src/calendar.jsx @@ -130,6 +130,7 @@ export default class Calendar extends React.Component { minTime: PropTypes.instanceOf(Date), maxTime: PropTypes.instanceOf(Date), excludeTimes: PropTypes.array, + filterTime: PropTypes.func, timeCaption: PropTypes.string, openToDate: PropTypes.instanceOf(Date), peekNextMonth: PropTypes.bool, @@ -858,6 +859,7 @@ export default class Calendar extends React.Component { minTime={this.props.minTime} maxTime={this.props.maxTime} excludeTimes={this.props.excludeTimes} + filterTime={this.props.filterTime} timeCaption={this.props.timeCaption} todayButton={this.props.todayButton} showMonthDropdown={this.props.showMonthDropdown} diff --git a/src/date_utils.js b/src/date_utils.js index 5b0fdf1d8..5ef819e24 100644 --- a/src/date_utils.js +++ b/src/date_utils.js @@ -482,10 +482,11 @@ export function isTimeInList(time, times) { )); } -export function isTimeDisabled(time, { excludeTimes, includeTimes } = {}) { +export function isTimeDisabled(time, { excludeTimes, includeTimes, filterTime } = {}) { return ( (excludeTimes && isTimeInList(time, excludeTimes)) || (includeTimes && !isTimeInList(time, includeTimes)) || + (filterTime && !filterTime(time)) || false ); } diff --git a/src/index.jsx b/src/index.jsx index b0ad64452..a1c59652f 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -229,6 +229,7 @@ export default class DatePicker extends React.Component { minTime: PropTypes.instanceOf(Date), maxTime: PropTypes.instanceOf(Date), excludeTimes: PropTypes.array, + filterTime: PropTypes.func, useShortMonthInDropdown: PropTypes.bool, clearButtonTitle: PropTypes.string, previousMonthButtonLabel: PropTypes.oneOfType([ @@ -846,6 +847,7 @@ export default class DatePicker extends React.Component { minTime={this.props.minTime} maxTime={this.props.maxTime} excludeTimes={this.props.excludeTimes} + filterTime={this.props.filterTime} timeCaption={this.props.timeCaption} className={this.props.calendarClassName} container={this.props.calendarContainer} diff --git a/src/time.jsx b/src/time.jsx index 4d050f821..4ae7de7f7 100644 --- a/src/time.jsx +++ b/src/time.jsx @@ -44,6 +44,7 @@ export default class Time extends React.Component { minTime: PropTypes.instanceOf(Date), maxTime: PropTypes.instanceOf(Date), excludeTimes: PropTypes.array, + filterTime: PropTypes.func, monthRef: PropTypes.object, timeCaption: PropTypes.string, injectTimes: PropTypes.array, @@ -77,7 +78,7 @@ export default class Time extends React.Component { if ( ((this.props.minTime || this.props.maxTime) && isTimeInDisabledRange(time, this.props)) || - ((this.props.excludeTimes || this.props.includeTimes) && + ((this.props.excludeTimes || this.props.includeTimes || this.props.filterTime) && isTimeDisabled(time, this.props)) ) { return; @@ -103,7 +104,7 @@ export default class Time extends React.Component { if ( ((this.props.minTime || this.props.maxTime) && isTimeInDisabledRange(time, this.props)) || - ((this.props.excludeTimes || this.props.includeTimes) && + ((this.props.excludeTimes || this.props.includeTimes || this.props.filterTime) && isTimeDisabled(time, this.props)) ) { classes.push("react-datepicker__time-list-item--disabled"); From 4a1570ff0a2ce7fdac6f8fc2575e16f4d12a6669 Mon Sep 17 00:00:00 2001 From: Marcus Kamps Date: Tue, 29 Sep 2020 19:07:55 -0400 Subject: [PATCH 3/4] Add unit tests --- src/date_utils.js | 2 +- test/date_utils_test.js | 55 +++++++++++++++++++++++++++++++++++++++ test/filter_times_test.js | 28 ++++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 test/filter_times_test.js diff --git a/src/date_utils.js b/src/date_utils.js index 5ef819e24..b31d08bf0 100644 --- a/src/date_utils.js +++ b/src/date_utils.js @@ -251,7 +251,7 @@ export { addMinutes, addDays, addWeeks, addMonths, addYears }; // *** Subtraction *** -export { subMinutes, subHours, subDays, subWeeks, subMonths, subYears }; +export { addHours, subMinutes, subHours, subDays, subWeeks, subMonths, subYears }; // ** Date Comparison ** diff --git a/test/date_utils_test.js b/test/date_utils_test.js index c294adbb5..ea6dc161f 100644 --- a/test/date_utils_test.js +++ b/test/date_utils_test.js @@ -1,5 +1,6 @@ import { newDate, + addHours, addDays, subDays, isEqual, @@ -18,6 +19,7 @@ import { getEffectiveMinDate, getEffectiveMaxDate, addZero, + isTimeDisabled, isTimeInDisabledRange, isDayInRange, parseDate, @@ -550,6 +552,59 @@ describe("date_utils", function() { }); }); + describe("isTimeDisabled", function() { + it("should be enabled by default", () => { + const date = newDate(); + const time = setHours(setMinutes(date, 30), 1); + expect(isTimeDisabled(time)).to.be.false; + }); + + it("should be disabled if in excluded times", () => { + const date = newDate(); + const time = setHours(setMinutes(date, 30), 1); + expect(isTimeDisabled(time, { excludeTimes: [time] })).to.be.true; + }); + + it("should be enabled if in included times", () => { + const date = newDate(); + const time = setHours(setMinutes(date, 30), 1); + expect(isTimeDisabled(time, { includeTimes: [time] })).to.be.false; + }); + + it("should be disabled if not in included times", () => { + const date = newDate(); + const time = setHours(setMinutes(date, 30), 1); + const includeTimes = [addHours(time, 1)]; + expect(isTimeDisabled(time, { includeTimes })).to.be.true; + }); + + it("should be enabled if time filter returns true", () => { + const date = newDate(); + const time = setHours(setMinutes(date, 30), 1); + const filterTime = t => isEqual(t, time); + expect(isTimeDisabled(time, { filterTime })).to.be.false; + }); + + it("should be disabled if time filter returns false", () => { + const date = newDate(); + const time = setHours(setMinutes(date, 30), 1); + const filterTime = t => !isEqual(t, time); + expect(isTimeDisabled(time, { filterTime })).to.be.true; + }); + + it("should not allow time filter to modify input time", () => { + const date = newDate(); + const time = setHours(setMinutes(date, 30), 1); + const timeClone = newDate(time); + const filterTime = t => { + addHours(t, 1); + return true; + }; + isTimeDisabled(time, { filterTime }); + expect(isEqual(time, timeClone)).to.be.true; + }); + }); + describe("isTimeInDisabledRange", () => { it("should tell if time is in disabled range", () => { const date = newDate("2016-03-15"); diff --git a/test/filter_times_test.js b/test/filter_times_test.js new file mode 100644 index 000000000..d225f9afa --- /dev/null +++ b/test/filter_times_test.js @@ -0,0 +1,28 @@ +import React from "react"; +import { mount } from "enzyme"; +import { getHours } from "../src/date_utils"; +import DatePicker from "../src/index.jsx"; +import TimeComponent from "../src/time"; + +describe("TimeComponent", () => { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should disable times matched by filterTime prop", () => { + const timeComponent = mount( + getHours(time) !== 17} + /> + ); + + expect(timeComponent.find(".react-datepicker__time-list-item--disabled")) + .to.have.length(2); + }); +}); From 7a909ca434a3f78ea905c11e93d987aa3bb3c836 Mon Sep 17 00:00:00 2001 From: Marcus Kamps Date: Tue, 29 Sep 2020 19:48:15 -0400 Subject: [PATCH 4/4] Add example --- docs-site/src/components/Examples/index.js | 5 +++++ docs-site/src/examples/.eslintrc | 1 + docs-site/src/examples/filterTimes.js | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 docs-site/src/examples/filterTimes.js diff --git a/docs-site/src/components/Examples/index.js b/docs-site/src/components/Examples/index.js index 105a1a5dd..e192e4b57 100644 --- a/docs-site/src/components/Examples/index.js +++ b/docs-site/src/components/Examples/index.js @@ -11,6 +11,7 @@ import ShowTimeOnly from "../../examples/showTimeOnly"; import ExcludeTimes from "../../examples/excludeTimes"; import IncludeTimes from "../../examples/includeTimes"; import InjectTimes from "../../examples/injectTimes"; +import FilterTimes from "../../examples/filterTimes"; import ExcludeTimePeriod from "../../examples/excludeTimePeriod"; import CustomDateFormat from "../../examples/customDateFormat"; import CustomClassName from "../../examples/customClassName"; @@ -195,6 +196,10 @@ export default class exampleComponents extends React.Component { title: "Filter dates", component: FilterDates }, + { + title: "Filter times", + component: FilterTimes + }, { title: "Fixed height of Calendar", component: FixedCalendar diff --git a/docs-site/src/examples/.eslintrc b/docs-site/src/examples/.eslintrc index a58b07f55..ac3e5796e 100644 --- a/docs-site/src/examples/.eslintrc +++ b/docs-site/src/examples/.eslintrc @@ -13,6 +13,7 @@ "useState": false, "render": false, "DatePicker": false, + "getHours": false, "setHours": false, "setMinutes": false, "getDate": false, diff --git a/docs-site/src/examples/filterTimes.js b/docs-site/src/examples/filterTimes.js new file mode 100644 index 000000000..a52da736e --- /dev/null +++ b/docs-site/src/examples/filterTimes.js @@ -0,0 +1,18 @@ +() => { + const [startDate, setStartDate] = useState( + setHours(setMinutes(new Date(), 0), 9) + ); + const from9to5 = time => { + const hour = getHours(time); + return hour >= 9 && hour < 17; + } + return ( + setStartDate(date)} + showTimeSelect + filterTime={from9to5} + dateFormat="MMMM d, yyyy h:mm aa" + /> + ); +};