swiftlang / swift-corelibs-foundation

The Foundation Project, providing core utilities, internationalization, and OS independence
swift.org
Apache License 2.0
5.3k stars 1.14k forks source link

DateFormatter.string still applies Daylight Saving Time to mexican timezones, even when Mexico stoped using daylight saving time this year (2023) #4749

Open jasaldivara opened 1 year ago

jasaldivara commented 1 year ago

Mexico stopped using daylight saving time

But when I try to format a Date using Foundation's DateFormatter with a mexican TimeZone on Swift, it shows the date as if the daylight saving time were still in use.

Time zones from central Mexico (like "America/Monterrey" and "America/Mexico") are six hours before Universal Time (UTC-6:00), but with Foundation they are currently being formatted as if they were UTC-5:00, which is not correct.

Code example:

import Foundation

let now = Date()
let dtFormatter = DateFormatter()
dtFormatter.timeStyle = .medium
dtFormatter.dateStyle = .none
dtFormatter.timeZone = TimeZone(identifier: "America/Monterrey")
print (dtFormatter.string(from: now))

Result: It prints 9:13:22 PM when the correct local time is 8:13 PM

There is something strange. If I run:

print (dtFormatter.timeZone!.isDaylightSavingTime(for: now))

It prints false, which is correct. But trying to format a Date as string still behaves as if daylight saving time were active.

If I run:

print (TimeZone.timeZoneDataVersion)

It prints 2019c, which make me think that the time zone data is too old

Operating System: Linux, Fedora 38. I'm using the swift-lang package from the Fedora repos

compnerd commented 1 year ago

This information is queried through ICU. The version of the ICU data that is being built and linked against is https://github.com/apple/swift/blob/main/utils/update_checkout/update-checkout-config.json#L120 (65.1), which likely does not have the updated CLDR data.

jasaldivara commented 1 year ago

Is there a reason why Swift depends on ICU version 65 instead of it's latest release?

fred-sch commented 3 months ago

We also observe the described behavior. Here is a small test case that will find time zone identifiers which show the same behavior. Maybe this is of any use

import Foundation
import XCTest

extension DateFormatter {
    public convenience init(format: String, timeZone: TimeZone = TimeZone(abbreviation: "UTC")!, locale: Locale = .current) {
        self.init()
        self.dateFormat = format
        self.timeZone = timeZone
        self.locale = locale
    }
}

final class TimeZoneTests: XCTestCase {
    func testTimeZonesFromUTCOffset() {

        let now = Date(timeIntervalSince1970: Date().timeIntervalSince1970.rounded())

        for identifier in TimeZone.knownTimeZoneIdentifiers {
            guard let timeZoneFromIdentifier = TimeZone(identifier: identifier) else {
                XCTFail("Cannot initialize timezone from identifier \(identifier)")
                continue
            }

            let gmtOffset = timeZoneFromIdentifier.secondsFromGMT()
            let timeZoneFromGMTOffset = TimeZone(secondsFromGMT: gmtOffset)!

            let formatterFromIdentifier = DateFormatter(format: "yyyyMMdd'T'HH:mm:ss", timeZone: timeZoneFromIdentifier)
            let formatterFromGMTOffset = DateFormatter(format: "yyyyMMdd'T'HH:mm:ss", timeZone: timeZoneFromGMTOffset)

            let formatted = formatterFromIdentifier.string(from: now)

            guard let nowAfterRoundtrip = formatterFromGMTOffset.date(from: formatted) else {
                XCTFail("Could not parse date \(formatted) with \(formatterFromGMTOffset.description)")
                continue
            }

            XCTAssertEqual(now, nowAfterRoundtrip, "Roundtrip did not work for timezone \(identifier)")
        }
    }
}