swiftlang / swift-foundation

The Foundation project
Apache License 2.0
2.39k stars 155 forks source link

ISO8601FormatStyle produces unexpected results when including fractional seconds #963

Open arthurcro opened 1 week ago

arthurcro commented 1 week ago

While working on https://github.com/apple/swift-openapi-generator/issues/637, I noticed ISO8601FormatStyle formattes dates with fractional seconds incorreclty. The fractional seconds field is off by one. Also, when parsing a formatted date with fractional seconds, the result is not equal to the initial formatted date.

You can reproduce the issue with the following code snippet.

import Foundation

let initialDate = Date(timeIntervalSince1970: 1_674_036_251.123) 
let formatStyle = Date.ISO8601FormatStyle.iso8601
    .year()
    .month()
    .day()
    .time(includingFractionalSeconds: true)

let encoded = initialDate.formatted(formatStyle)
print(encoded) // prints 2023-01-18T10:04:11.122 instead of 2023-01-18T10:04:11.123
print(initialDate == (try! formatStyle.parse(encoded)) // prints false
LordBurtz commented 1 week ago

Hi, could not replicate, copy/pasting your snippet... on what versions are you? does the issue still exist?

$ swift --version
swift-driver version: 1.90.11.1 Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4)
Target: arm64-apple-macosx14.0

$ uname -a
Darwin JPXNGP6J95 23.6.0 Darwin Kernel Version 23.6.0: Wed Jul 31 20:48:52 PDT 2024; root:xnu-10063.141.1.700.5~1/RELEASE_ARM64_T6020 arm64
arthurcro commented 1 week ago

Hey! Thank you for checking! The issue still exists, I'm on the following:

$ swift --version
swift-driver version: 1.115 Apple Swift version 6.0 (swiftlang-6.0.0.9.10 clang-1600.0.26.2)
Target: arm64-apple-macosx15.0

$ uname -a
Darwin arthurs-mbp.home 24.0.0 Darwin Kernel Version 24.0.0: Mon Aug 12 20:51:54 PDT 2024; root:xnu-11215.1.10~2/RELEASE_ARM64_T6000 arm64
jvdvleuten commented 1 week ago

I had the same issue in my own project (not swift-openapi) when I updated my own iPhone, I first opened a thread on the Swift forums: https://forums.swift.org/t/rounding-error-in-milliseconds-using-iso8601formatstyle-instead-of-iso8601dateformatter/75206

I also opened this case on the feedback assistant: FB15418195 (Rounding error in milliseconds using ISO8601FormatStyle on iOS 18.0)

import Testing
import UIKit

struct DateFormatterTests {
    @Test func testDateEncodingDecoding() async throws {
        let originalIsoString = "2024-10-05T17:08:34.650Z"

        let iso8601DateFormatter = ISO8601DateFormatter()
        iso8601DateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]

        let testDate = iso8601DateFormatter.date(from: originalIsoString)!
        let oldFormatterString = iso8601DateFormatter.string(from: testDate)

        #expect(oldFormatterString == originalIsoString)

        let isoDateFormat: Date.ISO8601FormatStyle = .iso8601
            .year()
            .month()
            .day()
            .timeZone(separator: .omitted)
            .time(includingFractionalSeconds: true)
            .timeSeparator(.colon)

        let date = try isoDateFormat.parse(originalIsoString)

        let newFormatterString = isoDateFormat.format(date)

        #expect(newFormatterString == originalIsoString)
    }
}

It happens when using iOS 18. I have no idea how the implementation of this works, is it baked inside iOS? Could it be due to this change perhaps: https://github.com/swiftlang/swift-foundation/pull/453/commits/337a22e08704c1024aa8d32c4608e83fedd41174 ?

LordBurtz commented 1 week ago

seems to be a problem of 6.0 vs 5.10 which matches the date of the change.. ill have a look over the weekend

jvdvleuten commented 5 days ago

@LordBurtz isn't this baked in the iOS version? How could I test this myself?

arthurcro commented 3 days ago

@parkera is this something you could help with when you get a chance? 🙇🏻

parkera commented 1 day ago

@stephentyrone was looking into this