mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-19 19:57:22 +08:00
fix(datetime-button): render correct text when passing partial date values (#27816)
Issue number: resolves #27797 --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> Datetime Button passes a parsed value to one of the many text formatting utilities we have, such as `getMonthAndYear`. However, developers can pass partial date values such as `2022` or `2022-04` (April 2022). According to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format, these are still valid date strings. However, the `parseDate` utility does not add fallback values. So passing `2022` will cause the `day` and `month` fields to be `undefined`. This means that `getNormalizedDate` passes `'//2022'` to the `Date` constructor. Some browsers, such as Chrome, will automatically account for the stray slashes and still return a valid date. Other browsers, such as Safari, do not do this and will either return "Invalid Date" or throw an error. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - Date normalizing utility now has fallback values so we always pass in a valid date. In the example above, `getNormalizedDate` will now pass `'1/1/2022'` instead of `'//2022'` to the `Date` constructor. - Refactored other utils that use `new Date` to make use of `getNormalizedDate` since they are also impacted. Note: I added an E2E test instead of a spec test because I want to test cross-browser behavior to ensure consistency. ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. -->
This commit is contained in:
@ -122,6 +122,42 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
|
||||
await expect(dateTarget).toContainText('May 10, 2023');
|
||||
});
|
||||
test('should set only month and year when only passing month and year', async ({ page }, testInfo) => {
|
||||
testInfo.annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/27797',
|
||||
});
|
||||
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-datetime-button locale="en-US" datetime="datetime"></ion-datetime-button>
|
||||
<ion-datetime id="datetime" value="2022-01" presentation="month-year"></ion-datetime>
|
||||
`,
|
||||
config
|
||||
);
|
||||
await page.waitForSelector('.datetime-ready');
|
||||
|
||||
await expect(page.locator('#date-button')).toContainText('January 2022');
|
||||
await expect(page.locator('#time-button')).toBeHidden();
|
||||
});
|
||||
test('should set only year when passing only year', async ({ page }, testInfo) => {
|
||||
testInfo.annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/27797',
|
||||
});
|
||||
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-datetime-button locale="en-US" datetime="datetime"></ion-datetime-button>
|
||||
<ion-datetime id="datetime" value="2022" presentation="year"></ion-datetime>
|
||||
`,
|
||||
config
|
||||
);
|
||||
await page.waitForSelector('.datetime-ready');
|
||||
|
||||
await expect(page.locator('#date-button')).toContainText('2022');
|
||||
await expect(page.locator('#time-button')).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe(title('datetime-button: locale'), () => {
|
||||
|
@ -113,7 +113,7 @@ export const generateDayAriaLabel = (locale: string, today: boolean, refParts: D
|
||||
/**
|
||||
* MM/DD/YYYY will return midnight in the user's timezone.
|
||||
*/
|
||||
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
|
||||
const date = getNormalizedDate(refParts);
|
||||
|
||||
const labelString = new Intl.DateTimeFormat(locale, {
|
||||
weekday: 'long',
|
||||
@ -134,7 +134,7 @@ export const generateDayAriaLabel = (locale: string, today: boolean, refParts: D
|
||||
* Used for the header in MD mode.
|
||||
*/
|
||||
export const getMonthAndDay = (locale: string, refParts: DatetimeParts) => {
|
||||
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
|
||||
const date = getNormalizedDate(refParts);
|
||||
return new Intl.DateTimeFormat(locale, { weekday: 'short', month: 'short', day: 'numeric', timeZone: 'UTC' }).format(
|
||||
date
|
||||
);
|
||||
@ -147,7 +147,7 @@ export const getMonthAndDay = (locale: string, refParts: DatetimeParts) => {
|
||||
* Example: May 2021
|
||||
*/
|
||||
export const getMonthAndYear = (locale: string, refParts: DatetimeParts) => {
|
||||
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
|
||||
const date = getNormalizedDate(refParts);
|
||||
return new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric', timeZone: 'UTC' }).format(date);
|
||||
};
|
||||
|
||||
@ -183,11 +183,25 @@ export const getYear = (locale: string, refParts: DatetimeParts) => {
|
||||
return getLocalizedDateTime(locale, refParts, { year: 'numeric' });
|
||||
};
|
||||
|
||||
const getNormalizedDate = (refParts: DatetimeParts) => {
|
||||
/**
|
||||
* Given reference parts, return a JS Date object
|
||||
* with a normalized time.
|
||||
*/
|
||||
export const getNormalizedDate = (refParts: DatetimeParts) => {
|
||||
const timeString =
|
||||
refParts.hour !== undefined && refParts.minute !== undefined ? ` ${refParts.hour}:${refParts.minute}` : '';
|
||||
|
||||
return new Date(`${refParts.month}/${refParts.day}/${refParts.year}${timeString} GMT+0000`);
|
||||
/**
|
||||
* We use / notation here for the date
|
||||
* so we do not need to do extra work and pad values with zeroes.
|
||||
* Values such as YYYY-MM are still valid, so
|
||||
* we add fallback values so we still get
|
||||
* a valid date otherwise we will pass in a string
|
||||
* like "//2023". Some browsers, such as Chrome, will
|
||||
* account for this and still return a valid date. However,
|
||||
* this is not a consistent behavior across all browsers.
|
||||
*/
|
||||
return new Date(`${refParts.month ?? 1}/${refParts.day ?? 1}/${refParts.year ?? 2023}${timeString} GMT+0000`);
|
||||
};
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user