@@ -132,53 +132,55 @@ function symbolToFormatPart(symbol: string): FormatPart {
132
132
}
133
133
}
134
134
135
- function formatToFormatParts ( format : string ) {
136
- const formatParts : FormatPart [ ] = [ ] ;
137
- let index = 0 ;
138
- while ( index < format . length ) {
139
- const substring = format . slice ( index ) ;
140
-
141
- const symbolMatch = SYMBOL_REGEXP . exec ( substring ) ;
142
- if ( symbolMatch ) {
143
- const symbol = symbolMatch . groups ! . symbol ! ;
144
- formatParts . push ( symbolToFormatPart ( symbol ) ) ;
145
- index += symbol . length ;
146
- continue ;
147
- }
148
-
149
- const quotedLiteralMatch = QUOTED_LITERAL_REGEXP . exec ( substring ) ;
150
- if ( quotedLiteralMatch ) {
151
- const value = quotedLiteralMatch . groups ! . value ! ;
152
- formatParts . push ( { type : "literal" , value } ) ;
153
- index += quotedLiteralMatch [ 0 ] . length ;
154
- continue ;
155
- }
135
+ export class DateTimeFormatter {
136
+ #formatParts: FormatPart [ ] ;
156
137
157
- const literalMatch = LITERAL_REGEXP . exec ( substring ) ! ;
158
- const value = literalMatch . groups ! . value ! ;
159
- formatParts . push ( { type : "literal" , value } ) ;
160
- index += value . length ;
138
+ constructor ( formatString : string ) {
139
+ this . #formatParts = DateTimeFormatter . formatToFormatParts ( formatString ) ;
161
140
}
162
141
163
- return formatParts ;
164
- }
142
+ static formatToFormatParts ( format : string ) {
143
+ const formatParts : FormatPart [ ] = [ ] ;
144
+ let index = 0 ;
145
+ while ( index < format . length ) {
146
+ const substring = format . slice ( index ) ;
147
+
148
+ const symbolMatch = SYMBOL_REGEXP . exec ( substring ) ;
149
+ if ( symbolMatch ) {
150
+ const symbol = symbolMatch . groups ! . symbol ! ;
151
+ formatParts . push ( symbolToFormatPart ( symbol ) ) ;
152
+ index += symbol . length ;
153
+ continue ;
154
+ }
165
155
166
- export class DateTimeFormatter {
167
- #formatParts: FormatPart [ ] ;
156
+ const quotedLiteralMatch = QUOTED_LITERAL_REGEXP . exec ( substring ) ;
157
+ if ( quotedLiteralMatch ) {
158
+ const value = quotedLiteralMatch . groups ! . value ! ;
159
+ formatParts . push ( { type : "literal" , value } ) ;
160
+ index += quotedLiteralMatch [ 0 ] . length ;
161
+ continue ;
162
+ }
168
163
169
- constructor ( formatString : string ) {
170
- this . #formatParts = formatToFormatParts ( formatString ) ;
164
+ const literalMatch = LITERAL_REGEXP . exec ( substring ) ! ;
165
+ const value = literalMatch . groups ! . value ! ;
166
+ formatParts . push ( { type : "literal" , value } ) ;
167
+ index += value . length ;
168
+ }
169
+
170
+ return formatParts ;
171
171
}
172
172
173
- format ( date : Date , options : Options = { } ) : string {
173
+ static formatPartsToString (
174
+ date : Date ,
175
+ parts : FormatPart [ ] ,
176
+ options : Options = { } ,
177
+ ) : string {
174
178
let string = "" ;
175
179
176
180
const utc = options . timeZone === "UTC" ;
177
181
178
- for ( const part of this . #formatParts) {
179
- const type = part . type ;
180
-
181
- switch ( type ) {
182
+ for ( const part of parts ) {
183
+ switch ( part . type ) {
182
184
case "year" : {
183
185
const value = utc ? date . getUTCFullYear ( ) : date . getFullYear ( ) ;
184
186
switch ( part . value ) {
@@ -292,6 +294,14 @@ export class DateTimeFormatter {
292
294
break ;
293
295
}
294
296
case "fractionalSecond" : {
297
+ if (
298
+ typeof part . value === "string" || part . value < 1 || part . value > 3
299
+ ) {
300
+ throw new Error (
301
+ `FormatterError: DateTimeFormatPartType "fractionalSecond" does not support value ${ part . value } ` ,
302
+ ) ;
303
+ }
304
+
295
305
const value = utc
296
306
? date . getUTCMilliseconds ( )
297
307
: date . getMilliseconds ( ) ;
@@ -305,7 +315,9 @@ export class DateTimeFormatter {
305
315
}
306
316
307
317
// TODO(WWRS): add support for time zones
308
- throw new Error ( `FormatterError: Time zone is not supported` ) ;
318
+ throw new Error (
319
+ `FormatterError: DateTimeFormatPartType "timeZoneName" does not support value ${ part . value } ` ,
320
+ ) ;
309
321
}
310
322
case "dayPeriod" : {
311
323
switch ( part . value ) {
@@ -336,10 +348,13 @@ export class DateTimeFormatter {
336
348
return string ;
337
349
}
338
350
339
- formatToParts ( string : string ) : DateTimeFormatPart [ ] {
340
- const parts : DateTimeFormatPart [ ] = [ ] ;
351
+ static formatPartsToDateTimeParts (
352
+ string : string ,
353
+ formatParts : FormatPart [ ] ,
354
+ ) : DateTimeFormatPart [ ] {
355
+ const dateTimeParts : DateTimeFormatPart [ ] = [ ] ;
341
356
342
- for ( const part of this . # formatParts) {
357
+ for ( const part of formatParts ) {
343
358
let value : string | undefined ;
344
359
// The number of chars consumed in the parse, defaults to `value.length`
345
360
let parsedLength : number | undefined ;
@@ -434,7 +449,7 @@ export class DateTimeFormatter {
434
449
}
435
450
default :
436
451
throw new Error (
437
- `ParserError: DateTimeFormatPartType "hour" does not support value " ${ part . value } " ` ,
452
+ `ParserError: DateTimeFormatPartType "hour" does not support value ${ part . value } ` ,
438
453
) ;
439
454
}
440
455
break ;
@@ -474,6 +489,14 @@ export class DateTimeFormatter {
474
489
break ;
475
490
}
476
491
case "fractionalSecond" : {
492
+ if (
493
+ typeof part . value === "string" || part . value < 1 || part . value > 3
494
+ ) {
495
+ throw new Error (
496
+ `ParserError: DateTimeFormatPartType "fractionalSecond" does not support value ${ part . value } ` ,
497
+ ) ;
498
+ }
499
+
477
500
const re = new RegExp ( `^\\d{${ part . value } }` ) ;
478
501
value = re . exec ( string ) ?. [ 0 ] ;
479
502
break ;
@@ -484,22 +507,49 @@ export class DateTimeFormatter {
484
507
break ;
485
508
}
486
509
case "dayPeriod" : {
487
- value = / ^ [ A P ] (?: \. M \. | M \. ? ) / i. exec ( string ) ?. [ 0 ] ;
488
- parsedLength = value ?. length ;
489
- switch ( value ?. toUpperCase ( ) ) {
490
- case "AM" :
491
- case "AM." :
492
- case "A.M." :
493
- value = "AM" ;
510
+ switch ( part . value ) {
511
+ case "short" :
512
+ case "long" : {
513
+ value = / ^ [ A P ] (?: \. M \. | M \. ? ) / i. exec ( string ) ?. [ 0 ] ;
514
+ parsedLength = value ?. length ;
515
+ switch ( value ?. toUpperCase ( ) ) {
516
+ case "AM" :
517
+ case "AM." :
518
+ case "A.M." :
519
+ value = "AM" ;
520
+ break ;
521
+ case "PM" :
522
+ case "PM." :
523
+ case "P.M." :
524
+ value = "PM" ;
525
+ break ;
526
+ default :
527
+ throw new Error (
528
+ `ParserError: Could not parse dayPeriod from "${ string } "` ,
529
+ ) ;
530
+ }
494
531
break ;
495
- case "PM" :
496
- case "PM." :
497
- case "P.M." :
498
- value = "PM" ;
532
+ }
533
+ case "narrow" : {
534
+ value = / ^ [ A P ] / i. exec ( string ) ?. [ 0 ] ;
535
+ parsedLength = 1 ;
536
+ switch ( value ?. toUpperCase ( ) ) {
537
+ case "A" :
538
+ value = "AM" ;
539
+ break ;
540
+ case "P" :
541
+ value = "PM" ;
542
+ break ;
543
+ default :
544
+ throw new Error (
545
+ `ParserError: Could not parse dayPeriod from "${ string } "` ,
546
+ ) ;
547
+ }
499
548
break ;
549
+ }
500
550
default :
501
551
throw new Error (
502
- `ParserError: Could not parse dayPeriod from " ${ value } " ` ,
552
+ `ParserError: DateTimeFormatPartType "dayPeriod" does not support value ${ part . value } ` ,
503
553
) ;
504
554
}
505
555
break ;
@@ -536,7 +586,7 @@ export class DateTimeFormatter {
536
586
} `,
537
587
) ;
538
588
}
539
- parts . push ( { type : part . type , value } ) ;
589
+ dateTimeParts . push ( { type : part . type , value } ) ;
540
590
541
591
string = string . slice ( parsedLength ?? value . length ) ;
542
592
}
@@ -549,10 +599,10 @@ export class DateTimeFormatter {
549
599
) ;
550
600
}
551
601
552
- return parts ;
602
+ return dateTimeParts ;
553
603
}
554
604
555
- partsToDate ( parts : DateTimeFormatPart [ ] ) : Date {
605
+ static dateTimePartsToDate ( parts : DateTimeFormatPart [ ] ) : Date {
556
606
let year ;
557
607
let month ;
558
608
let day ;
@@ -612,11 +662,6 @@ export class DateTimeFormatter {
612
662
}
613
663
614
664
if ( dayPeriod !== undefined ) {
615
- if ( hour === undefined ) {
616
- throw new Error (
617
- `ParserError: Cannot use dayPeriod without hour` ,
618
- ) ;
619
- }
620
665
if ( hour > 12 ) {
621
666
throw new Error (
622
667
`ParserError: Cannot use dayPeriod with hour greater than 12, hour is "${ hour } "` ,
@@ -647,8 +692,19 @@ export class DateTimeFormatter {
647
692
) ;
648
693
}
649
694
695
+ format ( date : Date , options : Options = { } ) : string {
696
+ return DateTimeFormatter . formatPartsToString (
697
+ date ,
698
+ this . #formatParts,
699
+ options ,
700
+ ) ;
701
+ }
702
+
650
703
parse ( string : string ) : Date {
651
- const parts = this . formatToParts ( string ) ;
652
- return this . partsToDate ( parts ) ;
704
+ const parts = DateTimeFormatter . formatPartsToDateTimeParts (
705
+ string ,
706
+ this . #formatParts,
707
+ ) ;
708
+ return DateTimeFormatter . dateTimePartsToDate ( parts ) ;
653
709
}
654
710
}
0 commit comments