-
-
Notifications
You must be signed in to change notification settings - Fork 245
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
feat: Support ISO 8601 date strings with full precision for all formatting functions where dates can be passed #758
base: main
Are you sure you want to change the base?
Changes from 2 commits
ed74ea6
656e701
275a403
8bbaaa7
fa44e74
d9d31c5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
import DateTimeFormatOptions from './DateTimeFormatOptions'; | ||
import Formats from './Formats'; | ||
import IntlError, {IntlErrorCode} from './IntlError'; | ||
import IntlError, { IntlErrorCode } from './IntlError'; | ||
martinmunillas marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import NumberFormatOptions from './NumberFormatOptions'; | ||
import RelativeTimeFormatOptions from './RelativeTimeFormatOptions'; | ||
import TimeZone from './TimeZone'; | ||
import {defaultOnError} from './defaults'; | ||
import { defaultOnError } from './defaults'; | ||
|
||
const SECOND = 1; | ||
const MINUTE = SECOND * 60; | ||
|
@@ -31,7 +31,7 @@ const UNIT_SECONDS: Record<Intl.RelativeTimeFormatUnit, number> = { | |
quarter: QUARTER, | ||
quarters: QUARTER, | ||
year: YEAR, | ||
years: YEAR | ||
years: YEAR, | ||
} as const; | ||
|
||
function resolveRelativeTimeUnit(seconds: number) { | ||
|
@@ -75,7 +75,7 @@ export default function createFormatter({ | |
locale, | ||
now: globalNow, | ||
onError = defaultOnError, | ||
timeZone: globalTimeZone | ||
timeZone: globalTimeZone, | ||
}: Props) { | ||
function resolveFormatOrOptions<Options>( | ||
typeFormats: Record<string, Options> | undefined, | ||
|
@@ -128,7 +128,7 @@ export default function createFormatter({ | |
|
||
function dateTime( | ||
/** If a number is supplied, this is interpreted as a UTC timestamp. */ | ||
martinmunillas marked this conversation as resolved.
Show resolved
Hide resolved
|
||
value: Date | number, | ||
value: Date | number | string, | ||
/** If a time zone is supplied, the `value` is converted to that time zone. | ||
* Otherwise the user time zone will be used. */ | ||
formatOrOptions?: string | DateTimeFormatOptions | ||
|
@@ -140,7 +140,7 @@ export default function createFormatter({ | |
(options) => { | ||
if (!options?.timeZone) { | ||
if (globalTimeZone) { | ||
options = {...options, timeZone: globalTimeZone}; | ||
options = { ...options, timeZone: globalTimeZone }; | ||
} else { | ||
onError( | ||
new IntlError( | ||
|
@@ -153,6 +153,22 @@ export default function createFormatter({ | |
} | ||
} | ||
|
||
if (typeof value === 'string') { | ||
const str = value; | ||
value = new Date(value); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are two problems that I know of with date strings: 1) Optional parts of date strings
(source) Omitting the time or the time zone part in the input is problematic because by calling the constructor, a time and time zone are implied and the user can format them:
The time zone is especially tricky due to:
Here's an interesting case for you that can be run in the test suite of this repo (uses const formatter = createFormatter({locale: 'en'});
expect(
formatter.dateTime('2020-11-20', {
dateStyle: 'medium',
timeZone: 'America/New_York'
})
).toBe('Nov 20, 2020'); // ❌ Result: Nov 19, 2020 Fun, right? The reason is that the UTC time zone is assumed and the explicitly provided time zone moves the created date the the previous date. 2) Using non-standard date stringsThe date constructor also accepts non-standard date strings with varying browser support. Maybe due to this mess Don't get me wrong, I absolutely see your point and am trying to optimize for convenience with I think to move forward with this, we'd have to validate that the incoming date string conforms to ISO 8601 and includes all parts (year, month, date, hour, minute, seconds, milliseconds, timezone). That however will increase the bundle size slightly. I'm honestly not confident about this change currently. Especially I think as an immediate todo, the I'm not saying this will never make it in, but I'd say we should leave the PR open for a bit, give it more thought and maybe consider opinions from other users in case others chime in. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand point 1 but I don't see how that is any different to what exists now, if i create a date with About point 2, yes, there is not much to argue there, js Dates are a mess, and there could be possibles bugs as everywhere, but this doesn't change anything for users use other parsing mechanisms, only adds an easier way for users that do use the I still understand your concerns and appreciate your feedback! I'm just think it would be super useful to directly accept strings There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That's true. To me, the difference is that this is in userland and hopefully caught in review (or by a linter). If the user calls The possibility I see here is validating at runtime that a full ISO 8601 string is passed. I'm unsure if dev-only error handling is a good idea here, and I'd like to avoid increasing the bundle size due to this. We currently don't have this issue if the user calls I think for the time being I'll wait with this and instead improve the docs on parsing dates. I hope you can understand. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A closely related note from the recently released (https://blog.date-fns.org/v3-is-out/) Example: Might be worth keeping an eye on this, maybe it works out well for For reference, here's a blog post on a 2.0 prerelease for This is the implementation of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another related update: Tempo was just released and they support receiving dates as strings. They include runtime validation that will throw in case an invalid date string is encountered. I've asked the library author for some background on this decision. Maybe in the end we should really support this pattern. I'd still consider doing the runtime validation only during development to avoid increasing the bundle size. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI, I just saw that We should be sure that we have good test coverage on which strings will print the warning. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On a second thought, maybe we don't need to get too elaborative with docs and explain all the problems with dates in JavaScript. Maybe it'd be sufficient to not add the troubleshooting section and use this for the error message:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi @amannn sorry for leaving this a little abandoned, I haven't really had time to get my hands on this again. I'm not sure when I'll be able to, so anyone feel free to take over if they have time, if not I'll finish this, I just don't know when. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll look into this! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the implementation is good now. One tradeoff I noticed: Dates in messages are required to be actual dates, strings will lead to a formatting error. So there's some slight divergence there. Potentially with #705 we'd have more control over this in the future and could enable string dates in messages too. Need to decide between:
|
||
if (isNaN(value.getTime())) { | ||
onError( | ||
new IntlError( | ||
IntlErrorCode.INVALID_FORMAT, | ||
process.env.NODE_ENV !== 'production' | ||
? `The \`value\` string parameter does not follow a valid ISO 8601 format. For more information check: https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-date-time-string-format` | ||
: undefined | ||
) | ||
); | ||
return str; | ||
} | ||
} | ||
|
||
return new Intl.DateTimeFormat(locale, options).format(value); | ||
} | ||
); | ||
|
@@ -219,7 +235,7 @@ export default function createFormatter({ | |
const value = calculateRelativeTimeValue(seconds, unit); | ||
|
||
return new Intl.RelativeTimeFormat(locale, { | ||
numeric: 'auto' | ||
numeric: 'auto', | ||
}).format(value, unit); | ||
} catch (error) { | ||
onError( | ||
|
@@ -238,5 +254,5 @@ export default function createFormatter({ | |
); | ||
} | ||
|
||
return {dateTime, number, relativeTime, list}; | ||
return { dateTime, number, relativeTime, list }; | ||
} |
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 think the existing
FORMATTING_ERROR
would do for this and avoids increasing the bundle size more than necessary.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 thought so at first but then I thought FORMATTING_ERROR was a formatting error but not an error because of the format while parsing. but if it still makes sense to you I'll revert it