Working with dates and Codable in Swift – Donny Wals


While you’re decoding JSON, you’ll run into conditions the place you’ll need to decode dates each every so often. Mostly you’ll in all probability be coping with dates that conform to the ISO-8601 commonplace however there’s additionally likelihood that you simply’ll need to take care of completely different date codecs.

On this submit, we’ll check out how one can leverage a few of Swift’s built-in date codecs for en- and decoding information in addition to offering your personal date format. We’ll have a look at a number of the up- and drawbacks of how Swift decodes dates, and the way we will probably work round a number of the downsides.

This submit is a part of a collection I’ve on Swift’s codable so I extremely advocate that you simply check out my different posts on this subject too.

Exploring the default JSON en- and decoding habits

After we don’t do something, a JSONDecoder (and JSONEncoder) will anticipate dates in a JSON file to be formatted as a double. This double ought to signify the variety of seconds which have handed since January 1st 2001 which is a fairly non-standard approach to format a timestamp. The commonest approach to arrange a timestamp could be to make use of the variety of seconds handed since January 1st 1970.

Nevertheless, this technique of speaking about dates isn’t very dependable whenever you take complexities like timezones into consideration.

Often a system will use its personal timezone because the timezone to use the reference date to. So a given variety of seconds since January 1st 2001 may be fairly ambiguous as a result of the timestamp doesn’t say through which timezone we ought to be including the given timestamp to January 1st 2001. Completely different elements of the world have a unique second the place January 1st 2001 begins so it’s not a steady date to match towards.

In fact, we now have some finest practices round this like most servers will use UTC as their timezone which signifies that timestamps which might be returned by these servers ought to at all times be utilized utilizing the UTC timezone whatever the shopper’s timezone.

After we obtain a JSON file just like the one proven beneath, the default habits for our JSONDecoder will probably be to simply decode the supplied timestamps utilizing the system’s present timezone.

var jsonData = """
[
    {
        "title": "Grocery shopping",
        "date": 730976400.0
    },
    {
        "title": "Dentist appointment",
        "date": 731341800.0
    },
    {
        "title": "Finish project report",
        "date": 731721600.0
    },
    {
        "title": "Call plumber",
        "date": 732178800.0
    },
    {
        "title": "Book vacation",
        "date": 732412800.0
    }
]
""".information(utilizing: .utf8)!

struct ToDoItem: Codable {
  let title: String
  let date: Date
}

do {
  let decoder = JSONDecoder()
  let todos = attempt decoder.decode([ToDoItem].self, from: jsonData)
  print(todos)
} catch {
  print(error)
}

This may be high-quality in some instances however most of the time you’ll wish to use one thing that’s extra standardized, and extra specific about which timezone the date is in.

Earlier than we have a look at what I feel is essentially the most wise answer I wish to present you how one can configure your JSON Decoder to make use of a extra commonplace timestamp reference date which is January 1st 1970.

Setting a date decoding technique

If you wish to change how a JSONEncoder or JSONDecoder offers along with your date, you need to just remember to set its date decoding technique. You are able to do this by assigning an applicable technique to the thing’s dateDecodingStrategy property (or dateEncodingStrategy for JSONEncoder. The default technique is named deferredToDate and also you’ve simply seen the way it works.

If we wish to change the date decoding technique so it decodes dates primarily based on timestamps in seconds since January 1st 1970, we will do this as follows:

do {
  let decoder = JSONDecoder()
  decoder.dateDecodingStrategy = .secondsSince1970
  let todos = attempt decoder.decode([ToDoItem].self, from: jsonData)
  print(todos)
} catch {
  print(error)
}

Some servers work with timestamps in milliseconds since 1970. You may accommodate for that through the use of the .millisecondsSince1970 configuration as a substitute of .secondsSince1970 and the system will deal with the remaining.

Whereas this lets you use a standardized timestamp format, you’re nonetheless going to run into timezone associated points. To work round that, we want to check out dates that use the ISO-8601 commonplace.

Working with dates that conform to ISO-8601

As a result of there are numerous methods to signify dates so long as you may have some consistency amongst the techniques the place these dates are used, an ordinary was created to signify dates as strings. This commonplace is named ISO-8601 and it describes a number of conventions round how we will signify dates as strings.

We are able to signify something from only a 12 months or a full date to a date with a time that features details about which timezone that date exists in.

For instance, a date that represents 5pm on Feb fifteenth 2024 in The Netherlands (UTC+1 throughout February) would signify 9am on Feb fifteenth 2024 in New York (UTC-5 in February).

It may be vital for a system to signify a date in a person’s native timezone (for instance whenever you’re publishing a sports activities occasion schedule) in order that the person doesn’t need to do the timezone math for themselves. For that cause, ISO-8601 tells us how we will signify Feb fifteenth 2024 at 5pm in a standardized method. For instance, we might use the next string:

2024-02-15T17:00:00+01:00

This method accommodates details about the date, the time, and timezone. This permits a shopper in New York to translate the supplied time to a neighborhood time which on this case signifies that the time could be proven to a person as 9am as a substitute of 5pm.

We are able to inform our JSONEncoder or JSONDecoder to find which one of many a number of completely different date codecs from ISO-8601 our JSON makes use of, after which decode our fashions utilizing that format.

Let’s have a look at an instance of how we will set this up:

var jsonData = """
[
    {
        "title": "Grocery shopping",
        "date": "2024-03-01T10:00:00+01:00"
    },
    {
        "title": "Dentist appointment",
        "date": "2024-03-05T14:30:00+01:00"
    },
    {
        "title": "Finish project report",
        "date": "2024-03-10T23:59:00+01:00"
    },
    {
        "title": "Call plumber",
        "date": "2024-03-15T08:00:00+01:00"
    },
    {
        "title": "Book vacation",
        "date": "2024-03-20T20:00:00+01:00"
    }
]
""".information(utilizing: .utf8)!

struct ToDoItem: Codable {
  let title: String
  let date: Date
}

do {
  let decoder = JSONDecoder()
  decoder.dateDecodingStrategy = .iso8601
  let todos = attempt decoder.decode([ToDoItem].self, from: jsonData)
  print(todos)
} catch {
  print(error)
}

The JSON within the snippet above is barely modified to make it use ISO-8601 date strings as a substitute of timestamps.

The ToDoItem mannequin is totally unchanged.

The decoder’s dateDecodingStrategy has been modified to .iso8601 which can enable us to not fear concerning the actual date format that’s utilized in our JSON so long as it conforms to .iso8601.

In some instances, you may need to take some extra management over how your dates are decoded. You are able to do this by setting your dateDecodingStrategy to both .customized or .formatted.

Utilizing a customized encoding and decoding technique for dates

Generally, a server returns a date that technically conforms to the ISO-8601 commonplace but Swift doesn’t decode your dates appropriately. On this case, it’d make sense to offer a customized date format that your encoder / decoder can use.

You are able to do this as follows:

do {
  let decoder = JSONDecoder()

  let formatter = DateFormatter()
  formatter.dateFormat = "yyyy-MM-dd"
  formatter.locale = Locale(identifier: "en_US_POSIX")
  formatter.timeZone = TimeZone(secondsFromGMT: 0)

  decoder.dateDecodingStrategy = .formatted(formatter)

  let todos = attempt decoder.decode([ToDoItem].self, from: jsonData)
  print(todos)
} catch {
  print(error)
}

Alternatively, you would possibly must have some extra advanced logic than you may encapsulate in a date formatter. If that’s the case, you may present a closure to the customized configuration on your date decoding technique as follows:

decoder.dateDecodingStrategy = .customized({ decoder in
  let container = attempt decoder.singleValueContainer()
  let dateString = attempt container.decode(String.self)

  if let date = ISO8601DateFormatter().date(from: dateString) {
    return date
  } else {
    throw DecodingError.dataCorruptedError(in: container, debugDescription: "Can not decode date string (dateString)")
  }
})

This instance creates its personal ISO-8601 date formatter so it’s not essentially the most helpful instance (you may simply use .iso8601 as a substitute) but it surely reveals how you need to go about decoding and making a date utilizing customized logic.

In Abstract

On this submit, you noticed a number of methods to work with dates and JSON.

You discovered concerning the default method to decoding dates from a JSON file which requires your dates to be represented as seconds from January 1st 2001. After that, you noticed how one can configure your JSONEncoder or JSONDecoder to make use of the extra commonplace January 1st 1970 reference date.

Subsequent, we checked out use ISO-8601 date strings as that optionally embrace timezone info which vastly improves our scenario.

Lastly, you be taught how one can take extra management over your JSON through the use of a customized date formatter and even having a closure that permits you to carry out rather more advanced decoding (or encoding) logic by taking full management over the method.

I hope you loved this submit!

Recent Articles

Related Stories

Leave A Reply

Please enter your comment!
Please enter your name here

Stay on op - Ge the daily news in your inbox