@@ -37,9 +37,7 @@ export function dateToUtcString(date: Date): string {
3737 return `${ DAYS [ dayOfWeek ] } , ${ dayOfMonthString } ${ MONTHS [ month ] } ${ year } ${ hoursString } :${ minutesString } :${ secondsString } GMT` ;
3838}
3939
40- const RFC3339 = new RegExp (
41- / ^ (?< Y > \d { 4 } ) - (?< M > \d { 2 } ) - (?< D > \d { 2 } ) [ t T ] (?< H > \d { 2 } ) : (?< m > \d { 2 } ) : (?< s > \d { 2 } ) (?: \. (?< frac > \d + ) ) ? [ z Z ] $ /
42- ) ;
40+ const RFC3339 = new RegExp ( / ^ ( \d { 4 } ) - ( \d { 2 } ) - ( \d { 2 } ) [ t T ] ( \d { 2 } ) : ( \d { 2 } ) : ( \d { 2 } ) (?: \. ( \d + ) ) ? [ z Z ] $ / ) ;
4341
4442/**
4543 * Parses a value into a Date. Returns undefined if the input is null or
@@ -62,24 +60,27 @@ export const parseRfc3339DateTime = (value: unknown): Date | undefined => {
6260 throw new TypeError ( "RFC-3339 date-times must be expressed as strings" ) ;
6361 }
6462 const match = RFC3339 . exec ( value ) ;
65- if ( ! match || ! match . groups ) {
63+ if ( ! match ) {
6664 throw new TypeError ( "Invalid RFC-3339 date-time value" ) ;
6765 }
68- const year = strictParseShort ( stripLeadingZeroes ( match . groups [ "Y" ] ) ) ! ;
69- const month = parseDateValue ( match . groups [ "M" ] , "month" , 1 , 12 ) ;
70- const day = parseDateValue ( match . groups [ "D" ] , "day" , 1 , 31 ) ;
7166
72- return buildDate ( year , month , day , match ) ;
67+ const [ _ , yearStr , monthStr , dayStr , hours , minutes , seconds , fractionalMilliseconds ] = match ;
68+
69+ const year = strictParseShort ( stripLeadingZeroes ( yearStr ) ) ! ;
70+ const month = parseDateValue ( monthStr , "month" , 1 , 12 ) ;
71+ const day = parseDateValue ( dayStr , "day" , 1 , 31 ) ;
72+
73+ return buildDate ( year , month , day , { hours, minutes, seconds, fractionalMilliseconds } ) ;
7374} ;
7475
7576const IMF_FIXDATE = new RegExp (
76- / ^ (?: M o n | T u e | W e d | T h u | F r i | S a t | S u n ) , (?< D > \d { 2 } ) (?< M > J a n | F e b | M a r | A p r | M a y | J u n | J u l | A u g | S e p | O c t | N o v | D e c ) (?< Y > \d { 4 } ) (?< H > \d { 2 } ) : (?< m > \d { 2 } ) : (?< s > \d { 2 } ) (?: \. (?< frac > \d + ) ) ? G M T $ /
77+ / ^ (?: M o n | T u e | W e d | T h u | F r i | S a t | S u n ) , ( \d { 2 } ) ( J a n | F e b | M a r | A p r | M a y | J u n | J u l | A u g | S e p | O c t | N o v | D e c ) ( \d { 4 } ) ( \d { 2 } ) : ( \d { 2 } ) : ( \d { 2 } ) (?: \. ( \d + ) ) ? G M T $ /
7778) ;
7879const RFC_850_DATE = new RegExp (
79- / ^ (?: M o n d a y | T u e s d a y | W e d n e s d a y | T h u r s d a y | F r i d a y | S a t u r d a y | S u n d a y ) , (?< D > \d { 2 } ) - (?< M > J a n | F e b | M a r | A p r | M a y | J u n | J u l | A u g | S e p | O c t | N o v | D e c ) - (?< Y > \d { 2 } ) (?< H > \d { 2 } ) : (?< m > \d { 2 } ) : (?< s > \d { 2 } ) (?: \. (?< frac > \d + ) ) ? G M T $ /
80+ / ^ (?: M o n d a y | T u e s d a y | W e d n e s d a y | T h u r s d a y | F r i d a y | S a t u r d a y | S u n d a y ) , ( \d { 2 } ) - ( J a n | F e b | M a r | A p r | M a y | J u n | J u l | A u g | S e p | O c t | N o v | D e c ) - ( \d { 2 } ) ( \d { 2 } ) : ( \d { 2 } ) : ( \d { 2 } ) (?: \. ( \d + ) ) ? G M T $ /
8081) ;
8182const ASC_TIME = new RegExp (
82- / ^ (?: M o n | T u e | W e d | T h u | F r i | S a t | S u n ) (?< M > J a n | F e b | M a r | A p r | M a y | J u n | J u l | A u g | S e p | O c t | N o v | D e c ) (?< D > [ 1 - 9 ] | \d { 2 } ) (?< H > \d { 2 } ) : (?< m > \d { 2 } ) : (?< s > \d { 2 } ) (?: \. (?< frac > \d + ) ) ? (?< Y > \d { 4 } ) $ /
83+ / ^ (?: M o n | T u e | W e d | T h u | F r i | S a t | S u n ) ( J a n | F e b | M a r | A p r | M a y | J u n | J u l | A u g | S e p | O c t | N o v | D e c ) ( [ 1 - 9 ] | \d { 2 } ) ( \d { 2 } ) : ( \d { 2 } ) : ( \d { 2 } ) (?: \. ( \d + ) ) ? ( \d { 4 } ) $ /
8384) ;
8485
8586/**
@@ -102,37 +103,45 @@ export const parseRfc7231DateTime = (value: unknown): Date | undefined => {
102103 throw new TypeError ( "RFC-7231 date-times must be expressed as strings" ) ;
103104 }
104105
105- //allow customization of day parsing for asctime days, which can be left-padded with spaces
106- let dayFn : ( value : string ) => number = ( value ) => parseDateValue ( value , "day" , 1 , 31 ) ;
106+ let match = IMF_FIXDATE . exec ( value ) ;
107+ if ( match ) {
108+ const [ _ , dayStr , monthStr , yearStr , hours , minutes , seconds , fractionalMilliseconds ] = match ;
109+ return buildDate (
110+ strictParseShort ( stripLeadingZeroes ( yearStr ) ) ! ,
111+ parseMonthByShortName ( monthStr ) ,
112+ parseDateValue ( dayStr , "day" , 1 , 31 ) ,
113+ { hours, minutes, seconds, fractionalMilliseconds }
114+ ) ;
115+ }
107116
108- //all formats other than RFC 850 use a four-digit year
109- let yearFn : ( value : string ) => number = ( value : string ) => strictParseShort ( stripLeadingZeroes ( value ) ) ! ;
110- //RFC 850 dates need post-processing to adjust year values if they are too far in the future
111- let dateAdjustmentFn : ( value : Date ) => Date = ( value ) => value ;
117+ match = RFC_850_DATE . exec ( value ) ;
118+ if ( match ) {
119+ const [ _ , dayStr , monthStr , yearStr , hours , minutes , seconds , fractionalMilliseconds ] = match ;
120+ // RFC 850 dates use 2-digit years. So we parse the year specifically,
121+ // and then once we've constructed the entire date, we adjust it if the resultant date
122+ // is too far in the future.
123+ return adjustRfc850Year (
124+ buildDate ( parseTwoDigitYear ( yearStr ) , parseMonthByShortName ( monthStr ) , parseDateValue ( dayStr , "day" , 1 , 31 ) , {
125+ hours,
126+ minutes,
127+ seconds,
128+ fractionalMilliseconds,
129+ } )
130+ ) ;
131+ }
112132
113- let match = IMF_FIXDATE . exec ( value ) ;
114- if ( ! match || ! match . groups ) {
115- match = RFC_850_DATE . exec ( value ) ;
116- if ( match && match . groups ) {
117- // RFC 850 dates use 2-digit years. So we parse the year specifically,
118- // and then once we've constructed the entire date, we adjust it if the resultant date
119- // is too far in the future.
120- yearFn = parseTwoDigitYear ;
121- dateAdjustmentFn = adjustRfc850Year ;
122- } else {
123- match = ASC_TIME . exec ( value ) ;
124- if ( match && match . groups ) {
125- dayFn = ( value ) => parseDateValue ( value . trimLeft ( ) , "day" , 1 , 31 ) ;
126- } else {
127- throw new TypeError ( "Invalid RFC-7231 date-time value" ) ;
128- }
129- }
133+ match = ASC_TIME . exec ( value ) ;
134+ if ( match ) {
135+ const [ _ , monthStr , dayStr , hours , minutes , seconds , fractionalMilliseconds , yearStr ] = match ;
136+ return buildDate (
137+ strictParseShort ( stripLeadingZeroes ( yearStr ) ) ! ,
138+ parseMonthByShortName ( monthStr ) ,
139+ parseDateValue ( dayStr . trimLeft ( ) , "day" , 1 , 31 ) ,
140+ { hours, minutes, seconds, fractionalMilliseconds }
141+ ) ;
130142 }
131143
132- const year = yearFn ( match . groups [ "Y" ] ) ;
133- const month = parseMonthByShortName ( match . groups [ "M" ] ) ;
134- const day = dayFn ( match . groups [ "D" ] ) ;
135- return dateAdjustmentFn ( buildDate ( year , month , day , match ) ) ;
144+ throw new TypeError ( "Invalid RFC-7231 date-time value" ) ;
136145} ;
137146
138147/**
@@ -164,6 +173,13 @@ export const parseEpochTimestamp = (value: unknown): Date | undefined => {
164173 return new Date ( Math . round ( valueAsDouble * 1000 ) ) ;
165174} ;
166175
176+ interface RawTime {
177+ hours : string ;
178+ minutes : string ;
179+ seconds : string ;
180+ fractionalMilliseconds : string | undefined ;
181+ }
182+
167183/**
168184 * Build a date from a numeric year, month, date, and an match with named groups
169185 * "H", "m", s", and "frac", representing hours, minutes, seconds, and optional fractional seconds.
@@ -172,7 +188,7 @@ export const parseEpochTimestamp = (value: unknown): Date | undefined => {
172188 * @param day numeric year
173189 * @param match match with groups "H", "m", s", and "frac"
174190 */
175- const buildDate = ( year : number , month : number , day : number , match : RegExpMatchArray ) : Date => {
191+ const buildDate = ( year : number , month : number , day : number , time : RawTime ) : Date => {
176192 const adjustedMonth = month - 1 ; // JavaScript, and our internal data structures, expect 0-indexed months
177193 validateDayOfMonth ( year , adjustedMonth , day ) ;
178194 // Adjust month down by 1
@@ -181,11 +197,11 @@ const buildDate = (year: number, month: number, day: number, match: RegExpMatchA
181197 year ,
182198 adjustedMonth ,
183199 day ,
184- parseDateValue ( match . groups ! [ "H" ] ! , "hour" , 0 , 23 ) ,
185- parseDateValue ( match . groups ! [ "m" ] ! , "minute" , 0 , 59 ) ,
200+ parseDateValue ( time . hours , "hour" , 0 , 23 ) ,
201+ parseDateValue ( time . minutes , "minute" , 0 , 59 ) ,
186202 // seconds can go up to 60 for leap seconds
187- parseDateValue ( match . groups ! [ "s" ] ! , "seconds" , 0 , 60 ) ,
188- parseMilliseconds ( match . groups ! [ "frac" ] )
203+ parseDateValue ( time . seconds , "seconds" , 0 , 60 ) ,
204+ parseMilliseconds ( time . fractionalMilliseconds )
189205 )
190206 ) ;
191207} ;
0 commit comments