@@ -37,9 +37,7 @@ export function dateToUtcString(date: Date): string {
37
37
return `${ DAYS [ dayOfWeek ] } , ${ dayOfMonthString } ${ MONTHS [ month ] } ${ year } ${ hoursString } :${ minutesString } :${ secondsString } GMT` ;
38
38
}
39
39
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 ] $ / ) ;
43
41
44
42
/**
45
43
* 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 => {
62
60
throw new TypeError ( "RFC-3339 date-times must be expressed as strings" ) ;
63
61
}
64
62
const match = RFC3339 . exec ( value ) ;
65
- if ( ! match || ! match . groups ) {
63
+ if ( ! match ) {
66
64
throw new TypeError ( "Invalid RFC-3339 date-time value" ) ;
67
65
}
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 ) ;
71
66
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 } ) ;
73
74
} ;
74
75
75
76
const 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 $ /
77
78
) ;
78
79
const 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 $ /
80
81
) ;
81
82
const 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 } ) $ /
83
84
) ;
84
85
85
86
/**
@@ -102,37 +103,45 @@ export const parseRfc7231DateTime = (value: unknown): Date | undefined => {
102
103
throw new TypeError ( "RFC-7231 date-times must be expressed as strings" ) ;
103
104
}
104
105
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
+ }
107
116
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
+ }
112
132
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
+ ) ;
130
142
}
131
143
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" ) ;
136
145
} ;
137
146
138
147
/**
@@ -164,6 +173,13 @@ export const parseEpochTimestamp = (value: unknown): Date | undefined => {
164
173
return new Date ( Math . round ( valueAsDouble * 1000 ) ) ;
165
174
} ;
166
175
176
+ interface RawTime {
177
+ hours : string ;
178
+ minutes : string ;
179
+ seconds : string ;
180
+ fractionalMilliseconds : string | undefined ;
181
+ }
182
+
167
183
/**
168
184
* Build a date from a numeric year, month, date, and an match with named groups
169
185
* "H", "m", s", and "frac", representing hours, minutes, seconds, and optional fractional seconds.
@@ -172,7 +188,7 @@ export const parseEpochTimestamp = (value: unknown): Date | undefined => {
172
188
* @param day numeric year
173
189
* @param match match with groups "H", "m", s", and "frac"
174
190
*/
175
- const buildDate = ( year : number , month : number , day : number , match : RegExpMatchArray ) : Date => {
191
+ const buildDate = ( year : number , month : number , day : number , time : RawTime ) : Date => {
176
192
const adjustedMonth = month - 1 ; // JavaScript, and our internal data structures, expect 0-indexed months
177
193
validateDayOfMonth ( year , adjustedMonth , day ) ;
178
194
// Adjust month down by 1
@@ -181,11 +197,11 @@ const buildDate = (year: number, month: number, day: number, match: RegExpMatchA
181
197
year ,
182
198
adjustedMonth ,
183
199
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 ) ,
186
202
// 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 )
189
205
)
190
206
) ;
191
207
} ;
0 commit comments