@@ -28,6 +28,8 @@ const ORIGINAL = Symbol('original');
2828const TZ_RESOLVED = Symbol ( 'timezone' ) ;
2929const TZ_GIVEN = Symbol ( 'timezone-id-given' ) ;
3030const CAL_ID = Symbol ( 'calendar-id' ) ;
31+ const LOCALE = Symbol ( 'locale' ) ;
32+ const OPTIONS = Symbol ( 'options' ) ;
3133
3234const descriptor = ( value ) => {
3335 return {
@@ -41,21 +43,68 @@ const descriptor = (value) => {
4143const IntlDateTimeFormat = globalThis . Intl . DateTimeFormat ;
4244const 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 ( typeof val === 'function' ) {
52+ val = new IntlDateTimeFormat ( obj [ LOCALE ] , val ( obj [ OPTIONS ] ) ) ;
53+ obj [ prop ] = val ;
54+ }
55+ return val ;
56+ }
57+ // Similarly, lazy-init TimeZone instances.
58+ function getResolvedTimeZoneLazy ( obj ) {
59+ let val = obj [ TZ_RESOLVED ] ;
60+ if ( typeof val === 'string' ) {
61+ val = new TimeZone ( val ) ;
62+ obj [ TZ_RESOLVED ] = val ;
63+ }
64+ return val ;
65+ }
66+
67+ export function DateTimeFormat ( locale = undefined , options = undefined ) {
4568 if ( ! ( this instanceof DateTimeFormat ) ) return new DateTimeFormat ( locale , options ) ;
69+ const hasOptions = typeof options !== 'undefined' ;
70+ options = hasOptions ? ObjectAssign ( { } , options ) : { } ;
71+ const original = new IntlDateTimeFormat ( locale , options ) ;
72+ const ro = original . resolvedOptions ( ) ;
73+
74+ // DateTimeFormat instances are very expensive to create. Therefore, they will
75+ // be lazily created only when needed, using the locale and options provided.
76+ // But it's possible for callers to mutate those inputs before lazy creation
77+ // happens. For this reason, we clone the inputs instead of caching the
78+ // original objects. To avoid the complexity of deep cloning any inputs that
79+ // are themselves objects (e.g. the locales array, or options property values
80+ // that will be coerced to strings), we rely on `resolvedOptions()` to do the
81+ // coercion and cloning for us. Unfortunately, we can't just use the resolved
82+ // options as-is because our options-amending logic adds additional fields if
83+ // the user doesn't supply any unit fields like year, month, day, hour, etc.
84+ // Therefore, we limit the properties in the clone to properties that were
85+ // present in the original input.
86+ if ( hasOptions ) {
87+ const clonedResolved = ObjectAssign ( { } , ro ) ;
88+ for ( const prop in clonedResolved ) {
89+ if ( ! ES . HasOwnProperty ( options , prop ) ) delete clonedResolved [ prop ] ;
90+ }
91+ this [ OPTIONS ] = clonedResolved ;
92+ } else {
93+ this [ OPTIONS ] = options ;
94+ }
4695
4796 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 ) ) ;
97+ this [ LOCALE ] = ro . locale ;
98+ this [ ORIGINAL ] = original ;
99+ this [ TZ_RESOLVED ] = ro . timeZone ;
100+ this [ CAL_ID ] = ro . calendar ;
101+ this [ DATE ] = dateAmend ;
102+ this [ YM ] = yearMonthAmend ;
103+ this [ MD ] = monthDayAmend ;
104+ this [ TIME ] = timeAmend ;
105+ this [ DATETIME ] = datetimeAmend ;
106+ this [ ZONED ] = zonedDateTimeAmend ;
107+ this [ INST ] = instantAmend ;
59108}
60109
61110DateTimeFormat . supportedLocalesOf = function ( ...args ) {
@@ -85,6 +134,7 @@ function resolvedOptions() {
85134function adjustFormatterTimeZone ( formatter , timeZone ) {
86135 if ( ! timeZone ) return formatter ;
87136 const options = formatter . resolvedOptions ( ) ;
137+ if ( options . timeZone === timeZone ) return formatter ;
88138 return new IntlDateTimeFormat ( options . locale , { ...options , timeZone } ) ;
89139}
90140
@@ -327,8 +377,8 @@ function extractOverrides(temporalObj, main) {
327377 const nanosecond = GetSlot ( temporalObj , ISO_NANOSECOND ) ;
328378 const datetime = new DateTime ( 1970 , 1 , 1 , hour , minute , second , millisecond , microsecond , nanosecond , main [ CAL_ID ] ) ;
329379 return {
330- instant : ES . BuiltinTimeZoneGetInstantFor ( main [ TZ_RESOLVED ] , datetime , 'compatible' ) ,
331- formatter : main [ TIME ]
380+ instant : ES . BuiltinTimeZoneGetInstantFor ( getResolvedTimeZoneLazy ( main ) , datetime , 'compatible' ) ,
381+ formatter : getPropLazy ( main , TIME )
332382 } ;
333383 }
334384
@@ -344,8 +394,8 @@ function extractOverrides(temporalObj, main) {
344394 }
345395 const datetime = new DateTime ( isoYear , isoMonth , referenceISODay , 12 , 0 , 0 , 0 , 0 , 0 , calendar ) ;
346396 return {
347- instant : ES . BuiltinTimeZoneGetInstantFor ( main [ TZ_RESOLVED ] , datetime , 'compatible' ) ,
348- formatter : main [ YM ]
397+ instant : ES . BuiltinTimeZoneGetInstantFor ( getResolvedTimeZoneLazy ( main ) , datetime , 'compatible' ) ,
398+ formatter : getPropLazy ( main , YM )
349399 } ;
350400 }
351401
@@ -361,8 +411,8 @@ function extractOverrides(temporalObj, main) {
361411 }
362412 const datetime = new DateTime ( referenceISOYear , isoMonth , isoDay , 12 , 0 , 0 , 0 , 0 , 0 , calendar ) ;
363413 return {
364- instant : ES . BuiltinTimeZoneGetInstantFor ( main [ TZ_RESOLVED ] , datetime , 'compatible' ) ,
365- formatter : main [ MD ]
414+ instant : ES . BuiltinTimeZoneGetInstantFor ( getResolvedTimeZoneLazy ( main ) , datetime , 'compatible' ) ,
415+ formatter : getPropLazy ( main , MD )
366416 } ;
367417 }
368418
@@ -376,8 +426,8 @@ function extractOverrides(temporalObj, main) {
376426 }
377427 const datetime = new DateTime ( isoYear , isoMonth , isoDay , 12 , 0 , 0 , 0 , 0 , 0 , main [ CAL_ID ] ) ;
378428 return {
379- instant : ES . BuiltinTimeZoneGetInstantFor ( main [ TZ_RESOLVED ] , datetime , 'compatible' ) ,
380- formatter : main [ DATE ]
429+ instant : ES . BuiltinTimeZoneGetInstantFor ( getResolvedTimeZoneLazy ( main ) , datetime , 'compatible' ) ,
430+ formatter : getPropLazy ( main , DATE )
381431 } ;
382432 }
383433
@@ -413,8 +463,8 @@ function extractOverrides(temporalObj, main) {
413463 ) ;
414464 }
415465 return {
416- instant : ES . BuiltinTimeZoneGetInstantFor ( main [ TZ_RESOLVED ] , datetime , 'compatible' ) ,
417- formatter : main [ DATETIME ]
466+ instant : ES . BuiltinTimeZoneGetInstantFor ( getResolvedTimeZoneLazy ( main ) , datetime , 'compatible' ) ,
467+ formatter : getPropLazy ( main , DATETIME )
418468 } ;
419469 }
420470
@@ -434,15 +484,15 @@ function extractOverrides(temporalObj, main) {
434484
435485 return {
436486 instant : GetSlot ( temporalObj , INSTANT ) ,
437- formatter : main [ ZONED ] ,
487+ formatter : getPropLazy ( main , ZONED ) ,
438488 timeZone : objTimeZone
439489 } ;
440490 }
441491
442492 if ( ES . IsTemporalInstant ( temporalObj ) ) {
443493 return {
444494 instant : temporalObj ,
445- formatter : main [ INST ]
495+ formatter : getPropLazy ( main , INST )
446496 } ;
447497 }
448498
0 commit comments