-1

I tried to use custom date strategy in json decoding using Codable as describe in https://stackoverflow.com/a/28016692/3477974 . Here is the simplified implementation code:

public extension Formatter {
    static let iso8601: DateFormatter = {
        let formatter = DateFormatter()
        formatter.calendar = Calendar(identifier: .iso8601)
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
        return formatter
    }()
}

public extension Date {
    var iso8601: String {
        return Formatter.iso8601.string(from: self)
    }
}


public extension JSONDecoder.DateDecodingStrategy {
    static let iso8601withFractionalSeconds = custom {
        let container = try $0.singleValueContainer()
        let string = try container.decode(String.self)
        guard let date = Formatter.iso8601.date(from: string) else {
            throw DecodingError.dataCorruptedError(in: container,
                  debugDescription: "Invalid date: " + string)
        }
        return date
    }
}


public extension JSONEncoder.DateEncodingStrategy {
    static let iso8601withFractionalSeconds = custom {
        var container = $1.singleValueContainer()
        try container.encode(Formatter.iso8601.string(from: $0))
    }
}

However, when it comes to unit testing it fails although the error message shows equal value used by description method of date:

    func test_jsonCodable_iso8601() {
        let dates = [Date()] // ["Feb 9, 2020 at 12:55:24 PM"]

        let encoder = JSONEncoder()
        encoder.dateEncodingStrategy = .iso8601withFractionalSeconds
        let data = try! encoder.encode(dates)

        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601withFractionalSeconds
        let decodedDates = try! decoder.decode([Date].self, from: data) // ["Feb 9, 2020 at 12:55:24 PM"]

        XCTAssertEqual(dates, decodedDates)
        // fails with error message: [2020-02-09 12:55:24 +0000]") is not equal to ("[2020-02-09 12:55:24 +0000]"
    }

Do you have any idea what is the issue and how we can resolve it?

By the way, the actual decodable object is the following one where publishedAt is of type date:

public struct NewsItem: Equatable {
    public let id: UUID
    public let author: String
    public let title: String
    public let description: String
    public let urlToImage: URL?
    public let publishedAt: Date
    public let content: String

    public init(id: UUID,
                author: String,
                title: String,
                description: String,
                urlToImage: URL?,
                publishedAt: Date,
                content: String) {

        self.id = id
        self.author = author
        self.title = title
        self.description = description
        self.urlToImage = urlToImage
        self.publishedAt = publishedAt
        self.content = content
    }
}

extension NewsItem: Decodable {}
Soheil Novinfard
  • 1,358
  • 1
  • 16
  • 43
  • 1
    The resolution of a DateFormatter is limited to milliseconds, compare https://stackoverflow.com/q/23684727/1187415, https://stackoverflow.com/q/43123944/1187415. – Martin R Feb 09 '20 at 13:20
  • @SoheilNovinfard One way to work around your issue is to store on your struct the date after converting it to `String` and back to `Date`. Something like `extension Date { var iso8601LossLessStringConvertible: Date { Formatter.iso8601.date(from: Formatter.iso8601.string(from: self))! } }` and then you can use `Date().iso8601LossLessStringConvertible` instead of `Date()` – Leo Dabus Feb 09 '20 at 16:48
  • @LeoDabus Thank you I'll check this out – Soheil Novinfard Feb 09 '20 at 18:50
  • @MartinR Yes these links point out to the problem, but not the solution in this case. For keeping to use Date but be able to have this comparison I was thinking about private Date equal operator overload, but not sure if it works or not. – Soheil Novinfard Feb 09 '20 at 18:52

1 Answers1

0

What you are testing is the date/string conversion so there is no need to involve JSON encoding and decoding into this. Start with a fixed date in string form, convert it to date and back and check the end result vs the fixed date

let input = "2020-02-02T19:10:23.123Z"

let formatter = DateFormatter.iso8601
let date = formatter.date(from: input)
let output = date?.iso8601

print(date != nil && input == output!)

Note that this was written in a playground and not as a unit test

Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52