10.6 C
London
Thursday, February 29, 2024

Working with dates and Codable in Swift – Donny Wals


Whenever you’re decoding JSON, you’ll run into conditions the place you’ll must decode dates each on occasion. Mostly you’ll most likely be coping with dates that conform to the ISO-8601 normal however there’s additionally a great probability that you simply’ll must cope with completely different date codecs.

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

This publish 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 matter too.

Exploring the default JSON en- and decoding conduct

Once 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 symbolize the variety of seconds which have handed since January 1st 2001 which is a fairly non-standard technique to format a timestamp. The commonest technique to arrange a timestamp could be to make use of the variety of seconds handed since January 1st 1970.

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

Normally 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 might be fairly ambiguous as a result of the timestamp doesn’t say by which timezone we must be including the given timestamp to January 1st 2001. Completely different components of the world have a unique second the place January 1st 2001 begins so it’s not a steady date to match in opposition to.

After all, we now have some finest practices round this like most servers will use UTC as their timezone which implies that timestamps which might be returned by these servers ought to all the time be utilized utilizing the UTC timezone whatever the consumer’s timezone.

Once we obtain a JSON file just like the one proven beneath, the default conduct for our JSONDecoder might be to simply decode the offered timestamps utilizing the gadget’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
    }
]
""".knowledge(utilizing: .utf8)!

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

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

This is likely to be superb in some circumstances however as a rule you’ll need to use one thing that’s extra standardized, and extra express about which timezone the date is in.

Earlier than we take a look at what I feel is probably the most smart answer I need to present you how one can configure your JSON Decoder to make use of a extra normal timestamp reference date which is January 1st 1970.

Setting a date decoding technique

If you wish to change how a JSONEncoder or JSONDecoder offers together with your date, it’s best to just be sure you set its date decoding technique. You are able to do this by assigning an applicable technique to the article’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 need to change the date decoding technique so it decodes dates primarily based on timestamps in seconds since January 1st 1970, we will try this as follows:

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

Some servers work with timestamps in milliseconds since 1970. You possibly can accommodate for that by utilizing the .millisecondsSince1970 configuration as an alternative of .secondsSince1970 and the system will deal with the remainder.

Whereas this lets you use a standardized timestamp format, you’re nonetheless going to run into timezone associated points. To work round that, we’d like to try dates that use the ISO-8601 normal.

Working with dates that conform to ISO-8601

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

We will symbolize something from only a yr 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 symbolize 9am on Feb fifteenth 2024 in New York (UTC-5 in February).

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

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

This technique accommodates details about the date, the time, and timezone. This permits a consumer in New York to translate the offered time to a neighborhood time which on this case implies that the time could be proven to a consumer as 9am as an alternative of 5pm.

We will 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 take 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"
    }
]
""".knowledge(utilizing: .utf8)!

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

do {
  let decoder = JSONDecoder()
  decoder.dateDecodingStrategy = .iso8601
  let todos = strive 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 an alternative of timestamps.

The ToDoItem mannequin is totally unchanged.

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

In some circumstances, 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

Typically, a server returns a date that technically conforms to the ISO-8601 normal but Swift doesn’t decode your dates accurately. On this case, it’d make sense to supply 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 = strive decoder.decode([ToDoItem].self, from: jsonData)
  print(todos)
} catch {
  print(error)
}

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

decoder.dateDecodingStrategy = .customized({ decoder in
  let container = strive decoder.singleValueContainer()
  let dateString = strive 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 probably the most helpful instance (you’ll be able to simply use .iso8601 as an alternative) nevertheless it exhibits how it’s best to go about decoding and making a date utilizing customized logic.

In Abstract

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

You realized 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 normal January 1st 1970 reference date.

Subsequent, we checked out how one can use ISO-8601 date strings as that optionally embody timezone info which significantly improves our scenario.

Lastly, you study how one can take extra management over your JSON by utilizing a customized date formatter and even having a closure that lets you carry out far more advanced decoding (or encoding) logic by taking full management over the method.

I hope you loved this publish!

Latest news
Related news

LEAVE A REPLY

Please enter your comment!
Please enter your name here