This works with or without milliseconds because it ignores the time portion of the string. That is, your resulting Date will always have time at 00:00:00Z.
Adding .withFullTime brings us back to matching either with or without milliseconds again.
Im having the same issue and i've solved it the same way as above. Im curious though what backend apis are written in in these cases. They are all .net backend apis i bet. that's what i've seen the most non standard stuff with.
Only hope to get this fixed is to ensure the swift-foundation rewrite increases compatibility. I don't think they've implemented DateFormatter publicly yet but some of the tools are there, so hopefully we can see the implementation before it goes public.
I want to throw in some other specifity:
As far as I understood "date only" (without time) is also ISO/RFC compliant.
The ISO decoding strategy cannot handle this case either.
Some services don't want to represent time in a date and represent dates using the ISO format, but Swift can't handle that without modifications.
Apparently the standard is much more generic than just one type of string representation in JSON, so I think the name "iso8601" for the decoding stategy is too generic.
We should have different strict formats for "iso8601" and also lenient variants.
In case this is useful to anyone else: I was led to revisit this because I'd been using a JSONDecoder with
let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions = [
.withInternetDateTime, .withFractionalSeconds
]
decoder.dateDecodingStrategy = .custom({ decoder in
// Error handling omitted for brevity
dateFormatter.date(from: try! decoder.singleValueContainer().decode(String.self))!
})
but Swift 6 is warning that ISO8601DateFormatter is not Sendable, which is a requirement of the .custom closure.
I noticed that Foundation's internal handling for the .iso8601 strategy is effectively just Date.ISO8601FormatStyle().parse(string)
... which led me to realize I could use a custom Date.ISO8601FormatStyle to have a simpler (and probably faster?) alternative that is free from Sendable violations:
decoder.dateDecodingStrategy = .custom({ decoder in
// Again skipping error handling for brevity
let string = try! decoder.singleValueContainer().decode(String.self)
return try! Date.ISO8601FormatStyle(includingFractionalSeconds: true).parse(string)
})
It would still be cleaner and more discoverable to have a .iso8601WithOptions(...) strategy like @itaiferber proposed, but in the absence of that this is functionally equivalent.