How to parse ISO 8601 date in Swift
Table of Contents
What is ISO 8601
Date representation is complicated. Everyone has their own interpretation of the date. For example, 01/05/12 could mean January 5, 2012, or May 1, 2012.
This can be problematic if you want to communicate date and time information. How can we ensure that the receiver interprets date time the same way as a sender?
ISO 8601 tackles this problem by setting out an international standard way to represent dates. It has many variations to support date, date/time, with and without timezone and many more. Here are some examples:
- Date: 2022-01-31
- Date and time in UTC: 2022-01-31T02:22:40+00:00
- Date and time in UTC: 2022-01-31T02:22:40Z
You might have seen this kind of a date representation in some API responses, which makes perfect sense since we want both parties to agree on the same date format.
You can easily support sarunw.com by checking out this sponsor.
AI Grammar: Correct grammar, spell check, check punctuation, and parphrase.
How to parse dates
When parsing a date from a string, you might think of DateFormatter
.
You have to manually set dateFormat
and locale
to DateFormatter
instance.
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
let date = formatter.date(from: "2022-01-31T02:22:40Z")
You probably need this formatter every time you need to talk with API. Luckily, since iOS 10, Apple introduced a dedicated formatter for handling ISO 8601 date parsing, ISO8601DateFormatter
.
ISO8601DateFormatter
ISO8601DateFormatter
is simpler than DateFormatter
. We don't have to set locale
of dateFormat
. Less code means less chance we can do wrong with this new formatter.
Here is an example how we parsing ISO 8601 date string with ISO8601DateFormatter
. What we need to do is initialize and use it.
let newFormatter = ISO8601DateFormatter()
let date = newFormatter.date(from: "2022-01-31T02:22:40Z")
Caveats
ISO 8601 is a way to represent date/time that means it can be presented in a number of ways, e.g., without time, without a timezone, without dash separator in date, etc.
Here are some variations.
- 2022-01-31
- 2022-01-31T02:22:40+00:00
- 2022-01-31T02:22:40Z
- 20220131T022240Z
What you might not be aware of is ISO8601DateFormatter
won't handle all of these variations out of the box.
As you can see in the following example, ISO8601DateFormatter
failed to parse "2022-01-31"
which result in nil
.
let formatter = ISO8601DateFormatter()
let date = formatter.date(from: "2022-01-31")
print(date) // nil
We control the variation of date format by setting a combination of ISO8601DateFormatter.Options
, which we can set via the formatOptions
property.
The default formatOptions
is .withInternetDateTime
which is the format aim for use on the Internet defined in RFC 3339.
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime]
I can't go through every combination of format options, but I can show some popular ones.
ISO 8601 With fractional seconds
We can support the date with milliseconds part by specifying .withFractionalSeconds
. We can do this by setting whole new options to formatOptions
or inserting .withFractionalSeconds
into the current format.
let formatter = ISO8601DateFormatter()
// Insert .withFractionalSeconds to the current format.
formatter.formatOptions.insert(.withFractionalSeconds)
let date = formatter.date(from: "2022-01-05T03:30:00.000Z")
// Set new format with .withFractionalSeconds
formatter.formatOptions = [
.withInternetDateTime,
.withFractionalSeconds
]
let date2 = formatter.date(from: "2022-01-05T03:30:00.000Z")
ISO 8601 without dash and colon
ISO 8601 also supports date/time without separator, e.g., 20220131T022240Z
(2022-01-31T02:22:40Z
). To parse this format, we need to specify each element one by one.
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [
.withDay,
.withMonth,
.withYear,
.withTime,
.withTimeZone
]
let date = formatter.date(from: "20220131T022240Z")
print(date)
// 2022-01-31 02:22:40 +0000
ISO8601DateFormatter.Options
Here are some examples of how it looks for various combinations of format options.
The following table shows the various alignment
you can set and how it would position.
Format | Example | Options |
---|---|---|
Date | 2022-01-31 | withFullDate |
Day, Month, Year | 20220131 | withDay , withMonth , withYear |
Date and Time | 2022-01-31T08:42:07Z | withFullDate , withFullTime |
Date and Time with space separator between date and time | 2022-01-31 08:42:07Z | withFullDate , withFullTime , withSpaceBetweenDateAndTime |
Year and Week of Year | 2022-W05 | withYear , withWeekOfYear , withDashSeparatorInDate |
Year, Week of Year with Ordinal Weekday | 2022-W05-01 | withYear , withWeekOfYear , withDay , withDashSeparatorInDate |
You can easily support sarunw.com by checking out this sponsor.
AI Grammar: Correct grammar, spell check, check punctuation, and parphrase.
Codable
We can set the date encoding strategy to JSONEncoder
/JSONDecoder
to control how you want a date to be decoded and encoded. If the date string is in RFC 3339 format, you can use a built-in .iso8601
date coding strategy.
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
If you have a date other than RFC 3339 format, you must specify a .custom
date coding strategy.
Since ISO8601DateFormatter
doesn't inherit from DateFormatter
, you can't set it with the .formatted
date coding strategy since it accepts DateFormatter
.
public enum DateDecodingStrategy {
/// Decode the `Date` as a string parsed by the given formatter.
case formatted(DateFormatter)
...
}
We can't see it like this:
let encoder = JSONEncoder()
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withFullDate]
encoder.dateEncodingStrategy = .formatted(formatter)
// Cannot convert value of type 'ISO8601DateFormatter' to expected argument type 'DateFormatter'
Instead, we need to use the .custom
date coding strategy.
Encoder
Here is how you set custom ISO 8601 date format for JSONEncoder
.
let encoder = JSONEncoder()
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withFullDate]
encoder.dateEncodingStrategy = .custom({ date, encoder in
var container = encoder.singleValueContainer()
let dateString = formatter.string(from: date)
try container.encode(dateString)
})
Decoder
Here is how you set custom ISO 8601 date format for JSONDecoder
.
let decoder = JSONDecoder()
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withFullDate]
decoder.dateDecodingStrategy = .custom({ decoder in
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)
if let date = formatter.date(from: dateString) {
return date
}
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date string \(dateString)")
})
Read more article about Swift, DateFormatter, Date, or see all available topic
Enjoy the read?
If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.
Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.
If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.
Become a patron Buy me a coffee Tweet ShareHow to reset push notification permission on iOS
Learn how to make notification permission dialog popup again like the first time you run the app.
How to render text with a color gradient in SwiftUI
Learn how to apply gradient colors to a SwiftUI text view.