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.
- This article is part of the Parsing JSON in Swift series.
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 GUIDETable of contents
- Decoding dates in the ISO 8601 format (without fractional seconds)
- Decoding UNIX dates expressed in seconds or milliseconds
- Decoding custom string dates using a DateFormatter
- Decoding custom date formats with a custom date decoding strategy
- The .deferredToDate strategy is only useful with Apple dates
- Decoding JSON data containing multiple date formats
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 GUIDEMatteo has been developing apps for iOS since 2008. He has been teaching iOS development best practices to hundreds of students since 2015 and he is the developer of Vulcan, a macOS app to generate SwiftUI code. Before that he was a freelance iOS developer for small and big clients, including TomTom, Squla, Siilo, and Layar. Matteo got a master’s degree in computer science and computational logic at the University of Turin. In his spare time he dances and teaches tango.