ISO8601DateFormatter doesn't parse ISO date string
Solution 1
Prior to macOS 10.13 / iOS 11 ISO8601DateFormatter
does not support date strings including milliseconds.
A workaround is to remove the millisecond part with regular expression.
let isoDateString = "2017-01-23T10:12:31.484Z"
let trimmedIsoString = isoDateString.replacingOccurrences(of: "\\.\\d+", with: "", options: .regularExpression)
let formatter = ISO8601DateFormatter()
let date = formatter.date(from: trimmedIsoString)
In macOS 10.13+ / iOS 11+ a new option is added to support fractional seconds:
static var withFractionalSeconds: ISO8601DateFormatter.Options { get }
let isoDateString = "2017-01-23T10:12:31.484Z"
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let date = formatter.date(from: isoDateString)
Solution 2
For people that are not ready to go to iOS 11 yet, you can always create your own formatter to handle milliseconds, e.g.:
extension DateFormatter {
static var iSO8601DateWithMillisec: DateFormatter {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
return dateFormatter
}
}
Usage:
let formater = DateFormatter.iSO8601DateWithMillisec
let date = formater.date(from: "2017-01-23T10:12:31.484Z")!
print(date) // output: 2017-01-23 10:12:31 +0000
It is slightly more elegant than writing a regex to strip out the milliseconds from the input string.
Solution 3
Maybe this will help to decode slightly different formats:
extension JSONDecoder {
enum DateDecodeError: String, Error {
case invalidDate
}
static var bestDateAttemptDecoder: JSONDecoder {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
let container = try decoder.singleValueContainer()
if let dateSecs = try? container.decode(Double.self) {
return Date(timeIntervalSince1970: dateSecs)
}
if let dateSecs = try? container.decode(UInt.self) {
return Date(timeIntervalSince1970: TimeInterval(dateSecs))
}
let dateStr = try container.decode(String.self)
let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
if let date = isoFormatter.date(from: dateStr) {
return date
}
isoFormatter.formatOptions = [.withInternetDateTime ]
if let date = isoFormatter.date(from: dateStr) {
return date
}
log.warning("Cannot decode date");
throw DateDecodeError.invalidDate
})
return decoder
}
}
From: https://gist.github.com/th3m477/442a0d1da6354dd3b84e3b71df5dca6a
Solution 4
I encountered same issue some months ago. And here's my solution for reference:
// *****************************************
// MARK: - Formatter extension
// *****************************************
extension Formatter {
static let iso8601: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.timeZone = TimeZone.current
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return formatter
}()
static let iso8601NoSecond: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.timeZone = TimeZone.current
formatter.formatOptions = [.withInternetDateTime]
return formatter
}()
}
// *****************************************
// MARK: - ISO8601 helper
// *****************************************
func getDateFrom(DateString8601 dateString:String) -> Date?
{
if let date = Formatter.iso8601.date(from: dateString) {
return date
}
if let date = Formatter.iso8601NoSecond.date(from: dateString) {
return date
}
return nil
}
// *****************************************
// usage
// *****************************************
let d = getDateFrom(DateString8601: "2017-01-23T10:12:31.484Z")
print("2017-01-23T10:12:31.484Z millis= ", d?.timeIntervalSinceReferenceDate)
let d2 = getDateFrom(DateString8601: "2017-01-23T10:12:31Z")
print("2017-01-23T10:12:31Z millis= ", d2?.timeIntervalSinceReferenceDate)
// *****************************************
// result
// *****************************************
2017-01-23T10:12:31.484Z millis= Optional(506859151.48399997)
2017-01-23T10:12:31Z millis= Optional(506859151.0)
mhergon
Updated on June 25, 2022Comments
-
mhergon almost 2 years
I'm trying to parse this
2017-01-23T10:12:31.484Z
using native
ISO8601DateFormatter
class provided byiOS 10
but always fails. If the string not contains milliseconds, theDate
object is created without problems.I'm tried this and many
options
combination but always fails...let formatter = ISO8601DateFormatter() formatter.timeZone = TimeZone(secondsFromGMT: 0) formatter.formatOptions = [.withInternetDateTime, .withDashSeparatorInDate, .withColonSeparatorInTime, .withColonSeparatorInTimeZone, .withFullTime]
Any idea? Thanks!
-
Fogmeister over 6 yearsWhen was that added? I filed a radar about 3 weeks ago because it didn't have fractional seconds. It still doesn't seem to allow for selecting the number of decimal places though :( openradar.appspot.com/radar?id=6095965782016000
-
vadian over 6 yearsIt was added with the macOS 10.13 / iOS 11 SDK
-
Fogmeister over 6 yearsNo, it really wasn't. That wasn't there 3 weeks ago. Must have been added very recently. It may support back to 11.0 but it must only have been added in the past couple days. I even showed a colleague that it wasn't there just last week. :)
-
vadian over 6 yearsCould be. I read about it in the release notes / API changes when Xcode 9 was released but I didn't prove that the API was really available.
-
4b0 almost 6 yearsMm282, while this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Answers that are little more than a link may be deleted.
-
craft over 5 yearsstackoverflow.com/questions/28016578/… also provides some additional ideas and context re: pre 10.13 and post 10.13 approaches
-
hash3r about 5 yearsBe careful withFractionalSeconds crashes up to 11.2. it is fixed in 11.2+
-
zero3nna about 5 yearsthx @hash3r i was wondering if I'm the only one that run into this problem
-
vauxhall about 4 yearsI was crashing for us also, documentation is misleading, thanks @hash3r
-
Leo Dabus almost 4 years
formatter.timeZone = TimeZone.current
is wrong. Why do you think Apple made ISO8601DateFormatter timezone defaults to zero seconds from GMT?