How to decode dates with Codable
In this post we will look at strategies you can use to decode dates. Custom decoder isn't needed.
Published: April 4, 2021 Sponsored App StoreSimilarly how JSONDecoder allows us to convert from snake case, we can handle dates via configuration of a single attribute. Namely the dateDecodingStrategy
.
What kind of date is this?
Before we look at the Swift code, there is the most important first step. You need to find you, in what format is date represented in the JSON you want to parse. JSON does not have datatype for dates which means they are strings.
Ideally this should be in the documentation for the API, otherwise you need to take a look at the date values and decide what format that is.
One of the common formats is the ISO 8601, it is not very human-readable but machines can work with it pretty well. You can check this link for detailed examples and explanation.
This is an example of ISO 8601 date: 2021-04-04T18:32:53+0000
. This corresponds to April 4th 2021, 18:32:53 in the universal timezone.
Decoding ISO 8601
If your API returns dates in this format, you can set the property to be a type of Date
and configure JSONDecoder
:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
And you are done.
Decoding seconds & milliseconds
Working with dates represented ether as seconds or milliseconds since 1970 is similarly straightforward. You just need to figure out whether the big number is in seconds or milliseconds 🙂
I am writing this on April 4 and right now it is 1617562177
seconds since 1970 and 1617562150604
milliseconds.
We would configure the decoder like this for seconds:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
And for milliseconds:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .millisecondsSince1970
Decoding non-standard dates
But what if the date is in some non-standard format? That is actually the case for iOS Feeds API, where I left it as default.
It looks like this:
{
"title": "iOS Chat with Carlton Gibson",
"site_name": "Filip Němeček’s Blog",
"created": "2021-04-02T16:02:34.504051Z",
"guid": "https://nemecek.be/chats/carlton-gibson",
"url": "https://iosfeeds.com/read/10153",
"twitter_url": "https://twitter.com/nemecek_f"
},
This is one entry from the articles endpoint.
Although the date in created
field looks like ISO 8601, parsing it with this option unfortunately does not work.
But we can use this to demonstrate approach with custom DateFormatter
. First you create the formatter, and then set its date format:
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
And we can then configure our JSONDecoder
to use this formatter:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormatter)
And this will work correctly now 🚀 This approach is not as robust as the previous ones, but I think it works reasonably well if the date does not use standardized format.
Here is complete Playground if you want to try this:
If you create iOS version, change the import form Cocoa to UIKit.
import Cocoa
struct Article: Decodable {
let title: String
let created: Date
}
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormatter)
guard let data = try? Data(contentsOf: URL(string: "https://iosfeeds.com/api/articles/")!) else {
fatalError()
}
if let decoded = try? decoder.decode([Article].self, from: data) {
print(decoded)
} else {
print("Cannot decode")
}
There is last option which is custom
where you need to provide custom Decoder
which will decode the date. I honestly don't know when this would be useful since you can use DateFormatter
as shown above..
Uses: Xcode 12 & Swift 5.3