skip to Main Content

I need to parse the following date in a JSON payload and it’s almost in ISO8601 format.

"2020-06-05 14:52:54 UTC"

To conform to ISO8601 it needs to be altered slightly.

"2020-06-05T14:52:54Z"

It’s super annoying because I now have to make a customer date decoding strategy.

static func make() -> JSONDecoder {
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
//        decoder.dateDecodingStrategy = .iso8601
    decoder.dateDecodingStrategy = .custom({ decoder in
        let container = try decoder.singleValueContainer()
        let dateStr = try container.decode(String.self)
        guard let date = formatter.date(from: dateStr) else {
            preconditionFailure("Unexpected date format.")
        }
        return date
    })
    return decoder
}

I don’t have control of the data source. Is there anything I can do to avoid a custom decoding strategy in this case?

3

Answers


  1. Since you seem to just want to use a DateFormatter to parse the date string, use the formatted strategy.

    let formatter = DateFormatter()
    
    formatter.dateFormat = "yyyy-MM-dd HH:mm:ss zzzz" 
    // or 
    // formatter.dateFormat = "yyyy-MM-dd HH:mm:ss 'UTC'"
    // formatter.timeZone = .init(identifier: "UTC")
    
    formatter.locale = Locale(identifier: "en_US_POSIX")
    decoder.dateDecodingStrategy = .formatted(formatter)
    
    Login or Signup to reply.
  2. You could map your input into iso8601, to use an existing decoder:

    func iso8601ify(_ str: String) -> String {
        str.split(separator: " ")
            .prefix(2)
            .joined(separator: "T")
            .appending("Z")
    }
    

    This example, there’s no error handling, of course

    Login or Signup to reply.
  3. An alternative to Sweeper’s answer is to write an extension of DateFormatter

    extension DateFormatter {
        static let almostISO8601Formatter: DateFormatter = {
            let formatter = DateFormatter()
            formatter.calendar = Calendar(identifier: .iso8601)
            formatter.locale = Locale(identifier: "en_US_POSIX")
            formatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
            return formatter
        }()
    }
    

    Then your make() function simply becomes

    static func make() -> JSONDecoder {
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        decoder.dateDecodingStrategy = .formatted(.almostISO8601Formatter)
        return decoder
    }
    

    Consider also to put make() (with a more meaningful name) in an extension of JSONDecoder

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search