-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Inconsistent/broken behavior in parseDate
#3988
Conversation
Parsing `valueEn` as a Portugese date should fail, but accidentally gets parsed using the default/en-US locale, because parseDate internally retries and accidentally omits[^1] the original locale option that time around. [^1]: https://github.com/Hacker0x01/react-datepicker/blob/5c1d6d923931535f105f3dddbb6f3e10fd8dd25c/src/date_utils.js#L121
…making sure input values are applied in a more consistent manner, and with formatting that matches the currently active dateFormat (usually the default format). This only affects clarity/readability. All tests still pass.
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
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #3988 +/- ##
==========================================
- Coverage 94.21% 94.09% -0.13%
==========================================
Files 20 20
Lines 1746 1726 -20
Branches 419 419
==========================================
- Hits 1645 1624 -21
- Misses 30 31 +1
Partials 71 71 ☔ View full report in Codecov by Sentry. |
It's been a long time since this was first submitted, but I agree with the thinking here. If you're still willing to bring this forward we can work towards releasing this as part of a major version bump. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ This pull request was sent to the PullRequest network.
@maranomynet you can click here to see the review status or cancel the code review job - or - cancel by adding [!pr] to the title of the pull request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PullRequest Breakdown
Reviewable lines of change
+ 83
- 70
51% JavaScript
49% JavaScript (tests)
Type of change
Fix - These changes are likely to be fixing a bug or issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PullRequest Breakdown
Reviewable lines of change
+ 83
- 70
51% JavaScript
49% JavaScript (tests)
Type of change
Fix - These changes are likely to be fixing a bug or issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any issues with this. Thanks for adding tests. I did add one minor comment for consideration.
Reviewed with ❤️ by PullRequest
const format = formats[i]; | ||
const parsedDate = parse(value, format, refDate, { locale: localeObject }); | ||
if ( | ||
isValid(parsedDate /* , minDate */) && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Due to inactivity, PullRequest has cancelled this review job. You can reactivate the code review job from the PullRequest dashboard.
@maranomynet hello |
@martijnrusschen hello |
@mirus-ua I think that would be good. |
parsedDate = new Date(value); | ||
for (let i = 0, len = formats.length; i < len; i++) { | ||
const format = formats[i]; | ||
const parsedDate = parse(value, format, refDate, { locale: localeObject }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I have not tried this myself (I'm not experienced enough to know how), I believe this would fix a bug of mine that I discovered last week. I'm using UTCDate objects with the datepicker (set as selected
, minDate
, and maxDate
), but dates entered manually in the input box are not getting parsed as UTCDates, and each iteration of manual date input results in an additional timezone offset to the datetime. I think this is because the original call to parse()
passed in a new Date()
for the reference date to date-fns
's parse()
instead of the component's minDate
or some other UTCDate object.
My temporary workaround is to use startOfDay(new UTCDate(newDate)).toISOString()
in the onChange
function.
@@ -146,21 +107,15 @@ export function formatDate(date, formatStr, locale) { | |||
if (locale === "en") { | |||
return format(date, formatStr, { awareOfUnicodeTokens: true }); | |||
} | |||
let localeObj = getLocaleObject(locale); | |||
const localeObj = | |||
getLocaleObject(locale) || getLocaleObject(getDefaultLocale()) || null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding || getLocaleObject(getDefaultLocale())
here will prevent the warning below from being shown when the locale
string is invalid, as localeObj
will not be nullish even when the locale
string is invalid.
@@ -496,6 +496,7 @@ export default class DatePicker extends React.Component { | |||
this.props.dateFormat, | |||
this.props.locale, | |||
this.props.strictParsing, | |||
this.props.selected, | |||
this.props.minDate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parseDate
only takes 5 parameters but is being passed 6 parameters here. This PR renames the 5th from minDate
to refDate
, but minDate
is probably still needed to validate the parsed date correctly.
Edit: based on the commit message, minDate
does not need to be passed to parseDate
, so it should be removed here.
closing in favor of #5036 |
See the expanded commit messages for details.
After this fix, the parsing behavior of
react-datepicker
…a) is more consistent between locales/browsers/OS platforms
b) has fewer bugs
c) is the same whether
dateFormat
is string or an array of stringsd) treats arrayed
dateFormat
values as "most preferred first" (instead of the opposite).e) is slightly more "strict".
Item "e" results from
parseDate
no longer falling back (with string values forprops.dateFormat
) on free-form, hard-to-predictnew Date(value)
parsing of input values whendate-fns
has already declared the value as "invalid".It seems sensible (and more deterministic) to place the responsibility of parsing fully on
date-fns
and not (sometimes) second-guesss its results.The
new Date(value)
parsing causes surprising and deceptively incorrect "false positive" parsing in situations where the dateFormat wasdd.MM.yyyy
and users type in a date in this formatdd.MM.yy
. Then their input gets "helpfully" re-parsed asMM.dd.yy
, which was most definitely not intended by the developer setting up the datepicker input.However, there's still a lot of flexibility in the parsing, as
date-fns/parse
provides very sensible flexibility based on localized RegExp configs, and also sinceprops.dateFormat
can be an Array of valid formattings.As people are often relying on the old mis-behavior this PR needs to become part of a v5 release. A migration guide (and, possibly, updated documentation) could explain how to get the old behavior back by using
props.dateFormat
with multiple formats in order of decreasing priority.