@@ -343,20 +343,25 @@ extension DateComponents {
343343 // https://www.rfc-editor.org/rfc/rfc9110.html#http.date
344344 // <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
345345
346+ // Produce an error message to throw
347+ func error( _ extendedDescription: String ? = nil ) -> CocoaError {
348+ parseError ( view, exampleFormattedString: Date . HTTPFormatStyle ( ) . format ( Date . now) , extendedDescription: extendedDescription)
349+ }
350+
346351 var it = view. makeIterator ( )
347352 var dc = DateComponents ( )
348353
349354 // Despite the spec, we allow the weekday name to be optional.
350355 guard let maybeWeekday1 = it. peek ( ) else {
351- throw parseError ( view , exampleFormattedString : Date . HTTPFormatStyle ( ) . format ( Date . now ) )
356+ throw error ( )
352357 }
353358
354359 if isASCIIDigit ( maybeWeekday1) {
355360 // This is the first digit of the day. Weekday is not present.
356361 } else {
357362 // Anything else must be a day-name (Mon, Tue, ... Sun)
358363 guard let weekday1 = it. next ( ) , let weekday2 = it. next ( ) , let weekday3 = it. next ( ) else {
359- throw parseError ( view , exampleFormattedString : Date . HTTPFormatStyle ( ) . format ( Date . now ) )
364+ throw error ( )
360365 }
361366
362367 dc. weekday = switch ( weekday1, weekday2, weekday3) {
@@ -375,20 +380,30 @@ extension DateComponents {
375380 case ( UInt8 ( ascii: " S " ) , UInt8 ( ascii: " a " ) , UInt8 ( ascii: " t " ) ) :
376381 7
377382 default :
378- throw parseError ( view , exampleFormattedString : Date . HTTPFormatStyle ( ) . format ( Date . now ) , extendedDescription : " Malformed weekday name " )
383+ throw error ( " Malformed weekday name " )
379384 }
380385
381386 // Move past , and space to weekday
382- try it. expectCharacter ( UInt8 ( ascii: " , " ) , input: view, onFailure: Date . HTTPFormatStyle ( ) . format ( Date . now) , extendedDescription: " Missing , after weekday " )
383- try it. expectCharacter ( UInt8 ( ascii: " " ) , input: view, onFailure: Date . HTTPFormatStyle ( ) . format ( Date . now) , extendedDescription: " Missing space after weekday " )
387+ guard it. matchByte ( UInt8 ( ascii: " , " ) ) else {
388+ throw error ( " Missing , after weekday " )
389+ }
390+ guard it. matchByte ( UInt8 ( ascii: " " ) ) else {
391+ throw error ( " Missing space after weekday " )
392+ }
384393 }
385394
386- dc. day = try it. digits ( minDigits: 2 , maxDigits: 2 , input: view, onFailure: Date . HTTPFormatStyle ( ) . format ( Date . now) , extendedDescription: " Missing or malformed day " )
387- try it. expectCharacter ( UInt8 ( ascii: " " ) , input: view, onFailure: Date . HTTPFormatStyle ( ) . format ( Date . now) )
395+ guard let day = it. parseNumber ( minDigits: 2 , maxDigits: 2 ) else {
396+ throw error ( " Missing or malformed day " )
397+ }
398+ dc. day = day
399+
400+ guard it. matchByte ( UInt8 ( ascii: " " ) ) else {
401+ throw error ( )
402+ }
388403
389404 // month-name (Jan, Feb, ... Dec)
390405 guard let month1 = it. next ( ) , let month2 = it. next ( ) , let month3 = it. next ( ) else {
391- throw parseError ( view , exampleFormattedString : Date . HTTPFormatStyle ( ) . format ( Date . now ) , extendedDescription : " Missing month " )
406+ throw error ( " Missing month " )
392407 }
393408
394409 dc. month = switch ( month1, month2, month3) {
@@ -417,45 +432,68 @@ extension DateComponents {
417432 case ( UInt8 ( ascii: " D " ) , UInt8 ( ascii: " e " ) , UInt8 ( ascii: " c " ) ) :
418433 12
419434 default :
420- throw parseError ( view, exampleFormattedString: Date . HTTPFormatStyle ( ) . format ( Date . now) , extendedDescription: " Month \( String ( describing: dc. month) ) is out of bounds " )
435+ throw error ( " Month \( String ( describing: dc. month) ) is out of bounds " )
436+ }
437+
438+ guard it. matchByte ( UInt8 ( ascii: " " ) ) else {
439+ throw error ( )
421440 }
422441
423- try it. expectCharacter ( UInt8 ( ascii: " " ) , input: view, onFailure: Date . HTTPFormatStyle ( ) . format ( Date . now) )
442+ guard let year = it. parseNumber ( minDigits: 4 , maxDigits: 4 ) else {
443+ throw error ( )
444+ }
445+ dc. year = year
424446
425- dc. year = try it. digits ( minDigits: 4 , maxDigits: 4 , input: view, onFailure: Date . HTTPFormatStyle ( ) . format ( Date . now) )
426- try it. expectCharacter ( UInt8 ( ascii: " " ) , input: view, onFailure: Date . HTTPFormatStyle ( ) . format ( Date . now) )
447+ guard it. matchByte ( UInt8 ( ascii: " " ) ) else {
448+ throw error ( )
449+ }
427450
428- let hour = try it. digits ( minDigits: 2 , maxDigits: 2 , input: view, onFailure: Date . HTTPFormatStyle ( ) . format ( Date . now) )
451+ guard let hour = it. parseNumber ( minDigits: 2 , maxDigits: 2 ) else {
452+ throw error ( )
453+ }
429454 if hour < 0 || hour > 23 {
430- throw parseError ( view , exampleFormattedString : Date . HTTPFormatStyle ( ) . format ( Date . now ) , extendedDescription : " Hour \( hour) is out of bounds " )
455+ throw error ( " Hour \( hour) is out of bounds " )
431456 }
432457 dc. hour = hour
433458
434- try it. expectCharacter ( UInt8 ( ascii: " : " ) , input: view, onFailure: Date . HTTPFormatStyle ( ) . format ( Date . now) )
435- let minute = try it. digits ( minDigits: 2 , maxDigits: 2 , input: view, onFailure: Date . HTTPFormatStyle ( ) . format ( Date . now) )
459+ guard it. matchByte ( UInt8 ( ascii: " : " ) ) else {
460+ throw error ( )
461+ }
462+ guard let minute = it. parseNumber ( minDigits: 2 , maxDigits: 2 ) else {
463+ throw error ( )
464+ }
436465 if minute < 0 || minute > 59 {
437- throw parseError ( view , exampleFormattedString : Date . HTTPFormatStyle ( ) . format ( Date . now ) , extendedDescription : " Minute \( minute) is out of bounds " )
466+ throw error ( " Minute \( minute) is out of bounds " )
438467 }
439468 dc. minute = minute
440469
441- try it. expectCharacter ( UInt8 ( ascii: " : " ) , input: view, onFailure: Date . HTTPFormatStyle ( ) . format ( Date . now) )
442- let second = try it. digits ( minDigits: 2 , maxDigits: 2 , input: view, onFailure: Date . HTTPFormatStyle ( ) . format ( Date . now) )
470+ guard it. matchByte ( UInt8 ( ascii: " : " ) ) else {
471+ throw error ( )
472+ }
473+ guard let second = it. parseNumber ( minDigits: 2 , maxDigits: 2 ) else {
474+ throw error ( )
475+ }
443476 // second '60' is supported in the spec for leap seconds, but Foundation does not support leap seconds. 60 is adjusted to 59.
444477 if second < 0 || second > 60 {
445- throw parseError ( view , exampleFormattedString : Date . HTTPFormatStyle ( ) . format ( Date . now ) , extendedDescription : " Second \( second) is out of bounds " )
478+ throw error ( " Second \( second) is out of bounds " )
446479 }
447480 // Foundation does not support leap seconds. We convert 60 seconds into 59 seconds.
448481 if second == 60 {
449482 dc. second = 59
450483 } else {
451484 dc. second = second
452485 }
453- try it. expectCharacter ( UInt8 ( ascii: " " ) , input: view, onFailure: Date . HTTPFormatStyle ( ) . format ( Date . now) )
486+ guard it. matchByte ( UInt8 ( ascii: " " ) ) else {
487+ throw error ( )
488+ }
454489
455490 // "GMT"
456- try it. expectCharacter ( UInt8 ( ascii: " G " ) , input: view, onFailure: Date . HTTPFormatStyle ( ) . format ( Date . now) , extendedDescription: " Missing GMT time zone " )
457- try it. expectCharacter ( UInt8 ( ascii: " M " ) , input: view, onFailure: Date . HTTPFormatStyle ( ) . format ( Date . now) , extendedDescription: " Missing GMT time zone " )
458- try it. expectCharacter ( UInt8 ( ascii: " T " ) , input: view, onFailure: Date . HTTPFormatStyle ( ) . format ( Date . now) , extendedDescription: " Missing GMT time zone " )
491+ guard it. matchByte ( UInt8 ( ascii: " G " ) ) ,
492+ it. matchByte ( UInt8 ( ascii: " M " ) ) ,
493+ it. matchByte ( UInt8 ( ascii: " T " ) )
494+ else {
495+ throw error ( " Missing GMT time zone " )
496+ }
459497
460498 // Time zone is always GMT, calendar is always Gregorian
461499 dc. timeZone = . gmt
0 commit comments