Skip to content

Commit 481fffc

Browse files
committed
Optimize toLocaleString + Intl.DateTimeFormat
This commit speeds up toLocaleString and other operations that depend on the polyfilled Intl.DateTimeFormat. The fix was to prevent unnecessary creation of built-in Intl.DateTimeFormat objects, because the constructor of that built-in class is slooooooow. For more details about the underlying issue see: https://bugs.chromium.org/p/v8/issues/detail?id=6528 In local testing, speedup is about 2.5x for ZDT toLocaleString calls.
1 parent d6335df commit 481fffc

File tree

1 file changed

+50
-24
lines changed

1 file changed

+50
-24
lines changed

lib/intl.mjs

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const ORIGINAL = Symbol('original');
2828
const TZ_RESOLVED = Symbol('timezone');
2929
const TZ_GIVEN = Symbol('timezone-id-given');
3030
const CAL_ID = Symbol('calendar-id');
31+
const LOCALE = Symbol('locale');
32+
const OPTIONS = Symbol('options');
3133

3234
const descriptor = (value) => {
3335
return {
@@ -41,21 +43,44 @@ const descriptor = (value) => {
4143
const IntlDateTimeFormat = globalThis.Intl.DateTimeFormat;
4244
const ObjectAssign = Object.assign;
4345

44-
export function DateTimeFormat(locale = IntlDateTimeFormat().resolvedOptions().locale, options = {}) {
46+
// Construction of built-in Intl.DateTimeFormat objects is sloooooow,
47+
// so we'll only create those instances when we need them.
48+
// See https://bugs.chromium.org/p/v8/issues/detail?id=6528
49+
function getPropLazy(obj, prop) {
50+
let val = obj[prop];
51+
if (prop === TZ_RESOLVED) {
52+
// for TimeZone, the initializer is a string, not a function
53+
if (typeof val === 'string') {
54+
val = new TimeZone(val);
55+
obj[prop] = val;
56+
}
57+
} else if (typeof val === 'function') {
58+
val = new IntlDateTimeFormat(obj[LOCALE], val(obj[OPTIONS]));
59+
obj[prop] = val;
60+
}
61+
return val;
62+
}
63+
64+
export function DateTimeFormat(locale, options) {
4565
if (!(this instanceof DateTimeFormat)) return new DateTimeFormat(locale, options);
66+
const original = new IntlDateTimeFormat(locale, options);
67+
const ro = original.resolvedOptions();
68+
options = typeof options === 'undefined' ? {} : ObjectAssign({}, options);
4669

4770
this[TZ_GIVEN] = options.timeZone ? options.timeZone : null;
48-
49-
this[ORIGINAL] = new IntlDateTimeFormat(locale, options);
50-
this[TZ_RESOLVED] = new TimeZone(this.resolvedOptions().timeZone);
51-
this[CAL_ID] = this.resolvedOptions().calendar;
52-
this[DATE] = new IntlDateTimeFormat(locale, dateAmend(options));
53-
this[YM] = new IntlDateTimeFormat(locale, yearMonthAmend(options));
54-
this[MD] = new IntlDateTimeFormat(locale, monthDayAmend(options));
55-
this[TIME] = new IntlDateTimeFormat(locale, timeAmend(options));
56-
this[DATETIME] = new IntlDateTimeFormat(locale, datetimeAmend(options));
57-
this[ZONED] = new IntlDateTimeFormat(locale, zonedDateTimeAmend(options));
58-
this[INST] = new IntlDateTimeFormat(locale, instantAmend(options));
71+
this[OPTIONS] = options;
72+
this[LOCALE] = typeof locale === 'undefined' ? ro.locale : locale;
73+
74+
this[ORIGINAL] = original;
75+
this[TZ_RESOLVED] = ro.timeZone;
76+
this[CAL_ID] = ro.calendar;
77+
this[DATE] = dateAmend;
78+
this[YM] = yearMonthAmend;
79+
this[MD] = monthDayAmend;
80+
this[TIME] = timeAmend;
81+
this[DATETIME] = datetimeAmend;
82+
this[ZONED] = zonedDateTimeAmend;
83+
this[INST] = instantAmend;
5984
}
6085

6186
DateTimeFormat.supportedLocalesOf = function (...args) {
@@ -85,6 +110,7 @@ function resolvedOptions() {
85110
function adjustFormatterTimeZone(formatter, timeZone) {
86111
if (!timeZone) return formatter;
87112
const options = formatter.resolvedOptions();
113+
if (options.timeZone === timeZone) return formatter;
88114
return new IntlDateTimeFormat(options.locale, { ...options, timeZone });
89115
}
90116

@@ -327,8 +353,8 @@ function extractOverrides(temporalObj, main) {
327353
const nanosecond = GetSlot(temporalObj, ISO_NANOSECOND);
328354
const datetime = new DateTime(1970, 1, 1, hour, minute, second, millisecond, microsecond, nanosecond, main[CAL_ID]);
329355
return {
330-
instant: ES.BuiltinTimeZoneGetInstantFor(main[TZ_RESOLVED], datetime, 'compatible'),
331-
formatter: main[TIME]
356+
instant: ES.BuiltinTimeZoneGetInstantFor(getPropLazy(main, TZ_RESOLVED), datetime, 'compatible'),
357+
formatter: getPropLazy(main, TIME)
332358
};
333359
}
334360

@@ -344,8 +370,8 @@ function extractOverrides(temporalObj, main) {
344370
}
345371
const datetime = new DateTime(isoYear, isoMonth, referenceISODay, 12, 0, 0, 0, 0, 0, calendar);
346372
return {
347-
instant: ES.BuiltinTimeZoneGetInstantFor(main[TZ_RESOLVED], datetime, 'compatible'),
348-
formatter: main[YM]
373+
instant: ES.BuiltinTimeZoneGetInstantFor(getPropLazy(main, TZ_RESOLVED), datetime, 'compatible'),
374+
formatter: getPropLazy(main, YM)
349375
};
350376
}
351377

@@ -361,8 +387,8 @@ function extractOverrides(temporalObj, main) {
361387
}
362388
const datetime = new DateTime(referenceISOYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0, calendar);
363389
return {
364-
instant: ES.BuiltinTimeZoneGetInstantFor(main[TZ_RESOLVED], datetime, 'compatible'),
365-
formatter: main[MD]
390+
instant: ES.BuiltinTimeZoneGetInstantFor(getPropLazy(main, TZ_RESOLVED), datetime, 'compatible'),
391+
formatter: getPropLazy(main, MD)
366392
};
367393
}
368394

@@ -376,8 +402,8 @@ function extractOverrides(temporalObj, main) {
376402
}
377403
const datetime = new DateTime(isoYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0, main[CAL_ID]);
378404
return {
379-
instant: ES.BuiltinTimeZoneGetInstantFor(main[TZ_RESOLVED], datetime, 'compatible'),
380-
formatter: main[DATE]
405+
instant: ES.BuiltinTimeZoneGetInstantFor(getPropLazy(main, TZ_RESOLVED), datetime, 'compatible'),
406+
formatter: getPropLazy(main, DATE)
381407
};
382408
}
383409

@@ -413,8 +439,8 @@ function extractOverrides(temporalObj, main) {
413439
);
414440
}
415441
return {
416-
instant: ES.BuiltinTimeZoneGetInstantFor(main[TZ_RESOLVED], datetime, 'compatible'),
417-
formatter: main[DATETIME]
442+
instant: ES.BuiltinTimeZoneGetInstantFor(getPropLazy(main, TZ_RESOLVED), datetime, 'compatible'),
443+
formatter: getPropLazy(main, DATETIME)
418444
};
419445
}
420446

@@ -434,15 +460,15 @@ function extractOverrides(temporalObj, main) {
434460

435461
return {
436462
instant: GetSlot(temporalObj, INSTANT),
437-
formatter: main[ZONED],
463+
formatter: getPropLazy(main, ZONED),
438464
timeZone: objTimeZone
439465
};
440466
}
441467

442468
if (ES.IsTemporalInstant(temporalObj)) {
443469
return {
444470
instant: temporalObj,
445-
formatter: main[INST]
471+
formatter: getPropLazy(main, INST)
446472
};
447473
}
448474

0 commit comments

Comments
 (0)