Skip to content

Commit

Permalink
fix parsing of dates in receipts with milliseconds (#367)
Browse files Browse the repository at this point in the history
* fixed parsing of apple receipts when dates have millisecond resolution

* escaped the dot so that it works correctly with international formats

* learned to spell milliseconds
  • Loading branch information
aboedo committed Oct 8, 2020
1 parent d2d6627 commit 87af289
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 13 deletions.
Expand Up @@ -8,16 +8,17 @@ import Foundation
struct ISO3601DateFormatter {
static let shared = ISO3601DateFormatter()

private let dateFormatter = DateFormatter()
private let secondsDateFormatter = DateFormatter()
private let milisecondsDateFormatter = DateFormatter()

private init() {
dateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"
secondsDateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"
milisecondsDateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSSZ"
}

func date(fromBytes bytes: ArraySlice<UInt8>) -> Date? {
if let dateString = String(bytes: Array(bytes), encoding: .ascii) {
return dateFormatter.date(from: dateString)
}
return nil
guard let dateString = String(bytes: Array(bytes), encoding: .ascii) else { return nil }
return (secondsDateFormatter.date(from: dateString)
?? milisecondsDateFormatter.date(from: dateString))
}
}
Expand Up @@ -192,7 +192,7 @@ private extension AppleReceiptBuilderTests {

func creationDateContainer() -> ASN1Container {
containerFactory.receiptAttributeContainer(attributeType: ReceiptAttributeType.creationDate,
creationDate)
creationDate)
}

func sha1HashContainer() -> ASN1Container {
Expand All @@ -204,18 +204,17 @@ private extension AppleReceiptBuilderTests {
}

func originalAppVersionContainer() -> ASN1Container {
containerFactory
.receiptAttributeContainer(attributeType: ReceiptAttributeType.originalApplicationVersion,
originalApplicationVersion)
containerFactory.receiptAttributeContainer(attributeType: ReceiptAttributeType.originalApplicationVersion,
originalApplicationVersion)
}

func appVersionContainer() -> ASN1Container {
containerFactory.receiptAttributeContainer(attributeType: ReceiptAttributeType.applicationVersion,
applicationVersion)
applicationVersion)
}

func bundleIdContainer() -> ASN1Container {
containerFactory.receiptAttributeContainer(attributeType: ReceiptAttributeType.bundleId,
bundleId)
bundleId)
}
}
Expand Up @@ -15,10 +15,26 @@ class ISO3601DateFormatterTests: XCTestCase {
minute: 36,
second: 40)
let date = Calendar.current.date(from: dateComponents)
guard let dateBytes = "2020-07-14T19:36:40+0000".data(using: .ascii) else { fatalError() }
guard let dateBytes = "2020-07-14T19:36:40Z".data(using: .ascii) else { fatalError() }
expect(ISO3601DateFormatter.shared.date(fromBytes: ArraySlice(dateBytes))) == date
}

func testDateWithMillisecondsFromBytesReturnsCorrectValueIfPossible() {
let timeZone = TimeZone(identifier: "UTC")
let dateComponents = DateComponents(timeZone: timeZone,
year: 2020,
month: 7,
day: 14,
hour: 19,
minute: 36,
second: 40,
nanosecond: 202_000_000)
let date = Calendar.current.date(from: dateComponents)
guard let dateBytes = "2020-07-14T19:36:40.202Z".data(using: .ascii) else { fatalError() }
let receivedDate = ISO3601DateFormatter.shared.date(fromBytes: ArraySlice(dateBytes))
expect(receivedDate!.timeIntervalSince1970).to(beCloseTo(date!.timeIntervalSince1970))
}

func testDateFromBytesReturnsNilIfItCantBeParsedAsString() {
expect(ISO3601DateFormatter.shared.date(fromBytes: ArraySlice([0b11]))).to(beNil())
}
Expand Down

0 comments on commit 87af289

Please sign in to comment.