Skip to content

Commit b6d7e1c

Browse files
authored
fix(datetime): month picker no longer gives duplicate months on ios 14 and older (#24792)
resolves #24663
1 parent b0ac7de commit b6d7e1c

File tree

3 files changed

+57
-11
lines changed

3 files changed

+57
-11
lines changed

core/src/components/datetime/test/format.spec.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ describe('generateDayAriaLabel()', () => {
2727

2828
expect(generateDayAriaLabel('en-US', false, reference)).toEqual('Monday, May 31');
2929
});
30+
it('should return Saturday, April 1', () => {
31+
const reference = { month: 4, day: 1, year: 2006 };
32+
33+
expect(generateDayAriaLabel('en-US', false, reference)).toEqual('Saturday, April 1');
34+
});
3035
});
3136

3237
describe('getMonthAndDay()', () => {
@@ -37,6 +42,14 @@ describe('getMonthAndDay()', () => {
3742
it('should return mar, 11 may', () => {
3843
expect(getMonthAndDay('es-ES', { month: 5, day: 11, year: 2021 })).toEqual('mar, 11 may');
3944
});
45+
46+
it('should return Sat, Apr 1', () => {
47+
expect(getMonthAndDay('en-US', { month: 4, day: 1, year: 2006 })).toEqual('Sat, Apr 1');
48+
});
49+
50+
it('should return sáb, 1 abr', () => {
51+
expect(getMonthAndDay('es-ES', { month: 4, day: 1, year: 2006 })).toEqual('sáb, 1 abr');
52+
});
4053
})
4154

4255
describe('getFormattedHour()', () => {
@@ -63,7 +76,15 @@ describe('getMonthAndYear()', () => {
6376
expect(getMonthAndYear('en-US', { month: 5, day: 11, year: 2021 })).toEqual('May 2021');
6477
});
6578

66-
it('should return mar, 11 may', () => {
79+
it('should return mayo de 2021', () => {
6780
expect(getMonthAndYear('es-ES', { month: 5, day: 11, year: 2021 })).toEqual('mayo de 2021');
6881
});
82+
83+
it('should return April 2006', () => {
84+
expect(getMonthAndYear('en-US', { month: 4, day: 1, year: 2006 })).toEqual('April 2006');
85+
});
86+
87+
it('should return abril de 2006', () => {
88+
expect(getMonthAndYear('es-ES', { month: 4, day: 1, year: 2006 })).toEqual('abril de 2006');
89+
});
6990
})

core/src/components/datetime/utils/data.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -282,19 +282,44 @@ export const getPickerMonths = (
282282
}
283283

284284
processedMonths.forEach(processedMonth => {
285-
const date = new Date(`${processedMonth}/1/${year}`);
285+
const date = new Date(`${processedMonth}/1/${year} GMT+0000`);
286286

287-
const monthString = new Intl.DateTimeFormat(locale, { month: 'long' }).format(date);
287+
const monthString = new Intl.DateTimeFormat(locale, { month: 'long', timeZone: 'UTC' }).format(date);
288288
months.push({ text: monthString, value: processedMonth });
289289
});
290290
} else {
291291
const maxMonth = maxParts && maxParts.year === year ? maxParts.month : 12;
292292
const minMonth = minParts && minParts.year === year ? minParts.month : 1;
293293

294294
for (let i = minMonth; i <= maxMonth; i++) {
295-
const date = new Date(`${i}/1/${year}`);
296295

297-
const monthString = new Intl.DateTimeFormat(locale, { month: 'long' }).format(date);
296+
/**
297+
*
298+
* There is a bug on iOS 14 where
299+
* Intl.DateTimeFormat takes into account
300+
* the local timezone offset when formatting dates.
301+
*
302+
* Forcing the timezone to 'UTC' fixes the issue. However,
303+
* we should keep this workaround as it is safer. In the event
304+
* this breaks in another browser, we will not be impacted
305+
* because all dates will be interpreted in UTC.
306+
*
307+
* Example:
308+
* new Intl.DateTimeFormat('en-US', { month: 'long' }).format(new Date('Sat Apr 01 2006 00:00:00 GMT-0400 (EDT)')) // "March"
309+
* new Intl.DateTimeFormat('en-US', { month: 'long', timeZone: 'UTC' }).format(new Date('Sat Apr 01 2006 00:00:00 GMT-0400 (EDT)')) // "April"
310+
*
311+
* In certain timezones, iOS 14 shows the wrong
312+
* date for .toUTCString(). To combat this, we
313+
* force all of the timezones to GMT+0000 (UTC).
314+
*
315+
* Example:
316+
* Time Zone: Central European Standard Time
317+
* new Date('1/1/1992').toUTCString() // "Tue, 31 Dec 1991 23:00:00 GMT"
318+
* new Date('1/1/1992 GMT+0000').toUTCString() // "Wed, 01 Jan 1992 00:00:00 GMT"
319+
*/
320+
const date = new Date(`${i}/1/${year} GMT+0000`);
321+
322+
const monthString = new Intl.DateTimeFormat(locale, { month: 'long', timeZone: 'UTC' }).format(date);
298323
months.push({ text: monthString, value: i });
299324
}
300325
}

core/src/components/datetime/utils/format.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ export const generateDayAriaLabel = (locale: string, today: boolean, refParts: D
5656
/**
5757
* MM/DD/YYYY will return midnight in the user's timezone.
5858
*/
59-
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year}`);
59+
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
6060

61-
const labelString = new Intl.DateTimeFormat(locale, { weekday: 'long', month: 'long', day: 'numeric' }).format(date);
61+
const labelString = new Intl.DateTimeFormat(locale, { weekday: 'long', month: 'long', day: 'numeric', timeZone: 'UTC' }).format(date);
6262

6363
/**
6464
* If date is today, prepend "Today" so screen readers indicate
@@ -72,8 +72,8 @@ export const generateDayAriaLabel = (locale: string, today: boolean, refParts: D
7272
* Used for the header in MD mode.
7373
*/
7474
export const getMonthAndDay = (locale: string, refParts: DatetimeParts) => {
75-
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year}`);
76-
return new Intl.DateTimeFormat(locale, { weekday: 'short', month: 'short', day: 'numeric' }).format(date);
75+
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
76+
return new Intl.DateTimeFormat(locale, { weekday: 'short', month: 'short', day: 'numeric', timeZone: 'UTC' }).format(date);
7777
}
7878

7979
/**
@@ -83,6 +83,6 @@ export const getMonthAndDay = (locale: string, refParts: DatetimeParts) => {
8383
* Example: May 2021
8484
*/
8585
export const getMonthAndYear = (locale: string, refParts: DatetimeParts) => {
86-
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year}`);
87-
return new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric' }).format(date);
86+
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
87+
return new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric', timeZone: 'UTC' }).format(date);
8888
}

0 commit comments

Comments
 (0)