Date decoding strategies in Swift [with Examples]

The Date type is a Codable type in Swift. However, dates are not straightforward to decode, like other types conforming to the Codable protocol, because the default format used by JSONDecoder is not standard in JSON.

Dates can be encoded using different formats in JSON data. To handle all these formats, the JSONDecoder class has a  dateDecodingStrategy property, which can be configured with several decoding strategies according to the date format you must decode.



FREE GUIDE: Parsing JSON in Swift - The Ultimate Cheat Sheet

Learn the fundamentals of JSON parsing in Swift, advanced decoding techniques, and tasks for app development

DOWNLOAD THE FREE GUIDE

Table of contents

Decoding dates in the ISO 8601 format (without fractional seconds)

The most common date format is ISO 8601, which is widely used and standardized.

Dates in the ISO 8601 format usually have the form yyyy-MM-ddTHH:mm:ssZ. For example, noon on October 26th, 2024, in Coordinated Universal Time (UTC) would be represented as 2024-10-26T12:00:00Z.

import Foundation

let jsonData = """
{
	"date": "2024-10-26T12:00:00Z"
}
""".data(using: .utf8)!

struct MyStruct: Codable {
	let date: Date
}

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let decodedData = try decoder.decode(MyStruct.self, from: jsonData)
print(decodedData.date)
// Output: 2024-10-26 12:00:00 +0000

Unfortunately, the .iso8601 decoding strategy does not work with fractional seconds. In that case, you must use the custom(_:) decoding strategy, as shown below.

Decoding UNIX dates expressed in seconds or milliseconds

Another common way to represent dates in computers is using the epoch format. Since most internet servers are UNIX-based, they use UNIX time, which is the number of seconds that passed since midnight on January 1st, 1970 (UTC).

The JSONDecoder class offers two granularities for UNIX time: .secondsSince1970 and .millisecondsSince1970.

import Foundation

let jsonData = """
{
	"date": 1729936800
}
""".data(using: .utf8)!

struct MyStruct: Codable {
	let date: Date
}

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
let decodedData = try decoder.decode(MyStruct.self, from: jsonData)
print(decodedData.date)
// Output: 2024-10-26 12:00:00 +0000

Decoding custom string dates using a DateFormatter

Your JSON data might carry dates in a custom string format not covered by the abovementioned strategies. In that case, you can use the .formatted(_:) strategy, which accepts a DateFormatter instance.

import Foundation

let jsonData = """
{
	"date": "2024-10-26 12:00:00"
}
""".data(using: .utf8)!

struct MyStruct: Codable {
	let date: Date
}

let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
let decodedData = try decoder.decode(MyStruct.self, from: jsonData)
print(decodedData.date)
// Output: 2024-10-26 12:00:00 +0000

Decoding custom date formats with a custom date decoding strategy

When all else fails, the .custom(_:) date decoding strategy allows you to read the raw value from the JSON data and parse it with custom code.

This is useful when you need to decode a date format not supported by other strategies or when your data contains dates in multiple formats.

One example is decoding dates in an ISO 8601 format not supported by the .iso8601 decoding strategy. For that, you can use an instance of the ISO8601DateFormatter class to parse a date with fractional seconds.

import Foundation

let jsonData = """
{
	"date": "2024-10-26T12:34:56.789Z"
}
""".data(using: .utf8)!

struct MyStruct: Codable {
	let date: Date
}

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom({ decoder in
	let container = try decoder.singleValueContainer()
	let dateString = try container.decode(String.self)
	let formatter = ISO8601DateFormatter()
	formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
	return formatter.date(from: dateString)!
})
let decodedData = try decoder.decode(MyStruct.self, from: jsonData)
print(decodedData.date)
// Output: 2024-10-26 12:34:56 +0000

You cannot use the ISO8601DateFormatter class with the .formatted(_:) decoding strategy because it is not a subclass of DateFormatter.

The .deferredToDate strategy is only useful with Apple dates

Even though the .deferredToDate decoding strategy is the default one, you rarely need to use it.

This strategy uses an Apple-specific format used by the Date structure. You use this strategy only when decoding data you encoded using the same approach. This is rarely the case since most JSON data you handle comes from a third-party API.

import Foundation

let jsonData = """
{
	"date": 751636800
}
""".data(using: .utf8)!

struct MyStruct: Codable {
	let date: Date
}

let decoder = JSONDecoder()
let decodedData = try decoder.decode(MyStruct.self, from: jsonData)
print(decodedData.date)
// Output: 2024-10-26 12:00:00 +0000

Decoding JSON data containing multiple date formats

Sometimes, you must deal with JSON data containing dates in multiple formats. In such cases, you must use the .custom(_:) date decoding strategy to handle the various data formats.

If dates are all represented using the same data type, e.g., a String, you can first decode the date value and then try multiple date formatters in sequence until the conversion succeeds.

If dates are represented by different data types, e.g., String and TimeInterval, you need to check the coding key before you use the appropriate Swift type for the decoding.

import Foundation

let jsonData = """
{
	"ISODate": "2024-10-26T12:00:00Z",
	"UNIXDate": 1729936800
}
""".data(using: .utf8)!

struct MyStruct: Codable {
	let ISODate: Date
	let UNIXDate: Date

	enum CodingKeys: CodingKey {
		case ISODate
		case UNIXDate
	}
}

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom({ decoder in
	let container = try decoder.singleValueContainer()
	let codingKey = decoder.codingPath.last!
	if codingKey.stringValue == MyStruct.CodingKeys.ISODate.stringValue {
		let dateString = try container.decode(String.self)
		let formatter = ISO8601DateFormatter()
		formatter.formatOptions = [.withInternetDateTime]
		return formatter.date(from: dateString)!
	} else {
		let UNIXTime = try container.decode(TimeInterval.self)
		return Date(timeIntervalSince1970: UNIXTime)
	}
})

let decodedData = try decoder.decode(MyStruct.self, from: jsonData)
print(decodedData.ISODate)
// Output: 2024-10-26 12:00:00 +0000
print(decodedData.UNIXDate)
// Output: 2024-10-26 12:00:00 +0000

If you want a quick reference of all these decoding strategies, together with other JSON parsing techniques, download my free cheat sheet below.

FREE GUIDE: Parsing JSON in Swift - The Ultimate Cheat Sheet

Learn the fundamentals of JSON parsing in Swift, advanced decoding techniques, and tasks for app development

DOWNLOAD THE FREE GUIDE

Leave a Comment