Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions Sources/SkipFoundation/TimeZone.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,27 @@ public struct TimeZone : Hashable, Codable, CustomStringConvertible, Sendable, K
return knownTimeZoneIdentifiers
}

public static var abbreviationDictionary: [String : String] = {
// Built on first use: a stored static's initializer runs when the class loads on Android, and walking every
// zone through ICU there costs hundreds of milliseconds on low-end devices (UserDefaults touches TimeZone at launch).
public static var abbreviationDictionary: [String : String] {
get {
if let abbreviations = cachedAbbreviationDictionary {
return abbreviations
}
let abbreviations = buildAbbreviationDictionary()
cachedAbbreviationDictionary = abbreviations
return abbreviations
}
set {
cachedAbbreviationDictionary = newValue
}
}

// Not synchronized: concurrent first reads may each build the same dictionary, which is harmless.
// Internal so tests can check it's built on demand.
internal static var cachedAbbreviationDictionary: [String : String]? = nil

private static func buildAbbreviationDictionary() -> [String : String] {
var abbreviations: [String : String] = [:]
// Abbreviations are ambiguous. Keep the first identifier in sorted order so
// the mapping is stable, and use English names regardless of the device locale.
Expand All @@ -149,7 +169,7 @@ public struct TimeZone : Hashable, Codable, CustomStringConvertible, Sendable, K
}
}
return abbreviations
}()
}

@available(*, unavailable)
public static var timeZoneDataVersion: String {
Expand Down
16 changes: 16 additions & 0 deletions Tests/SkipFoundationTests/DateTime/TestTimeZone.swift
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,22 @@ class TestTimeZone: XCTestCase {
}
}

func test_abbreviationDictionaryIsBuiltOnFirstUse() {
#if SKIP
// Building the dictionary walks every known zone through ICU, so plain TimeZone use must not trigger it
let original = TimeZone.cachedAbbreviationDictionary
defer { TimeZone.cachedAbbreviationDictionary = original }
TimeZone.cachedAbbreviationDictionary = nil
XCTAssertEqual(TimeZone(identifier: "Asia/Tokyo")?.identifier, "Asia/Tokyo")
XCTAssertNotNil(TimeZone(secondsFromGMT: 3600))
let _ = TimeZone.current
XCTAssertNil(TimeZone.cachedAbbreviationDictionary)

XCTAssertNotNil(TimeZone(abbreviation: "GMT"))
XCTAssertNotNil(TimeZone.cachedAbbreviationDictionary)
#endif
}

func test_initializingTimeZoneWithCustomAbbreviations() {
let original = TimeZone.abbreviationDictionary
defer { TimeZone.abbreviationDictionary = original }
Expand Down
Loading