1- import { DateAndTime , getDatePart , getDateValue , parseTimeOffset , setFormatter } from './common' ;
1+ import {
2+ DateAndTime , enEras , enMonths , enMonthsShort , enWeekdays , enWeekdaysMin , enWeekdaysShort , getDatePart ,
3+ getDateValue , parseTimeOffset , setFormatter
4+ } from './common' ;
25import { DateTime } from './date-time' ;
36import { abs , floor , mod } from '@tubular/math' ;
47import { ILocale } from './i-locale' ;
@@ -17,6 +20,8 @@ const shortOptValues = { f: 'full', m: 'medium', n: 'narrow', s: 'short', l: 'lo
1720const styleOptValues = { F : 'full' , L : 'long' , M : 'medium' , S : 'short' } ;
1821const patternTokens = / ( { [ A - Z a - z 0 - 9 / _ ] + ?! ? } | V | v | R | r | I [ F L M S x ] [ F L M S ] ? | M M M M ~ ? | M M M ~ ? | M M ~ ? | M o | M ~ ? | Q o | Q | D D D D | D D D | D o | D D ~ ? | D ~ ? | d d d d | d d d | d o | d d | d | E | e | w w | w o | w | W W | W o | W | Y Y Y Y Y Y | y y y y y y | Y Y Y Y ~ ? | y y y y | Y Y | y y | Y ~ ? | y ~ ? | N { 1 , 5 } | n | g g g g | g g | G G G G | G G | A | a | H H | H | h h | h | k k | k | m m | m | s s | s | L T S | L T | L L L L | l l l l | L L L | l l l | L L | l l | L | l | S + | Z Z Z | z z z | Z Z | z z | Z | z | X T | x t | X X | x x | X | x ) / g;
1922const cachedLocales : Record < string , ILocale > = { } ;
23+ const invalidZones = new Set < string > ( ) ;
24+ const warnedZones = new Set < string > ( ) ;
2025
2126let allNumeric : RegExp ;
2227let dateMarkCheck : RegExp ;
@@ -99,8 +104,19 @@ function formatEscape(s: string): string {
99104 return result ;
100105}
101106
107+ const CACHE_LIMIT = 500 ;
108+ const cachedParts = new Map < string , string [ ] > ( ) ;
109+ const cachedPartsStripped = new Map < string , string [ ] > ( ) ;
110+
102111export function decomposeFormatString ( format : string , stripDateMarks = false ) : string [ ] {
103- const parts : ( string | string [ ] ) [ ] = [ ] ;
112+ const cache = ( stripDateMarks ? cachedPartsStripped : cachedParts ) ;
113+ let parts : ( string | string [ ] ) [ ] = cache . get ( format ) ;
114+
115+ if ( parts )
116+ return parts as string [ ] ;
117+ else
118+ parts = [ ] ;
119+
104120 let inLiteral = true ;
105121 let inBraces = false ;
106122 let literal = '' ;
@@ -168,7 +184,14 @@ export function decomposeFormatString(format: string, stripDateMarks = false): s
168184 }
169185 } ) ;
170186
171- return flatten ( parts ) as string [ ] ;
187+ parts = flatten ( parts ) ;
188+
189+ if ( cache . size >= CACHE_LIMIT )
190+ cache . clear ( ) ;
191+
192+ cache . set ( format , parts as string [ ] ) ;
193+
194+ return parts as string [ ] ;
172195}
173196
174197function parseDateTimeFormatMods ( s : string ) : DateTimeFormatOptions {
@@ -252,6 +275,7 @@ export function format(dt: DateTime, fmt: string, localeOverride?: string | stri
252275 const min = wt . min ;
253276 const sec = wt . sec ;
254277 const dayOfWeek = dt . getDayOfWeek ( ) ;
278+ const zoneName = dt . timezone . zoneName ;
255279
256280 for ( let i = 0 ; i < parts . length ; i += 2 ) {
257281 result . push ( parts [ i ] ) ;
@@ -267,8 +291,17 @@ export function format(dt: DateTime, fmt: string, localeOverride?: string | stri
267291 usesDateMarks = true ;
268292 }
269293
270- if ( / ^ [ L l Z z I ] / . test ( field ) && locale . cachedTimezone !== dt . timezone . zoneName || ( hasIntlDateTime && isEqual ( locale . dateTimeFormats , { } ) ) )
271- generatePredefinedFormats ( locale , dt . timezone . zoneName ) ;
294+ if ( ! invalidZones . has ( zoneName ) &&
295+ ( ( / ^ [ L l Z z I ] / . test ( field ) && locale . cachedTimezone !== zoneName ) ||
296+ ( hasIntlDateTime && isEqual ( locale . dateTimeFormats , { } ) ) ) ) {
297+ try {
298+ generatePredefinedFormats ( locale , zoneName ) ;
299+ }
300+ catch ( e ) {
301+ if ( / i n v a l i d t i m e z o n e / i. test ( e . message ) )
302+ invalidZones . add ( zoneName ) ;
303+ }
304+ }
272305
273306 switch ( field ) {
274307 case 'YYYYYY' : // long year, always signed
@@ -500,8 +533,8 @@ export function format(dt: DateTime, fmt: string, localeOverride?: string | stri
500533 break ;
501534
502535 case 'ZZZ' : // As IANA zone name, if possible
503- if ( dt . timezone . zoneName !== 'OS' ) {
504- result . push ( dt . timezone . zoneName ) ;
536+ if ( zoneName !== 'OS' ) {
537+ result . push ( zoneName ) ;
505538 break ;
506539 }
507540 else if ( hasIntlDateTime ) {
@@ -511,7 +544,7 @@ export function format(dt: DateTime, fmt: string, localeOverride?: string | stri
511544
512545 // eslint-disable-next-line no-fallthrough
513546 case 'zzz' : // As long zone name (e.g. "Pacific Daylight Time"), if possible
514- if ( dt . timezone . zoneName === 'TAI' ) {
547+ if ( zoneName === 'TAI' ) {
515548 result . push ( 'Temps Atomique International' ) ;
516549 break ;
517550 }
@@ -523,19 +556,25 @@ export function format(dt: DateTime, fmt: string, localeOverride?: string | stri
523556 // eslint-disable-next-line no-fallthrough
524557 case 'zz' : // As zone acronym (e.g. EST, PDT, AEST), if possible
525558 case 'z' :
526- if ( dt . timezone . zoneName !== 'TAI' && hasIntlDateTime && locale . dateTimeFormats . z instanceof DateTimeFormat ) {
559+ if ( zoneName !== 'TAI' && hasIntlDateTime && locale . dateTimeFormats . z instanceof DateTimeFormat ) {
527560 result . push ( getDatePart ( locale . dateTimeFormats . z , dt . epochMillis , 'timeZoneName' ) ) ;
528561 break ;
529562 }
530- else if ( dt . timezone . zoneName !== 'OS' ) {
531- result . push ( dt . timezone . zoneName ) ;
563+ else if ( invalidZones . has ( zoneName ) ) {
564+ result . push ( dt . timezone . getDisplayName ( dt . epochMillis ) ) ;
532565 break ;
533566 }
567+ else if ( zoneName !== 'OS' ) {
568+ result . push ( zoneName ) ;
569+ break ;
570+ }
571+ else
572+ field = 'Z' ;
534573
535574 // eslint-disable-next-line no-fallthrough
536575 case 'ZZ' : // Zone as UTC offset
537576 case 'Z' :
538- if ( dt . timezone . zoneName === 'TAI' )
577+ if ( zoneName === 'TAI' )
539578 result . push ( Timezone . formatUtcOffset ( dt . wallTime . deltaTai , field === 'ZZ' ) ) ;
540579 else
541580 result . push ( dt . timezone . getFormattedOffset ( dt . epochMillis , field === 'ZZ' ) ) ;
@@ -575,7 +614,7 @@ export function format(dt: DateTime, fmt: string, localeOverride?: string | stri
575614
576615 options . calendar = 'gregory' ;
577616
578- const zone = convertDigitsToAscii ( dt . timezone . zoneName ) ;
617+ const zone = convertDigitsToAscii ( zoneName ) ;
579618 let $ : RegExpExecArray ;
580619
581620 if ( zone === 'TAI' )
@@ -599,7 +638,11 @@ export function format(dt: DateTime, fmt: string, localeOverride?: string | stri
599638 locale . dateTimeFormats [ formatKey ] = intlFormat = newDateTimeFormat ( localeNames , options ) ;
600639 }
601640 catch {
602- console . warn ( 'Timezone "%s" not recognized' , options . timeZone ) ;
641+ if ( ! warnedZones . has ( options . timeZone ) ) {
642+ console . warn ( 'Timezone "%s" not recognized' , options . timeZone ) ;
643+ warnedZones . add ( options . timeZone ) ;
644+ }
645+
603646 delete options . timeZone ;
604647 locale . dateTimeFormats [ formatKey ] = intlFormat = newDateTimeFormat ( localeNames , options ) ;
605648 }
@@ -688,7 +731,24 @@ function quickFormat(localeNames: string | string[], timezone: string, opts: any
688731 options [ key ] = value ;
689732 } ) ;
690733
691- return newDateTimeFormat ( localeNames , options ) ;
734+ try {
735+ return newDateTimeFormat ( localeNames , options ) ;
736+ }
737+ catch ( e ) {
738+ if ( / i n v a l i d t i m e z o n e / i. test ( e . message ) ) {
739+ const aliases = Timezone . getAliasesForZone ( options . timeZone ) ;
740+
741+ aliases . forEach ( zone => {
742+ try {
743+ options . timeZone = zone ;
744+ return newDateTimeFormat ( localeNames , options ) ;
745+ }
746+ catch { }
747+ } ) ;
748+ }
749+
750+ throw e ;
751+ }
692752}
693753
694754// Find the shortest case-insensitive version of each string in the array that doesn't match
@@ -808,15 +868,14 @@ function getLocaleInfo(localeNames: string | string[]): ILocale {
808868 locale . zeroDigit = fmt ( { m : 'd' } ) . format ( 0 ) ;
809869 }
810870 else {
811- locale . eras = [ 'BC' , 'AD' , 'Before Christ' , 'Anno Domini' ] ;
812- locale . months = [ 'January' , 'February' , 'March' , 'April' , 'May' , 'June' ,
813- 'July' , 'August' , 'September' , 'October' , 'November' , 'December' ] ;
871+ locale . eras = enEras ;
872+ locale . months = enMonths ;
814873 locale . monthsMin = shortenItems ( locale . months ) ;
815- locale . monthsShort = [ 'Jan' , 'Feb' , 'Mar' , 'Apr' , 'May' , 'Jun' , 'Jul' , 'Aug' , 'Sep' , 'Oct' , 'Nov' , 'Dec' ] ;
874+ locale . monthsShort = enMonthsShort ;
816875 locale . monthsShortMin = shortenItems ( locale . monthsShort ) ;
817- locale . weekdays = [ 'Sunday' , 'Monday' , 'Tuesday' , 'Wednesday' , 'Thursday' , 'Friday' , 'Saturday' ] ;
818- locale . weekdaysShort = [ 'Sun' , 'Mon' , 'Tue' , 'Wed' , 'Thu' , 'Fri' , 'Sat' ] ;
819- locale . weekdaysMin = [ 'Su' , 'Mo' , 'Tu' , 'We' , 'Th' , 'Fr' , 'Sa' ] ;
876+ locale . weekdays = enWeekdays ;
877+ locale . weekdaysShort = enWeekdaysShort ;
878+ locale . weekdaysMin = enWeekdaysMin ;
820879 locale . zeroDigit = '0' ;
821880 }
822881
0 commit comments