@@ -28,6 +28,8 @@ const ORIGINAL = Symbol('original');
28
28
const TZ_RESOLVED = Symbol ( 'timezone' ) ;
29
29
const TZ_GIVEN = Symbol ( 'timezone-id-given' ) ;
30
30
const CAL_ID = Symbol ( 'calendar-id' ) ;
31
+ const LOCALE = Symbol ( 'locale' ) ;
32
+ const OPTIONS = Symbol ( 'options' ) ;
31
33
32
34
const descriptor = ( value ) => {
33
35
return {
@@ -41,21 +43,68 @@ const descriptor = (value) => {
41
43
const IntlDateTimeFormat = globalThis . Intl . DateTimeFormat ;
42
44
const ObjectAssign = Object . assign ;
43
45
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 ) {
45
68
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
+ }
46
95
47
96
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 ;
59
108
}
60
109
61
110
DateTimeFormat . supportedLocalesOf = function ( ...args ) {
@@ -85,6 +134,7 @@ function resolvedOptions() {
85
134
function adjustFormatterTimeZone ( formatter , timeZone ) {
86
135
if ( ! timeZone ) return formatter ;
87
136
const options = formatter . resolvedOptions ( ) ;
137
+ if ( options . timeZone === timeZone ) return formatter ;
88
138
return new IntlDateTimeFormat ( options . locale , { ...options , timeZone } ) ;
89
139
}
90
140
@@ -327,8 +377,8 @@ function extractOverrides(temporalObj, main) {
327
377
const nanosecond = GetSlot ( temporalObj , ISO_NANOSECOND ) ;
328
378
const datetime = new DateTime ( 1970 , 1 , 1 , hour , minute , second , millisecond , microsecond , nanosecond , main [ CAL_ID ] ) ;
329
379
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 )
332
382
} ;
333
383
}
334
384
@@ -344,8 +394,8 @@ function extractOverrides(temporalObj, main) {
344
394
}
345
395
const datetime = new DateTime ( isoYear , isoMonth , referenceISODay , 12 , 0 , 0 , 0 , 0 , 0 , calendar ) ;
346
396
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 )
349
399
} ;
350
400
}
351
401
@@ -361,8 +411,8 @@ function extractOverrides(temporalObj, main) {
361
411
}
362
412
const datetime = new DateTime ( referenceISOYear , isoMonth , isoDay , 12 , 0 , 0 , 0 , 0 , 0 , calendar ) ;
363
413
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 )
366
416
} ;
367
417
}
368
418
@@ -376,8 +426,8 @@ function extractOverrides(temporalObj, main) {
376
426
}
377
427
const datetime = new DateTime ( isoYear , isoMonth , isoDay , 12 , 0 , 0 , 0 , 0 , 0 , main [ CAL_ID ] ) ;
378
428
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 )
381
431
} ;
382
432
}
383
433
@@ -413,8 +463,8 @@ function extractOverrides(temporalObj, main) {
413
463
) ;
414
464
}
415
465
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 )
418
468
} ;
419
469
}
420
470
@@ -434,15 +484,15 @@ function extractOverrides(temporalObj, main) {
434
484
435
485
return {
436
486
instant : GetSlot ( temporalObj , INSTANT ) ,
437
- formatter : main [ ZONED ] ,
487
+ formatter : getPropLazy ( main , ZONED ) ,
438
488
timeZone : objTimeZone
439
489
} ;
440
490
}
441
491
442
492
if ( ES . IsTemporalInstant ( temporalObj ) ) {
443
493
return {
444
494
instant : temporalObj ,
445
- formatter : main [ INST ]
495
+ formatter : getPropLazy ( main , INST )
446
496
} ;
447
497
}
448
498
0 commit comments