ISO8601DateFormatter and Fractional Seconds
DateFormatter
is used for converting string representation of date and time to aDate
type and visa-versa. Something to be aware of is that the conversion loses microseconds precision. This is extremely important if we use theseDate
values for sorting and therefore ending up with incorrect order. Let’s consider an iOS app which uses API for fetching a list of items and each of the item contains a timestamp used for sorting the list. Often, these timestamps have the ISO8601 format like2024-09-21T10:32:32.113123Z
. Foundation framework has a dedicated formatter for parsing these strings: ISO8601DateFormatter.[…]
Fortunately this can be fixed by manually parsing microseconds and adding the missing precision to the converted
Date
value.
.withFractionalSeconds
only preserves three digits. Cocoa trivia: NSISO8601DateFormatter
is an NSFormatter
, not an NSDateFormatter
.
Previously:
Update (2024-09-25): calicoding:
ISO8601DateFormatter
also isn’tSendable
(butDateFormatter
is) 🫠. Makes it difficult to declare a shared instance for parsing dates from and API on a background thread.
See also: Ole Begemann.
4 Comments RSS · Twitter · Mastodon
The newer Date.ISO8601FormatStyle API does parse fractional seconds with sub-microsecond precision. Testing with an input string with 9 fractional digits (nanoseconds), the parsed timestamp is accurate to 7 fractional digits (100 ns), which seems to be roughly what can be expected for a 64-bit float. Tested on macOS 15.0 with Xcode 16.0. The code:
import Foundation
let dateStr = "2024-09-21T10:00:00.123456789Z"
let format = Date.ISO8601FormatStyle(includingFractionalSeconds: true)
let date = try format.parse(dateStr)
print(date.timeIntervalSince1970) // 1726912800.123457
Digging a little deeper. The Date.ISO8601FormatStyle accepts up to 9 fractional digits (nanoseconds). If your date string has 10 fractional digits, you get a parse error — it doesn't just ignore the extra digits. I believe this is the line of code in the new Swift Foundation package that explains this behavior: https://github.com/swiftlang/swift-foundation/blob/dcd7a97cd6af5593555c6ddbd33bf0c0c6523a44/Sources/FoundationEssentials/Formatting/Date%2BISO8601FormatStyle.swift#L581
The Date.ISO8601FormatStyle parser uses DateComponents internally, which also has nanosecond precision. The precision of the resulting Date value is, of course, limited by 64-bit float precision. If your timestamp is very close to the Foundation reference date 2001-01-01, you can actually get nanosecond precision in the resulting Date value:
let format = Date.ISO8601FormatStyle(includingFractionalSeconds: true)
let dateStr = "2001-01-01T00:00:00.123456789Z"
let date = try format.parse(dateStr)
print(date.timeIntervalSinceReferenceDate) // 0.123456789
The further you move away from that date, the less precision you get.
I did not have a deeper look at the parser side but the formatter part of ISO8601FormatterStyle is broken. Use it with great care!
I wrote about it (and how I envision a ISO8601Formatter) at https://github.com/swiftlang/swift-corelibs-foundation/issues/3220#issuecomment-1118309822
(An example where the parser fails for a valid ISO8601 date is: "2024-W39-2T00:00:00Z")