Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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'), () => {
Expand Down
24 changes: 19 additions & 5 deletions core/src/components/datetime/utils/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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
);
Expand All @@ -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);
};

Expand Down Expand Up @@ -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`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we fallback to the current year, instead of hardcoding 2023?

e.g.:

refParts.year ?? new Date().getFullYear()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, though we also default to 1/1/2023 here:

Any preference?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a preference myself. Presumably, if I'm passing in a value without the year, I'm using the month presentation, in which case I don't care about the year since it'll never be displayed.

That said, if we do go with the current year, I would rather use the current date for the month and day as well for consistency's sake.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably, if I'm passing in a value without the year, I'm using the month presentation, in which case I don't care about the year since it'll never be displayed.

Yep exactly this. If any of those values are undefined then they aren't actually used in the rendering of the component -- we just need them to get a valid date

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok with this as-is then 👍

};

/**
Expand Down