Skip to content

Commit 2837196

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

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
@@ -66,6 +66,11 @@ export class NativeDateAdapter extends DateAdapter<Date> {
6666
* Without this `Intl.DateTimeFormat` sometimes chooses the wrong timeZone, which can throw off
6767
* the result. (e.g. in the en-US locale `new Date(1800, 7, 14).toLocaleDateString()`
6868
* will produce `'8/13/1800'`.
69+
*
70+
* TODO(mmalerba): drop this variable. It's not being used in the code right now. We're now
71+
* getting the string representation of a Date object from it's utc representation. We're keeping
72+
* it here for sometime, just for precaution, in case we decide to revert some of these changes
73+
* though.
6974
*/
7075
useUtcForDisplay: boolean;
7176

@@ -101,34 +106,35 @@ export class NativeDateAdapter extends DateAdapter<Date> {
101106

102107
getMonthNames(style: 'long' | 'short' | 'narrow'): string[] {
103108
if (SUPPORTS_INTL_API) {
104-
let dtf = new Intl.DateTimeFormat(this.locale, {month: style});
105-
return range(12, i => this._stripDirectionalityCharacters(dtf.format(new Date(2017, i, 1))));
109+
const dtf = new Intl.DateTimeFormat(this.locale, {month: style, timeZone: 'utc'});
110+
return range(12, i =>
111+
this._stripDirectionalityCharacters(this._format(dtf, new Date(2017, i, 1))));
106112
}
107113
return DEFAULT_MONTH_NAMES[style];
108114
}
109115

110116
getDateNames(): string[] {
111117
if (SUPPORTS_INTL_API) {
112-
let dtf = new Intl.DateTimeFormat(this.locale, {day: 'numeric'});
118+
const dtf = new Intl.DateTimeFormat(this.locale, {day: 'numeric', timeZone: 'utc'});
113119
return range(31, i => this._stripDirectionalityCharacters(
114-
dtf.format(new Date(2017, 0, i + 1))));
120+
this._format(dtf, new Date(2017, 0, i + 1))));
115121
}
116122
return DEFAULT_DATE_NAMES;
117123
}
118124

119125
getDayOfWeekNames(style: 'long' | 'short' | 'narrow'): string[] {
120126
if (SUPPORTS_INTL_API) {
121-
let dtf = new Intl.DateTimeFormat(this.locale, {weekday: style});
127+
const dtf = new Intl.DateTimeFormat(this.locale, {weekday: style, timeZone: 'utc'});
122128
return range(7, i => this._stripDirectionalityCharacters(
123-
dtf.format(new Date(2017, 0, i + 1))));
129+
this._format(dtf, new Date(2017, 0, i + 1))));
124130
}
125131
return DEFAULT_DAY_OF_WEEK_NAMES[style];
126132
}
127133

128134
getYearName(date: Date): string {
129135
if (SUPPORTS_INTL_API) {
130-
let dtf = new Intl.DateTimeFormat(this.locale, {year: 'numeric'});
131-
return this._stripDirectionalityCharacters(dtf.format(date));
136+
const dtf = new Intl.DateTimeFormat(this.locale, {year: 'numeric', timeZone: 'utc'});
137+
return this._stripDirectionalityCharacters(this._format(dtf, date));
132138
}
133139
return String(this.getYear(date));
134140
}
@@ -159,7 +165,6 @@ export class NativeDateAdapter extends DateAdapter<Date> {
159165
}
160166

161167
let result = this._createDateWithOverflow(year, month, date);
162-
163168
// Check that the date wasn't above the upper bound for the month, causing the month to overflow
164169
if (result.getMonth() != month) {
165170
throw Error(`Invalid date "${date}" for month with index "${month}".`);
@@ -194,15 +199,10 @@ export class NativeDateAdapter extends DateAdapter<Date> {
194199
date.setFullYear(Math.max(1, Math.min(9999, date.getFullYear())));
195200
}
196201

197-
if (this.useUtcForDisplay) {
198-
date = new Date(Date.UTC(
199-
date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(),
200-
date.getMinutes(), date.getSeconds(), date.getMilliseconds()));
201-
displayFormat = {...displayFormat, timeZone: 'utc'};
202-
}
202+
displayFormat = {...displayFormat, timeZone: 'utc'};
203203

204204
const dtf = new Intl.DateTimeFormat(this.locale, displayFormat);
205-
return this._stripDirectionalityCharacters(dtf.format(date));
205+
return this._stripDirectionalityCharacters(this._format(dtf, date));
206206
}
207207
return this._stripDirectionalityCharacters(date.toDateString());
208208
}
@@ -275,7 +275,7 @@ export class NativeDateAdapter extends DateAdapter<Date> {
275275

276276
/** Creates a date but allows the month and date to overflow. */
277277
private _createDateWithOverflow(year: number, month: number, date: number) {
278-
let result = new Date(year, month, date);
278+
const result = new Date(year, month, date);
279279

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

0 commit comments

Comments
 (0)