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.

Similarly 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

Filip Němeček profile photo

WRITTEN BY

Filip Němeček @nemecek_f

iOS blogger and developer with interest in Python/Django. Telling other devs' stories with iOS Chat.