Skip to content

Commit e4ed031

Browse files
maranomynetlaug
authored andcommitted
fix: parseDate should prefer the first matching format in array
Fix involves vastly simplifying the internal code-paths of `parseDate`, to prevent further and repeated divergence of behavior when parsing `dateFormat` as `Array<string>` vs. as `string` NOTE: Removing the (redundant) `minDate` parameter has no effect on the tests, as minDate/maxDate boundry checks are enforced elsewhere in the component's value-updating lifecycle. NOTE 2: Adding instead `refDate` (using `props.selected`) to fully utilize the features of `date-fns/parse`. NOTE 3: The old behavior of re-parsing borked values using `new Date()` was somewhat dubious as it gave different results depending on the Browser/OS running the code. See more here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date#parameters
1 parent 6dfbbe6 commit e4ed031

File tree

4 files changed

+43
-44
lines changed

4 files changed

+43
-44
lines changed

src/date_utils.ts

Lines changed: 16 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -118,54 +118,35 @@ export function newDate(value?: string | Date | number | null): Date {
118118
* @param dateFormat - The date format.
119119
* @param locale - The locale.
120120
* @param strictParsing - The strict parsing flag.
121-
* @param minDate - The minimum date.
121+
* @param refDate - The base date to be passed to date-fns parse() function.
122122
* @returns - The parsed date or null.
123123
*/
124124
export function parseDate(
125125
value: string,
126126
dateFormat: string | string[],
127127
locale: Locale | undefined,
128128
strictParsing: boolean,
129-
minDate?: Date,
129+
refDate?: Date,
130130
): Date | null {
131-
let parsedDate = null;
132131
const localeObject =
133132
getLocaleObject(locale) || getLocaleObject(getDefaultLocale());
134-
let strictParsingValueMatch = true;
135-
if (Array.isArray(dateFormat)) {
136-
dateFormat.forEach((df) => {
137-
const tryParseDate = parse(value, df, new Date(), {
138-
locale: localeObject,
139-
useAdditionalWeekYearTokens: true,
140-
useAdditionalDayOfYearTokens: true,
141-
});
142-
if (strictParsing) {
143-
strictParsingValueMatch =
144-
isValid(tryParseDate, minDate) &&
145-
value === formatDate(tryParseDate, df, locale);
146-
}
147-
if (isValid(tryParseDate, minDate) && strictParsingValueMatch) {
148-
parsedDate = tryParseDate;
149-
}
150-
});
151-
return parsedDate;
152-
}
153133

154-
parsedDate = parse(value, dateFormat, new Date(), {
155-
locale: localeObject,
156-
useAdditionalWeekYearTokens: true,
157-
useAdditionalDayOfYearTokens: true,
158-
});
134+
const formats = Array.isArray(dateFormat) ? dateFormat : [dateFormat];
159135

160-
if (strictParsing) {
161-
strictParsingValueMatch =
136+
for (const format of formats) {
137+
const parsedDate = parse(value, format, refDate || newDate(), {
138+
locale: localeObject,
139+
useAdditionalWeekYearTokens: true,
140+
useAdditionalDayOfYearTokens: true,
141+
});
142+
if (
162143
isValid(parsedDate) &&
163-
value === formatDate(parsedDate, dateFormat, locale);
164-
} else if (!isValid(parsedDate)) {
165-
parsedDate = new Date(value);
144+
(!strictParsing || value === formatDate(parsedDate, format, locale))
145+
) {
146+
return parsedDate;
147+
}
166148
}
167-
168-
return isValid(parsedDate) && strictParsingValueMatch ? parsedDate : null;
149+
return null;
169150
}
170151

171152
// ** Date "Reflection" **
@@ -213,13 +194,7 @@ export function formatDate(
213194
`A locale object was not found for the provided string ["${locale}"].`,
214195
);
215196
}
216-
if (
217-
!localeObj &&
218-
!!getDefaultLocale() &&
219-
!!getLocaleObject(getDefaultLocale())
220-
) {
221-
localeObj = getLocaleObject(getDefaultLocale());
222-
}
197+
localeObj = localeObj || getLocaleObject(getDefaultLocale());
223198
return format(date, formatStr, {
224199
locale: localeObj,
225200
useAdditionalWeekYearTokens: true,

src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ export default class DatePicker extends Component<
606606
dateFormat,
607607
this.props.locale,
608608
strictParsing,
609-
this.props.minDate,
609+
this.props.selected ?? undefined,
610610
);
611611
// Use date from `selected` prop when manipulating only time for input value
612612
if (

src/test/date_utils_test.test.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -978,11 +978,35 @@ describe("date_utils", () => {
978978

979979
it("should parse date that matches one of the formats", () => {
980980
const value = "01/15/2019";
981-
const dateFormat = ["MM/dd/yyyy", "yyyy-MM-dd"];
981+
const dateFormat = ["yyyy-MM-dd", "MM/dd/yyyy"];
982982

983983
expect(parseDate(value, dateFormat, undefined, true)).not.toBeNull();
984984
});
985985

986+
it("should prefer the first matching format in array (strict)", () => {
987+
const value = "01/06/2019";
988+
const valueLax = "1/6/2019";
989+
const dateFormat = ["MM/dd/yyyy", "dd/MM/yyyy"];
990+
991+
const expected = new Date(2019, 0, 6);
992+
993+
expect(parseDate(value, dateFormat, undefined, true)).toEqual(expected);
994+
expect(parseDate(valueLax, dateFormat, undefined, true)).toBeNull();
995+
});
996+
997+
it("should prefer the first matching format in array", () => {
998+
const value = "01/06/2019";
999+
const valueLax = "1/6/2019";
1000+
const dateFormat = ["MM/dd/yyyy", "dd/MM/yyyy"];
1001+
1002+
const expected = new Date(2019, 0, 6);
1003+
1004+
expect(parseDate(value, dateFormat, undefined, false)).toEqual(expected);
1005+
expect(parseDate(valueLax, dateFormat, undefined, false)).toEqual(
1006+
expected,
1007+
);
1008+
});
1009+
9861010
it("should not parse date that does not match the format", () => {
9871011
const value = "01/15/20";
9881012
const dateFormat = "MM/dd/yyyy";

src/test/min_time_test.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ describe("Datepicker minTime", () => {
7979
<DatePickerWithState minTime={minTime} maxTime={maxTime} />,
8080
);
8181
const input = container.querySelector("input") ?? new HTMLInputElement();
82-
fireEvent.change(input, { target: { value: "2023-03-10 16:00" } });
82+
fireEvent.change(input, { target: { value: "03/10/2023 16:00" } });
8383
fireEvent.focusOut(input);
8484

8585
expect(input.value).toEqual("03/10/2023 16:00");

0 commit comments

Comments
 (0)