Monday, September 23, 2024

ISO8601DateFormatter and Fractional Seconds

Toomas Vahter:

DateFormatter is used for converting string representation of date and time to a Date type and visa-versa. Something to be aware of is that the conversion loses microseconds precision. This is extremely important if we use these Date 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 like 2024-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:

3 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


@Ole Interesting, thanks. I had thought the new API just called down to the old one.


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.

Leave a Comment