Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iso8601 date json decoding using swift4 [duplicate]

Tags:

swift

swift4

So, I've got iso8601 dates in my json which look like "2016-06-07T17:20:00.000+02:00"

Is there a way to parse these iso8601 dates using swift4? Am I missing something obvious?

I tried the following, but only the dateString "2016-06-07T17:20:00Z" from jsonShipA is parsable....

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") } 

The output of the playground is:

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

The error being thrown is "Expected date string to be ISO8601-formatted." But to my knowledge, the date "2016-06-07T17:20:00.000+02:00" is a valid ISO8601 date

like image 526
Tycho Pandelaar Avatar asked Oct 03 '17 05:10

Tycho Pandelaar


2 Answers

You can use like this :

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 }) 
like image 64
Vini App Avatar answered Oct 01 '22 02:10

Vini App


TL;DR version: it only parses the withInternetDateTime format of the ISO8601DateFormatter described here. This means that your string should not have milliseconds.

More info:

Looking at the Swift source on line 787, the comment says:

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

Looking at that RFC, it gives a couple of (admittedly tricky) examples in 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 

Only the second and the third example are actually decoded by Swift, the rest fails. It seems to me that either the comment is incorrect, or the implementation is not complete. As for the real implementation, that's outside the Swift source, it simply seems to use the ISO8601DateFormatter class in Foundation.

The Swift unittest is also very limited, see line 180. It simply encodes a single date, and then decodes it back. So in other words, the only thing that's tested, is the format that the ISO8601DateFormatter outputs by default, which is hardcoded to the option .withInternetDateTime, described here.

like image 31
Bart van Kuik Avatar answered Oct 01 '22 03:10

Bart van Kuik