web-dev-qa-db-fra.com

iso8601 date json decoding using Swift4

Donc, j'ai des dates iso8601 dans mon json qui ressemblent à "2016-06-07T17: 20: 00.000 + 02: 00"

Existe-t-il un moyen d'analyser ces dates iso8601 en utilisant Swift4? Suis-je en train de manquer quelque chose d'évident?

J'ai essayé ce qui suit, mais seule la dateString "2016-06-07T17: 20: 00Z" de jsonShipA est analysable ....

import Foundation

struct Spaceship : Codable {
    var name: String
    var createdAt: Date
}

let jsonShipA = """
{
    "name": "Skyhopper",
    "createdAt": "2016-06-07T17:20:00Z"
}
"""

let jsonShipB = """
{
    "name": "Skyhopper",
    "createdAt": "2016-06-07T17:20:00.000+02:00"
}
"""

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601

let dataA = jsonShipA.data(using: .utf8)!
if let decodedShip = try? decoder.decode(Spaceship.self, from: dataA) {
    print("jsonShipA date = \(decodedShip.createdAt)")
} else {
    print("Failed to decode iso8601 date format from jsonShipA")
}

let dataB = jsonShipB.data(using: .utf8)!
if let decodedShip = try? decoder.decode(Spaceship.self, from: dataB) {
    print("jsonShipA date = \(decodedShip.createdAt)")
} else {
    print("Failed to decode iso8601 date format from jsonShipB")
}

La sortie du terrain de jeu est:

jsonShipA date = 2016-06-07 17:20:00 +0000
Failed to decode iso8601 date format from jsonShipB

L'erreur renvoyée est "Chaîne de date attendue au format ISO8601". Mais à ma connaissance, la date "2016-06-07T17: 20: 00.000 + 02: 00" est une date ISO8601 valide

13
Tycho Pandelaar

Vous pouvez utiliser comme ceci:

enum DateError: String, Error {
    case invalidDate
}

let decoder = JSONDecoder() 

let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)

decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
    let container = try decoder.singleValueContainer()
    let dateStr = try container.decode(String.self)

    formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
    if let date = formatter.date(from: dateStr) {
        return date
    }
    formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssXXXXX"
    if let date = formatter.date(from: dateStr) {
        return date
    }
    throw DateError.invalidDate
})
30
Vini App

Version TL; DR: il analyse uniquement le format withInternetDateTime de l'ISO8601DateFormatter décrit ici . Cela signifie que votre chaîne ne doit pas avoir de millisecondes.

Plus d'informations:

En regardant la source Swift à la ligne 787, le commentaire dit:

/// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).

En regardant cela RFC , cela donne quelques exemples (certes délicats) dans la section 5.8:

1985-04-12T23:20:50.52Z
1996-12-19T16:39:57-08:00
1996-12-20T00:39:57Z
1990-12-31T23:59:60Z
1990-12-31T15:59:60-08:00
1937-01-01T12:00:27.87+00:20

Seuls le deuxième et le troisième exemple sont réellement décodés par Swift, le reste échoue. Il me semble que soit le commentaire est incorrect, soit l'implémentation n'est pas terminée. Quant à l'implémentation réelle, qui est en dehors de la source Swift, elle semble simplement utiliser la classe ISO8601DateFormatter dans Foundation.

Swift unittest est également très limité, voir la ligne 180. Il code simplement une seule date, puis la décode à nouveau. En d'autres termes, la seule chose testée est le format que l'ISO8601DateFormatter génère par défaut, qui est codé en dur avec l'option .withInternetDateTime, décrit ici .

24
Bart van Kuik