Skip to content

Commit 2fad732

Browse files
julianobrasiljelbourn
authored andcommitted
fix(datepicker): improve native adapter DST handling (#10068)
1 parent ffbb425 commit 2fad732

File tree

1 file changed

+35
-17
lines changed

1 file changed

+35
-17
lines changed

src/lib/core/datetime/native-date-adapter.ts

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ export class NativeDateAdapter extends DateAdapter<Date> {
6767
* Without this `Intl.DateTimeFormat` sometimes chooses the wrong timeZone, which can throw off
6868
* the result. (e.g. in the en-US locale `new Date(1800, 7, 14).toLocaleDateString()`
6969
* will produce `'8/13/1800'`.
70+
*
71+
* TODO(mmalerba): drop this variable. It's not being used in the code right now. We're now
72+
* getting the string representation of a Date object from it's utc representation. We're keeping
73+
* it here for sometime, just for precaution, in case we decide to revert some of these changes
74+
* though.
7075
*/
7176
useUtcForDisplay: boolean = true;
7277

@@ -97,34 +102,35 @@ export class NativeDateAdapter extends DateAdapter<Date> {
97102

98103
getMonthNames(style: 'long' | 'short' | 'narrow'): string[] {
99104
if (SUPPORTS_INTL_API) {
100-
let dtf = new Intl.DateTimeFormat(this.locale, {month: style});
101-
return range(12, i => this._stripDirectionalityCharacters(dtf.format(new Date(2017, i, 1))));
105+
const dtf = new Intl.DateTimeFormat(this.locale, {month: style, timeZone: 'utc'});
106+
return range(12, i =>
107+
this._stripDirectionalityCharacters(this._format(dtf, new Date(2017, i, 1))));
102108
}
103109
return DEFAULT_MONTH_NAMES[style];
104110
}
105111

106112
getDateNames(): string[] {
107113
if (SUPPORTS_INTL_API) {
108-
let dtf = new Intl.DateTimeFormat(this.locale, {day: 'numeric'});
114+
const dtf = new Intl.DateTimeFormat(this.locale, {day: 'numeric', timeZone: 'utc'});
109115
return range(31, i => this._stripDirectionalityCharacters(
110-
dtf.format(new Date(2017, 0, i + 1))));
116+
this._format(dtf, new Date(2017, 0, i + 1))));
111117
}
112118
return DEFAULT_DATE_NAMES;
113119
}
114120

115121
getDayOfWeekNames(style: 'long' | 'short' | 'narrow'): string[] {
116122
if (SUPPORTS_INTL_API) {
117-
let dtf = new Intl.DateTimeFormat(this.locale, {weekday: style});
123+
const dtf = new Intl.DateTimeFormat(this.locale, {weekday: style, timeZone: 'utc'});
118124
return range(7, i => this._stripDirectionalityCharacters(
119-
dtf.format(new Date(2017, 0, i + 1))));
125+
this._format(dtf, new Date(2017, 0, i + 1))));
120126
}
121127
return DEFAULT_DAY_OF_WEEK_NAMES[style];
122128
}
123129

124130
getYearName(date: Date): string {
125131
if (SUPPORTS_INTL_API) {
126-
let dtf = new Intl.DateTimeFormat(this.locale, {year: 'numeric'});
127-
return this._stripDirectionalityCharacters(dtf.format(date));
132+
const dtf = new Intl.DateTimeFormat(this.locale, {year: 'numeric', timeZone: 'utc'});
133+
return this._stripDirectionalityCharacters(this._format(dtf, date));
128134
}
129135
return String(this.getYear(date));
130136
}
@@ -155,7 +161,6 @@ export class NativeDateAdapter extends DateAdapter<Date> {
155161
}
156162

157163
let result = this._createDateWithOverflow(year, month, date);
158-
159164
// Check that the date wasn't above the upper bound for the month, causing the month to overflow
160165
if (result.getMonth() != month) {
161166
throw Error(`Invalid date "${date}" for month with index "${month}".`);
@@ -190,15 +195,10 @@ export class NativeDateAdapter extends DateAdapter<Date> {
190195
date.setFullYear(Math.max(1, Math.min(9999, date.getFullYear())));
191196
}
192197

193-
if (this.useUtcForDisplay) {
194-
date = new Date(Date.UTC(
195-
date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(),
196-
date.getMinutes(), date.getSeconds(), date.getMilliseconds()));
197-
displayFormat = {...displayFormat, timeZone: 'utc'};
198-
}
198+
displayFormat = {...displayFormat, timeZone: 'utc'};
199199

200200
const dtf = new Intl.DateTimeFormat(this.locale, displayFormat);
201-
return this._stripDirectionalityCharacters(dtf.format(date));
201+
return this._stripDirectionalityCharacters(this._format(dtf, date));
202202
}
203203
return this._stripDirectionalityCharacters(date.toDateString());
204204
}
@@ -271,7 +271,7 @@ export class NativeDateAdapter extends DateAdapter<Date> {
271271

272272
/** Creates a date but allows the month and date to overflow. */
273273
private _createDateWithOverflow(year: number, month: number, date: number) {
274-
let result = new Date(year, month, date);
274+
const result = new Date(year, month, date);
275275

276276
// We need to correct for the fact that JS native Date treats years in range [0, 99] as
277277
// abbreviations for 19xx.
@@ -300,4 +300,22 @@ export class NativeDateAdapter extends DateAdapter<Date> {
300300
private _stripDirectionalityCharacters(str: string) {
301301
return str.replace(/[\u200e\u200f]/g, '');
302302
}
303+
304+
/**
305+
* When converting Date object to string, javascript built-in functions may return wrong
306+
* results because it applies its internal DST rules. The DST rules around the world change
307+
* very frequently, and the current valid rule is not always valid in previous years though.
308+
* We work around this problem building a new Date object which has its internal UTC
309+
* representation with the local date and time.
310+
* @param dtf Intl.DateTimeFormat object, containg the desired string format. It must have
311+
* timeZone set to 'utc' to work fine.
312+
* @param date Date from which we want to get the string representation according to dtf
313+
* @returns A Date object with its UTC representation based on the passed in date info
314+
*/
315+
private _format(dtf: Intl.DateTimeFormat, date: Date) {
316+
const d = new Date(Date.UTC(
317+
date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(),
318+
date.getMinutes(), date.getSeconds(), date.getMilliseconds()));
319+
return dtf.format(d);
320+
}
303321
}

0 commit comments

Comments
 (0)