Skip to content

Commit 983c099

Browse files
milsemanMichael Ilseman
authored andcommitted
Fully disentangle diagnostics from iterating
1 parent 7b1c2f1 commit 983c099

File tree

4 files changed

+208
-96
lines changed

4 files changed

+208
-96
lines changed

Sources/FoundationEssentials/Formatting/Date+HTTPFormatStyle.swift

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)