Skip to content

Commit

Permalink
Parse ASN.1 UTCTime according to spec
Browse files Browse the repository at this point in the history
Supports optional seconds and compact formatted time zone offets.

Also removes force unwrap in decoding, superceding and closing #19
  • Loading branch information
RockyYara authored and kdubb committed Feb 1, 2022
1 parent c3f5c13 commit ecb7a18
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 46 deletions.
10 changes: 5 additions & 5 deletions Sources/PotentASN1/ASN1.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public indirect enum ASN1: Value {
case teletexString(String)
case videotexString(String)
case ia5String(String)
case utcTime(Date)
case utcTime(ZonedDate)
case generalizedTime(ZonedDate)
case graphicString(String)
case visibleString(String)
Expand Down Expand Up @@ -170,7 +170,7 @@ public indirect enum ASN1: Value {
case .teletexString(let value): return AnyString(value, kind: .teletex)
case .videotexString(let value): return AnyString(value, kind: .videotex)
case .ia5String(let value): return AnyString(value, kind: .ia5)
case .utcTime(let value): return AnyTime(date: value, timeZone: .utc, kind: .utc)
case .utcTime(let value): return AnyTime(value, kind: .utc)
case .generalizedTime(let value): return AnyTime(value, kind: .generalized)
case .graphicString(let value): return AnyString(value, kind: .graphic)
case .visibleString(let value): return AnyString(value, kind: .visible)
Expand Down Expand Up @@ -312,7 +312,7 @@ public extension ASN1 {
return value
}

var utcTimeValue: Date? {
var utcTimeValue: ZonedDate? {
guard case .utcTime(let value) = absolute else { return nil }
return value
}
Expand Down Expand Up @@ -372,7 +372,7 @@ public extension ASN1 {

var timeValue: (ZonedDate, AnyTime.Kind)? {
switch absolute {
case .utcTime(let date): return (ZonedDate(date: date, timeZone: .utc), .utc)
case .utcTime(let date): return (date, .utc)
case .generalizedTime(let date): return (date, .generalized)
default: return nil
}
Expand Down Expand Up @@ -447,7 +447,7 @@ extension ASN1: Codable {
case .ia5String:
self = .ia5String(try container.decode(String.self))
case .utcTime:
self = .utcTime(try container.decode(Date.self))
self = .utcTime(try container.decode(ZonedDate.self))
case .generalizedTime:
self = .generalizedTime(try container.decode(ZonedDate.self))
case .graphicString:
Expand Down
2 changes: 1 addition & 1 deletion Sources/PotentASN1/ASN1Encoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ extension SchemaState {
func asn1Time(_ zonedDate: ZonedDate) -> ASN1 {
switch requiredKind {
case .generalized: return .generalizedTime(zonedDate)
case .utc: return .utcTime(zonedDate.utcDate)
case .utc: return .utcTime(zonedDate)
}
}

Expand Down
17 changes: 7 additions & 10 deletions Sources/PotentASN1/ASN1Reader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public enum DERReader {
case invalidStringEncoding
case invalidStringCharacters
case nonConstructedCollection
case invalidUTCTime
case invalidGeneralizedTime
}

Expand Down Expand Up @@ -143,7 +144,10 @@ public enum DERReader {

case .utcTime:
let string = try parseString(&itemBuffer, tag: tag, encoding: .ascii)
return .utcTime(utcDateFormatter.date(from: string)!)
guard let zonedDate = utcFormatter.date(from: string) else {
throw Error.invalidUTCTime
}
return .utcTime(zonedDate)

case .generalizedTime:
let string = try parseString(&itemBuffer, tag: tag, encoding: .ascii)
Expand Down Expand Up @@ -320,17 +324,10 @@ private extension UnsafeBufferPointer {
}


private let utcDateFormatter: DateFormatter = {
let fmt = DateFormatter()
fmt.timeZone = TimeZone(abbreviation: "UTC")
fmt.dateFormat = "yyMMddHHmmss'Z'"
return fmt
}()


private extension String {
var hasFractionalSeconds: Bool { contains(".") }
var hasZone: Bool { contains("+") || contains("-") || contains("Z") }
}

private let generalizedFormatter = ISO8601SuffixedDateFormatter(basePattern: "yyyyMMddHHmmss")
private let utcFormatter = SuffixedDateFormatter(basePattern: "yyMMddHHmm", secondsPattern: "ss") { $0.count > 10 }
private let generalizedFormatter = SuffixedDateFormatter.optionalFractionalSeconds(basePattern: "yyyyMMddHHmmss")
36 changes: 27 additions & 9 deletions Sources/PotentASN1/ASN1Writer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,13 +195,14 @@ public class DERWriter {
append(data: ascii)

case .utcTime(let value):
let ascii = utcDateFormatter.string(from: value).data(using: String.Encoding.ascii)!
let formatter = UTCFormatters.for(timeZone: value.timeZone)
let ascii = formatter.string(from: value.date).data(using: .ascii)!
append(tag: .utcTime, length: ascii.count)
append(data: ascii)

case .generalizedTime(let value):
let formatter = GeneralizedFormatters.for(timeZone: value.timeZone)
let ascii = formatter.string(from: value.date).data(using: String.Encoding.ascii)!
let ascii = formatter.string(from: value.date).data(using: .ascii)!
append(tag: .generalizedTime, length: ascii.count)
append(data: ascii)

Expand Down Expand Up @@ -247,14 +248,31 @@ public class DERWriter {

}

private enum UTCFormatters {

private let utcDateFormatter: DateFormatter = {
let fmt = DateFormatter()
fmt.locale = Locale(identifier: "en_US_POSIX")
fmt.timeZone = .utc
fmt.dateFormat = "yyMMddHHmmss'Z'"
return fmt
}()
private static var utcFormatters: [TimeZone: DateFormatter] = [:]
private static let utcFormattersLock = NSLock()

static func `for`(timeZone: TimeZone) -> DateFormatter {
utcFormattersLock.lock()
defer { utcFormattersLock.unlock() }

if let found = utcFormatters[timeZone] {
return found
}

let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = timeZone
formatter.dateFormat = timeZone == .utc ? "yyMMddHHmmss'Z'" : "yyMMddHHmmssZ"

utcFormatters[timeZone] = formatter

return formatter
}

}

private enum GeneralizedFormatters {

Expand Down
2 changes: 1 addition & 1 deletion Sources/PotentCBOR/CBORDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ public struct CBORDecoderTransform: InternalDecoderTransform, InternalValueDeser
}


private let _iso8601Formatter = ISO8601SuffixedDateFormatter(basePattern: "yyyy-MM-dd'T'HH:mm:ss")
private let _iso8601Formatter = SuffixedDateFormatter.optionalFractionalSeconds(basePattern: "yyyy-MM-dd'T'HH:mm:ss")


#if canImport(Combine)
Expand Down
41 changes: 23 additions & 18 deletions Sources/PotentCodables/Dates.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,21 @@

import Foundation

public struct ISO8601SuffixedDateFormatter {
public struct SuffixedDateFormatter {

public static func optionalFractionalSeconds(basePattern: String) -> SuffixedDateFormatter {
SuffixedDateFormatter(basePattern: basePattern, secondsPattern: ".S") { $0.contains(".") }
}

private let noSuffixes: DateFormatter
private let zoneSuffix: DateFormatter
private let fractionalSecondsSuffix: DateFormatter
private let zoneAndFractionalSecondsSuffixes: DateFormatter
private let secondsSuffix: DateFormatter
private let zoneAndSecondsSuffixes: DateFormatter
private let checkHasSeconds: (String) -> Bool

public init(basePattern: String, secondsPattern: String, checkHasSeconds: @escaping (String) -> Bool) {
self.checkHasSeconds = checkHasSeconds

public init(basePattern: String) {
noSuffixes = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
Expand All @@ -36,34 +43,37 @@ public struct ISO8601SuffixedDateFormatter {
return formatter
}()

fractionalSecondsSuffix = {
secondsSuffix = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "\(basePattern).S"
formatter.dateFormat = "\(basePattern)\(secondsPattern)"
return formatter
}()

zoneAndFractionalSecondsSuffixes = {
zoneAndSecondsSuffixes = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "\(basePattern).SXXXX"
formatter.dateFormat = "\(basePattern)\(secondsPattern)XXXX"
return formatter
}()
}

public func date(from string: String) -> ZonedDate? {
let parsedDate: Date?
if string.hasFractionalSeconds, string.hasZone {
parsedDate = zoneAndFractionalSecondsSuffixes.date(from: string)
let zoneStartIndex = string.firstIndex { $0 == "-" || $0 == "+" || $0 == "Z" } ?? string.endIndex
let stringWithoutZone = String(string[string.startIndex ..< zoneStartIndex])
let hasZone = string != stringWithoutZone
if checkHasSeconds(stringWithoutZone) && hasZone {
parsedDate = zoneAndSecondsSuffixes.date(from: string)
}
else if string.hasFractionalSeconds {
parsedDate = fractionalSecondsSuffix.date(from: string)
else if checkHasSeconds(stringWithoutZone) {
parsedDate = secondsSuffix.date(from: string)
}
else if string.hasZone {
else if hasZone {
parsedDate = zoneSuffix.date(from: string)
}
else {
Expand All @@ -77,8 +87,3 @@ public struct ISO8601SuffixedDateFormatter {
}

}

private extension String {
var hasFractionalSeconds: Bool { contains(".") }
var hasZone: Bool { contains("+") || contains("-") || contains("Z") }
}
2 changes: 1 addition & 1 deletion Sources/PotentJSON/JSONDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser
}


private let _iso8601Formatter = ISO8601SuffixedDateFormatter(basePattern: "yyyy-MM-dd'T'HH:mm:ss")
private let _iso8601Formatter = SuffixedDateFormatter.optionalFractionalSeconds(basePattern: "yyyy-MM-dd'T'HH:mm:ss")


#if canImport(Combine)
Expand Down
2 changes: 1 addition & 1 deletion Sources/PotentYAML/YAMLDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser
}


private let _iso8601Formatter = ISO8601SuffixedDateFormatter(basePattern: "yyyy-MM-dd'T'HH:mm:ss")
private let _iso8601Formatter = SuffixedDateFormatter.optionalFractionalSeconds(basePattern: "yyyy-MM-dd'T'HH:mm:ss")


#if canImport(Combine)
Expand Down
58 changes: 58 additions & 0 deletions Tests/ASN1Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,64 @@ class ASN1Tests: XCTestCase {
XCTAssertEqual(offset(23), -39600)
}

func testUTCTimeEncodingDecoding() throws {

struct TestStruct: Codable, Equatable {
let a: AnyTime
let b: AnyTime
}

let TestStructSchema: Schema =
.sequence([
"a": .time(kind: .utc),
"b": .time(kind: .utc),
])

let src = TestStruct(
a: AnyTime(date: Date().truncatedToSecs, timeZone: .utc, kind: .utc),
b: AnyTime(date: Date().truncatedToSecs, timeZone: .timeZone(from: "+1122")!, kind: .utc)
)
let srcData = try ASN1Encoder(schema: TestStructSchema).encode(src)
let dst = try ASN1Decoder(schema: TestStructSchema).decode(TestStruct.self, from: srcData)

XCTAssertEqual(src, dst)
}

func testUTCTimeReadingWriting() throws {

let writer = DERWriter()

try writer.write(ASN1.tagged(23, "2211112233Z".data(using: .ascii)!))
try writer.write(ASN1.tagged(23, "221111223344Z".data(using: .ascii)!))

try writer.write(ASN1.tagged(23, "2211112233+1122".data(using: .ascii)!))
try writer.write(ASN1.tagged(23, "221111223344+1122".data(using: .ascii)!))

try writer.write(ASN1.tagged(23, "2211112233-1122".data(using: .ascii)!))
try writer.write(ASN1.tagged(23, "221111223344-1122".data(using: .ascii)!))

let values = try DERReader.parse(data: writer.data)

func date(_ index: Int) -> TimeInterval { values[index].utcTimeValue!.date.timeIntervalSince1970 }
func tz(_ index: Int) -> TimeZone { values[index].utcTimeValue!.timeZone }
func offset(_ index: Int) -> Int { values[index].utcTimeValue!.timeZone.secondsFromGMT() }

XCTAssertEqual(date(0), 1_668_205_980.0)
XCTAssertEqual(tz(0), .utc)
XCTAssertEqual(date(1), 1_668_206_024.0)
XCTAssertEqual(tz(1), .utc)

XCTAssertEqual(date(2), 1_668_165_060.0)
XCTAssertEqual(offset(2), 40920)
XCTAssertEqual(date(3), 1_668_165_104.0)
XCTAssertEqual(offset(3), 40920)

XCTAssertEqual(date(4), 1_668_246_900.0)
XCTAssertEqual(offset(4), -40920)
XCTAssertEqual(date(5), 1_668_246_944.0)
XCTAssertEqual(offset(5), -40920)
}

}


Expand Down

1 comment on commit ecb7a18

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage for this commit

40.85%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
Sources/PotentASN1
   ASN1.swift27.59%100%37.21%26.51%146, 149–154, 156–184, 189–191, 193–195, 200–231, 255–258, 260–263, 265–268, 270–273, 275–278, 290–293, 295–298, 300–303, 305–308, 310–313, 325–328, 330–333, 335–338, 340–343, 345–348, 350–353, 381–387, 389–395, 399, 407–471, 473–528, 65–67, 73–75
   ASN1Decoder.swift36.93%100%44.23%36.36%100–101, 106, 112–117, 125–129, 133–145, 147–159, 161–173, 175–187, 189–201, 203–215, 217–229, 231–243, 245–257, 259–270, 272–283, 285–296, 303–307, 311–324, 326–337, 339–351, 36, 367–369, 37, 370–374, 377–379, 38, 380–384, 388–392, 396–399, 40, 400–409, 41, 410–419, 42, 420–424, 431, 444, 455, 458, 463, 482–486, 494–495, 505–510, 514–520, 535–545, 551–552, 556, 563–569, 573–583, 587–597, 601–611, 617–618, 627–628, 632–634, 643–644, 653–654, 658, 665–671, 675, 679–703, 708–724, 728, 76, 90–94, 98–99
   ASN1Encoder.swift50%100%41.67%50.88%100–103, 135–143, 159, 170–174, 182–183, 191–198, 204, 217–227, 233–234, 238, 245–251, 255–265, 269–330, 334–369, 37, 370–373, 379, 38, 380–383, 39, 393–395, 398, 41, 416–419, 42, 420, 43, 436, 439, 445–446, 452–453, 462, 486–493, 497–498, 503–509, 514–518, 523–541, 545–562, 566, 84, 87–95, 97–99
   ASN1Reader.swift53.56%100%43.75%54.64%111–113, 116, 122, 125, 131, 134, 137, 140, 143, 148, 155, 160, 163, 166, 169, 172, 175, 178, 181–182, 187–200, 210, 214–216, 225–250, 252–266, 276, 284, 295–300, 310, 328–329, 34–40, 47–52, 74–79, 97–98
   ASN1Serialization.swift66.67%100%66.67%66.67%24–26
   ASN1Writer.swift58.30%100%73.33%57.46%100–103, 106–107, 113–139, 142–155, 173–175, 178–180, 183–185, 188–190, 193–195, 210–212, 215–217, 220–222, 225–227, 23, 230–232, 235–237, 24, 244–245, 25, 261, 287, 52–53, 56–58, 61–66, 98–99
   AnyString.swift4.81%100%4.17%5%101–103, 105–107, 109–111, 113–115, 117–119, 124–126, 128–130, 132–134, 136–138, 140–142, 144–146, 148–150, 152–154, 156–158, 160–162, 49–52, 54–57, 68–71, 73–76, 79–82, 84–87, 92–95, 97–99
   AnyTime.swift100%100%100%100%
   BigInts.swift69.01%100%50%72.13%16–19, 21–24, 26–28, 30, 39, 44–47
   BitString.swift0%100%0%0%100–102, 104–113, 115–117, 124–133, 135–138, 23–26, 28–31, 33–35, 37–48, 50–53, 55–70, 72–74, 76–87, 89–93, 95–99
   ObjectIdentifier.swift0%100%0%0%21–23, 25–32, 34–36, 45–47, 53–56, 58–61, 67–69, 71–76
   Schema.swift13.17%100%6.25%13.91%123–135, 137–144, 146–149, 151–154, 156–166, 168–171, 173–176, 178–181, 183–186, 188–191, 193–196, 198–201, 203–206, 208–211, 213–216, 218–221, 223–226, 228–230, 236–279, 28, 280–289, 29, 290–299, 30, 300–309, 31, 310–319, 32, 320–329, 33, 330–339, 34, 340–349, 35, 350–369, 37, 370–375, 38–39, 41–48
   SchemaSpecified.swift0%100%0%0%22–24
   SchemaState.swift54.55%100%61.76%53.42%120–121, 138, 143, 150–155, 161, 167, 170, 179, 182, 186–187, 191–194, 198–212, 216–220, 224–255, 264–266, 50–56, 86–94
Sources/PotentCBOR
   CBOR.swift54.02%100%52.38%54.40%101–104, 106–109, 154–156, 162–164, 166–168, 174–179, 192–193, 212, 229–231, 233–235, 294–313, 66–69, 71–74, 76–79, 81–84, 91–94, 96–99
   CBORDecoder.swift46.67%100%51.43%46.15%100–105, 108–110, 120, 132, 136–146, 148–158, 168, 172–181, 183–192, 194–203, 205–214, 223, 227–237, 239–249, 257, 261–286, 291–299, 304, 306, 308, 310, 312, 314, 316–322, 325, 328, 332, 341, 345–349, 372–376, 75, 92–97
   CBOREncoder.swift63.44%100%55.17%67.19%70–72, 74–77, 80–81, 85, 90, 92–97
   CBORReader.swift98.89%100%100%98.77%173, 50
   CBORSerialization.swift88.24%100%75%92.31%80
   CBORStream.swift93.55%100%91.67%94%68–70
   CBORWriter.swift93.19%100%90.91%93.67%110–112, 115–117, 265–268
   Half.swift44.23%100%40%45.24%46–48, 57–66, 72–78, 84
Sources/PotentCodables
   AnyCodingKey.swift27.54%100%40%25.42%20–23, 35–42, 45–52, 59–67, 71–81, 89–92
   CodableErrors.swift76.92%100%100%75%33, 36, 39, 42, 45, 50
   Dates.swift99.15%100%100%99.04%83
   KeyStrategy.swift0%100%0%0%100–113, 153–200, 51–99
   NestedDecoders.swift5.65%100%4.55%6.02%101–103, 105–107, 109–111, 121–124, 126, 128–130, 132–134, 136–138, 142, 144–149, 151–153, 155–157, 159–161, 163–165, 167–169, 171–173, 175–177, 179–181, 183–185, 187–189, 191–193, 195–197, 199–201, 203–205, 207–209, 28, 34–36, 38–40, 44, 46–51, 53–55, 57–59, 61–63, 65–67, 69–71, 73–75, 77–79, 81–83, 85–87, 89–91, 93–95, 97–99
   NestedEncoders.swift0%100%0%0%100–102, 104–106, 108–110, 120–123, 125, 127–129, 131–133, 135–137, 141–143, 145–147, 149–151, 153–155, 157–159, 161–163, 165–167, 169–171, 173–175, 177–179, 181–183, 185–187, 189–191, 193–195, 197–199, 201–203, 205–207, 22–26, 28, 30–32, 34–36, 38–40, 44–46, 48–50, 52–54, 56–58, 60–62, 64–66, 68–70, 72–74, 76–78, 80–82, 84–86, 88–90, 92–94, 96–98
   Refs.swift93.24%100%93.75%93.10%189, 337, 407
   Tagged.swift0%100%0%0%100–102, 109–119, 126–137, 141–151, 156–167, 177–180, 185–188, 195–198, 208–211, 216–219, 224–227, 40–50, 59–70, 80–86, 96–99
   TimeZone.swift94.92%100%100%94.44%63–65
   ValueDecoder.swift29.52%100%51.69%27.05%100, 1000–1028, 1032–1039, 1046–1053, 1063–1095, 1097–1129, 1131–1148, 1156–1159, 116, 1160, 1164–1166, 117–119, 1191–1194, 1196–1199, 120, 1206–1209, 121, 1211–1214, 1216–1219, 122, 1221–1224, 1231–1234, 1236–1239, 1246–1249, 1267–1269, 1271–1273, 1279–1281, 1283–1285, 1287–1289, 1291–1293, 1299–1301, 1303–1305, 1311–1313, 1323–1325, 1332, 1360, 1363–1374, 1377, 1385, 139–141, 1416–1418, 142, 186–194, 211–217, 230–241, 268–270, 308–314, 350–352, 394–398, 400–404, 423–432, 434–440, 442–458, 462, 470, 477, 491–497, 501, 512–532, 534–540, 542–544, 546–548, 589–608, 610–638, 640–668, 670–698, 700–728, 730–758, 760–788, 790–818, 820–848, 850–878, 880–908, 910–938, 940–968, 970–998
   ValueEncoder.swift46.29%100%44.16%46.79%100, 1000–1003, 1016–1018, 1040–1042, 1045–1050, 1075–1077, 121–124, 159–162, 197, 199–207, 210–212, 256–258, 280–286, 303–307, 313, 337, 355–357, 361, 450–451, 453, 459–463, 465–469, 477–481, 483–487, 489–493, 495–499, 501–505, 507–511, 513–517, 519–523, 525–529, 537–541, 543–547, 558–577, 579–592, 594–601, 603–605, 641–653, 655–660, 662–667, 678–692, 694–703, 705–707, 735–738, 740–743, 745–748, 755–758, 760–763, 765–768, 770–773, 785–788, 790–793, 809–811, 813–816, 819–821, 823–824, 868–869, 872–873, 917–923, 931–937, 941–946, 948–950, 952–954, 956–962, 965–966, 971–980, 984–999
   ValueTransformer.swift0.60%100%0.58%0.60%1000, 103–105, 110–112, 117–119, 124–126, 131–133, 138–140, 145–147, 152–154, 159–161, 166–168, 173–175, 180–182, 187–189, 194–196, 201–203, 208–210, 217–219, 222–224, 227–229, 232–234, 237–239, 242–244, 247–249, 252–254, 257–259, 262–264, 267–269, 272–274, 277–279, 282–284, 287–289, 292–294, 297–299, 302–304, 307–309, 312–314, 317–319, 322–324, 327–329, 33, 332–334, 337–339, 34, 342–344, 35, 351–353, 356–358, 361–363, 366–368, 371–373, 376–378, 381–383, 386–388, 391–393, 396–398, 40, 401–403, 406–408, 41, 411–413, 42, 437–439, 445–447, 453–455, 461–463, 469, 47, 470–471, 477–479, 48, 485–487, 49, 493–495, 501–503, 509–511, 517–519, 525–527, 533–535, 54, 549, 55, 550–556, 56, 562–569, 575–582, 588–595, 601–608, 61, 614–619, 62, 620–621, 627–629, 63, 630–634, 640–647, 653–660, 666–673, 679, 68, 680–686, 69, 692–699, 70, 705–712, 721–723, 728–730, 735–737, 742–744, 749, 75, 750–751, 756–758, 76, 763–765, 77, 770–772, 777–779, 784–786, 791–793, 798–800, 805–807, 812–814, 819, 82, 820–821, 826–828, 83, 833–835, 84, 840–842, 847–849, 854–856, 861–863, 868–870, 875–877, 882–884, 889, 89, 890–891, 896–898, 90, 903–905, 91, 914–916, 921–923, 928–930, 935–937, 942–944, 949–951, 956–958, 96, 963–965, 97, 970–972, 977–979, 98, 984–986, 991–993, 998–999
   ZonedDate.swift13.89%100%25%12.50%18–24, 35–37, 46–63
Sources/PotentCodables/AnyValue
   AnyValue.swift30.48%100%24.24%31.04%100, 102–107, 109–114, 116–119, 121–124, 126–129, 131–134, 136–139, 141–144, 146–162, 172–174, 188–190, 212–215, 217–242, 245–268, 305, 312–415, 427, 433, 435, 437, 441, 443, 445, 447, 449, 451, 453, 455, 457, 459, 461, 95–99
   AnyValueDecoder.swift0%100%0%0%100–102, 108–111, 117–120, 126–129, 135–138, 144–147, 153–156, 162–165, 171–174, 180–183, 189, 19, 190–192, 198–199, 20, 200–201, 207–209, 21, 210, 216–219, 22, 224–227, 23, 232–235, 24, 247–249, 26–28, 43–45, 51–54, 60–69, 75–84, 90–93, 99
   AnyValueEncoder.swift0%100%0%0%100–101, 106–115, 120–122, 127–129, 134–136, 141–143, 148–150, 155–157, 162–164, 169–171, 176–178, 183–185, 19, 190–192, 197–199, 20, 204–206, 21, 211–213, 22, 225–227, 23–24, 26–28, 42–44, 46–48, 50–52, 57–59, 64–73, 78–80, 85–87, 92–94, 99
Sources/PotentJSON
   Errors.swift0%100%0%0%21–39, 47–65
   JSON.swift44.86%100%35.85%47.37%113, 118–121, 123–126, 128–131, 133–136, 138–141, 143–146, 148–151, 153–156, 167, 170–175, 181, 196, 229–231, 266–268, 270–272, 274–276, 296, 56–60, 62–66, 68–71, 73–76, 78–80, 82–84, 89–92, 95, 99
   JSONDecoder.swift24.91%100%38.24%23.05%115, 119–122, 127, 133–138, 145, 149–156, 158–165, 167–174, 176–183, 185–192, 194–201, 203–210, 212–219, 221–228, 230–237, 239–246, 248–255, 263, 267–275, 277–315, 317–341
   JSONEncoder.swift19.37%100%32.50%16.48%141, 144–146, 148–152, 154–155, 157–179, 181–203, 205–230, 232–243, 245–265, 275–284, 286–295, 300–307
   JSONReader.swift69.19%100%56.76%70.15%116, 136–143, 157–158, 165–173, 182–200, 202–230, 232–236, 238–246, 300–303, 309–310, 317–318, 322, 327–336, 346, 351–356, 379, 390, 403, 416–420, 422, 429, 432, 435, 451, 464–465, 467, 84, 93–95
   JSONSerialization.swift68.48%100%50%70.73%35, 52, 68–70, 76–78, 80–90, 92
   JSONWriter.swift75.53%100%66.67%76.14%100, 109–110, 118–119, 127–128, 138–139, 164–166, 170–171, 176–178, 182–185, 187–190, 192–196, 43, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 99
Sources/PotentYAML
   Errors.swift0%100%0%0%21–39, 47–65
   YAML.swift46.99%100%51.47%45.83%100–101, 103–106, 108–110, 112–114, 116–130, 168, 178–187, 189–198, 200–209, 211–220, 222–225, 227–230, 237–242, 244–249, 255, 273–276, 306–307, 316–329, 38–40, 406, 42–44, 48–49, 74–78, 98–99
   YAMLDecoder.swift26.63%100%37.14%25.35%115, 119–122, 127, 133–138, 146, 150–158, 160–168, 170–178, 180–188, 190–198, 200–208, 210–218, 220–228, 230–238, 240–248, 250–258, 260–268, 276, 280–288, 290–328, 330–354, 365–372, 378–380, 384–388
   YAMLEncoder.swift21.52%100%31.71%19.23%138, 147, 151, 155, 159, 163, 167, 171, 175, 179, 187, 191, 193–215, 217–239, 241–266, 268–279, 281–301, 316–322, 324–330, 335–342
   YAMLReader.swift87.66%100%90.63%87.32%119, 158, 162, 170, 173, 176, 179, 182, 204, 207, 21, 221–225, 229–233, 238, 257, 267, 290, 332, 336–338, 56, 59–60, 98–99
   YAMLSerialization.swift20%100%16.67%20.69%37, 44–46, 51–53, 55–65, 67
   YAMLWriter.swift96.69%100%100%96.43%133, 175, 180, 34–35, 57, 80, 95

Please sign in to comment.