mirror of
https://github.com/apple/swift-foundation.git
synced 2025-05-25 07:19:02 +08:00
Update the Windows path for `String.range(of:)` being altered to `String._range(of:anchored:backwards:)`. This repairs the build of FoundationEssentials on Windows.
490 lines
18 KiB
Swift
490 lines
18 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#if canImport(Darwin)
|
|
import Darwin
|
|
#elseif canImport(Glibc)
|
|
import Glibc
|
|
#elseif canImport(ucrt)
|
|
import ucrt
|
|
#endif
|
|
|
|
#if os(Windows)
|
|
import WinSDK
|
|
#endif
|
|
|
|
#if FOUNDATION_FRAMEWORK
|
|
@_implementationOnly import _ForSwiftFoundation
|
|
@_implementationOnly import CoreFoundation_Private.CFNotificationCenter
|
|
#endif
|
|
|
|
/// Singleton which listens for notifications about preference changes for TimeZone and holds cached values for current, fixed time zones, etc.
|
|
struct TimeZoneCache : Sendable {
|
|
|
|
// MARK: - Concrete Classes
|
|
|
|
// _TimeZoneICU, if present
|
|
static var timeZoneICUClass: _TimeZoneProtocol.Type? = {
|
|
#if FOUNDATION_FRAMEWORK && canImport(FoundationICU)
|
|
_TimeZoneICU.self
|
|
#else
|
|
if let name = _typeByName("FoundationInternationalization._TimeZoneICU"), let t = name as? _TimeZoneProtocol.Type {
|
|
return t
|
|
} else {
|
|
return nil
|
|
}
|
|
#endif
|
|
}()
|
|
|
|
// _TimeZoneGMTICU or _TimeZoneGMT
|
|
static var timeZoneGMTClass: _TimeZoneProtocol.Type = {
|
|
#if FOUNDATION_FRAMEWORK && canImport(FoundationICU)
|
|
_TimeZoneGMTICU.self
|
|
#else
|
|
if let name = _typeByName("FoundationInternationalization._TimeZoneGMTICU"), let t = name as? _TimeZoneProtocol.Type {
|
|
return t
|
|
} else {
|
|
return _TimeZoneGMT.self
|
|
}
|
|
#endif
|
|
}()
|
|
|
|
// MARK: - State
|
|
|
|
struct State {
|
|
// a.k.a. `systemTimeZone`
|
|
private var currentTimeZone: TimeZone!
|
|
|
|
private var autoupdatingCurrentTimeZone: _TimeZoneAutoupdating!
|
|
|
|
// If this is not set, the behavior is to fall back to the current time zone
|
|
private var defaultTimeZone: TimeZone?
|
|
|
|
// This cache is not cleared, but only holds validly named time zones.
|
|
private var fixedTimeZones: [String: any _TimeZoneProtocol] = [:]
|
|
|
|
// This cache holds offset-specified time zones, but only a subset of the universe of possible values. See the implementation below for the policy.
|
|
private var offsetTimeZones: [Int: any _TimeZoneProtocol] = [:]
|
|
|
|
private var noteCount = -1
|
|
private var identifiers: [String]?
|
|
private var abbreviations: [String : String]?
|
|
|
|
#if FOUNDATION_FRAMEWORK
|
|
// These are caches of the NSTimeZone subclasses for use from Objective-C (without allocating each time)
|
|
private var bridgedCurrentTimeZone: _NSSwiftTimeZone!
|
|
private var bridgedAutoupdatingCurrentTimeZone: _NSSwiftTimeZone!
|
|
private var bridgedDefaultTimeZone: _NSSwiftTimeZone?
|
|
private var bridgedFixedTimeZones: [String : _NSSwiftTimeZone] = [:]
|
|
private var bridgedOffsetTimeZones: [Int : _NSSwiftTimeZone] = [:]
|
|
#endif // FOUNDATION_FRAMEWORK
|
|
|
|
mutating func check() {
|
|
#if FOUNDATION_FRAMEWORK
|
|
// On Darwin we listen for certain distributed notifications to reset the current TimeZone.
|
|
let newNoteCount = _CFLocaleGetNoteCount() + _CFTimeZoneGetNoteCount() + Int(_CFCalendarGetMidnightNoteCount())
|
|
#else
|
|
let newNoteCount = 1
|
|
#endif // FOUNDATION_FRAMEWORK
|
|
|
|
if newNoteCount != noteCount {
|
|
currentTimeZone = findCurrentTimeZone()
|
|
noteCount = newNoteCount
|
|
#if FOUNDATION_FRAMEWORK
|
|
bridgedCurrentTimeZone = _NSSwiftTimeZone(timeZone: currentTimeZone)
|
|
_CFNotificationCenterInitializeDependentNotificationIfNecessary(CFNotificationName.cfTimeZoneSystemTimeZoneDidChange!.rawValue)
|
|
#endif // FOUNDATION_FRAMEWORK
|
|
}
|
|
}
|
|
|
|
mutating func reset() -> TimeZone? {
|
|
let oldTimeZone = currentTimeZone
|
|
|
|
// Ensure we do not reuse the existing time zone
|
|
noteCount = -1
|
|
check()
|
|
|
|
return oldTimeZone
|
|
}
|
|
|
|
/// Reads from environment variables `TZFILE`, `TZ` and finally the symlink pointed at by the C macro `TZDEFAULT` to figure out what the current (aka "system") time zone is.
|
|
mutating func findCurrentTimeZone() -> TimeZone {
|
|
#if !NO_TZFILE
|
|
if let tzenv = getenv("TZFILE") {
|
|
if let name = String(validatingUTF8: tzenv) {
|
|
if let result = fixed(name) {
|
|
return TimeZone(inner: result)
|
|
}
|
|
}
|
|
}
|
|
|
|
if let tz = getenv("TZ") {
|
|
if let name = String(validatingUTF8: tz) {
|
|
// Try as an abbreviation first
|
|
// Use cached function here to avoid recursive lock
|
|
if let name2 = timeZoneAbbreviations()[name] {
|
|
if let result = fixed(name2) {
|
|
return TimeZone(inner: result)
|
|
}
|
|
}
|
|
|
|
// Try with just the name
|
|
if let result = fixed(name) {
|
|
return TimeZone(inner: result)
|
|
}
|
|
}
|
|
}
|
|
|
|
#if os(Windows)
|
|
let hFile = TimeZone.TZDEFAULT.withCString(encodedAs: UTF16.self) {
|
|
CreateFileW($0, GENERIC_READ, DWORD(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), nil, DWORD(OPEN_EXISTING), 0, nil)
|
|
}
|
|
defer { CloseHandle(hFile) }
|
|
let dwSize = GetFinalPathNameByHandleW(hFile, nil, 0, DWORD(VOLUME_NAME_DOS))
|
|
let path = withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(dwSize)) {
|
|
_ = GetFinalPathNameByHandleW(hFile, $0.baseAddress, dwSize, DWORD(VOLUME_NAME_DOS))
|
|
return String(decodingCString: $0.baseAddress!, as: UTF16.self)
|
|
}
|
|
if let rangeOfZoneInfo = path._range(of: "\(TimeZone.TZDIR)\\", anchored: false, backwards: false) {
|
|
let name = path[rangeOfZoneInfo.upperBound...]
|
|
if let result = fixed(String(name)) {
|
|
return TimeZone(inner: result)
|
|
}
|
|
}
|
|
#else
|
|
let buffer = UnsafeMutableBufferPointer<CChar>.allocate(capacity: Int(PATH_MAX + 1))
|
|
defer { buffer.deallocate() }
|
|
buffer.initialize(repeating: 0)
|
|
|
|
let ret = readlink(TimeZone.TZDEFAULT, buffer.baseAddress!, Int(PATH_MAX))
|
|
if ret >= 0 {
|
|
// Null-terminate the value
|
|
buffer[ret] = 0
|
|
if let file = String(validatingUTF8: buffer.baseAddress!) {
|
|
#if targetEnvironment(simulator) && (os(iOS) || os(tvOS) || os(watchOS))
|
|
let lookFor = "zoneinfo/"
|
|
#else
|
|
let lookFor = TimeZone.TZDIR + "/"
|
|
#endif
|
|
if let rangeOfZoneInfo = file._range(of: lookFor, anchored: false, backwards: false) {
|
|
let name = file[rangeOfZoneInfo.upperBound...]
|
|
if let result = fixed(String(name)) {
|
|
return TimeZone(inner: result)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#endif //!NO_TZFILE
|
|
// Last option as a default is the GMT value (again, using the cached version directly to avoid recursive lock)
|
|
return TimeZone(inner: offsetFixed(0)!)
|
|
}
|
|
|
|
mutating func current() -> TimeZone {
|
|
check()
|
|
return currentTimeZone
|
|
}
|
|
|
|
mutating func `default`() -> TimeZone {
|
|
check()
|
|
if let manuallySetDefault = defaultTimeZone {
|
|
return manuallySetDefault
|
|
} else {
|
|
return currentTimeZone
|
|
}
|
|
}
|
|
|
|
mutating func setDefaultTimeZone(_ tz: TimeZone?) -> TimeZone? {
|
|
// Ensure we are listening for notifications from here on out
|
|
check()
|
|
let old = defaultTimeZone
|
|
defaultTimeZone = tz
|
|
#if FOUNDATION_FRAMEWORK
|
|
if let tz {
|
|
bridgedDefaultTimeZone = _NSSwiftTimeZone(timeZone: tz)
|
|
} else {
|
|
bridgedDefaultTimeZone = nil
|
|
}
|
|
#endif // FOUNDATION_FRAMEWORK
|
|
return old
|
|
}
|
|
|
|
mutating func fixed(_ identifier: String) -> (any _TimeZoneProtocol)? {
|
|
// Check for GMT/UTC
|
|
if identifier == "GMT" {
|
|
return offsetFixed(0)
|
|
} else if let cached = fixedTimeZones[identifier] {
|
|
return cached
|
|
} else {
|
|
if let innerTz = TimeZoneCache.timeZoneICUClass?.init(identifier: identifier) {
|
|
fixedTimeZones[identifier] = innerTz
|
|
return innerTz
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
mutating func offsetFixed(_ offset: Int) -> (any _TimeZoneProtocol)? {
|
|
if let cached = offsetTimeZones[offset] {
|
|
return cached
|
|
} else {
|
|
// In order to avoid bloating a cache with weird time zones, only cache values that are 30min offsets (including 1hr offsets).
|
|
let doCache = abs(offset) % 1800 == 0
|
|
if let innerTz = TimeZoneCache.timeZoneGMTClass.init(secondsFromGMT: offset) {
|
|
if doCache {
|
|
offsetTimeZones[offset] = innerTz
|
|
}
|
|
return innerTz
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
mutating func autoupdatingCurrent() -> _TimeZoneAutoupdating {
|
|
if let cached = autoupdatingCurrentTimeZone {
|
|
return cached
|
|
} else {
|
|
autoupdatingCurrentTimeZone = _TimeZoneAutoupdating()
|
|
return autoupdatingCurrentTimeZone
|
|
}
|
|
}
|
|
|
|
mutating func timeZoneAbbreviations() -> [String : String] {
|
|
if abbreviations == nil {
|
|
abbreviations = defaultAbbreviations
|
|
}
|
|
return abbreviations!
|
|
}
|
|
|
|
mutating func setTimeZoneAbbreviations(_ abbreviations: [String : String]) {
|
|
self.abbreviations = abbreviations
|
|
}
|
|
|
|
let defaultAbbreviations: [String: String] = [
|
|
"ADT": "America/Halifax",
|
|
"AKDT": "America/Juneau",
|
|
"AKST": "America/Juneau",
|
|
"ART": "America/Argentina/Buenos_Aires",
|
|
"AST": "America/Halifax",
|
|
"BDT": "Asia/Dhaka",
|
|
"BRST": "America/Sao_Paulo",
|
|
"BRT": "America/Sao_Paulo",
|
|
"BST": "Europe/London",
|
|
"CAT": "Africa/Harare",
|
|
"CDT": "America/Chicago",
|
|
"CEST": "Europe/Paris",
|
|
"CET": "Europe/Paris",
|
|
"CLST": "America/Santiago",
|
|
"CLT": "America/Santiago",
|
|
"COT": "America/Bogota",
|
|
"CST": "America/Chicago",
|
|
"EAT": "Africa/Addis_Ababa",
|
|
"EDT": "America/New_York",
|
|
"EEST": "Europe/Athens",
|
|
"EET": "Europe/Athens",
|
|
"EST": "America/New_York",
|
|
"GMT": "GMT",
|
|
"GST": "Asia/Dubai",
|
|
"HKT": "Asia/Hong_Kong",
|
|
"HST": "Pacific/Honolulu",
|
|
"ICT": "Asia/Bangkok",
|
|
"IRST": "Asia/Tehran",
|
|
"IST": "Asia/Kolkata",
|
|
"JST": "Asia/Tokyo",
|
|
"KST": "Asia/Seoul",
|
|
"MDT": "America/Denver",
|
|
"MSD": "Europe/Moscow",
|
|
"MSK": "Europe/Moscow",
|
|
"MST": "America/Phoenix",
|
|
"NDT": "America/St_Johns",
|
|
"NST": "America/St_Johns",
|
|
"NZDT": "Pacific/Auckland",
|
|
"NZST": "Pacific/Auckland",
|
|
"PDT": "America/Los_Angeles",
|
|
"PET": "America/Lima",
|
|
"PHT": "Asia/Manila",
|
|
"PKT": "Asia/Karachi",
|
|
"PST": "America/Los_Angeles",
|
|
"SGT": "Asia/Singapore",
|
|
"TRT": "Europe/Istanbul",
|
|
"UTC": "UTC",
|
|
"WAT": "Africa/Lagos",
|
|
"WEST": "Europe/Lisbon",
|
|
"WET": "Europe/Lisbon",
|
|
"WIT": "Asia/Jakarta",
|
|
]
|
|
|
|
// MARK: - State Bridging
|
|
#if FOUNDATION_FRAMEWORK
|
|
mutating func bridgedCurrent() -> _NSSwiftTimeZone {
|
|
check()
|
|
return bridgedCurrentTimeZone
|
|
}
|
|
|
|
mutating func bridgedAutoupdatingCurrent() -> _NSSwiftTimeZone {
|
|
if let autoupdating = bridgedAutoupdatingCurrentTimeZone {
|
|
return autoupdating
|
|
} else {
|
|
// Do not call TimeZone.autoupdatingCurrent, as it will recursively lock.
|
|
let tz = TimeZone(inner: autoupdatingCurrent())
|
|
let result = _NSSwiftTimeZone(timeZone: tz)
|
|
bridgedAutoupdatingCurrentTimeZone = result
|
|
return result
|
|
}
|
|
}
|
|
|
|
mutating func bridgedDefault() -> _NSSwiftTimeZone {
|
|
check()
|
|
if let manuallySetDefault = bridgedDefaultTimeZone {
|
|
return manuallySetDefault
|
|
} else {
|
|
return bridgedCurrentTimeZone
|
|
}
|
|
}
|
|
|
|
mutating func bridgedFixed(_ identifier: String) -> _NSSwiftTimeZone? {
|
|
if let cached = bridgedFixedTimeZones[identifier] {
|
|
return cached
|
|
}
|
|
if let swiftCached = fixedTimeZones[identifier] {
|
|
// If we don't have a bridged instance yet, check to see if we have a Swift one and re-use that
|
|
let bridged = _NSSwiftTimeZone(timeZone: TimeZone(inner: swiftCached))
|
|
bridgedFixedTimeZones[identifier] = bridged
|
|
return bridged
|
|
}
|
|
#if canImport(FoundationICU)
|
|
if let innerTz = _TimeZoneICU(identifier: identifier) {
|
|
// In this case, the identifier is unique and we need to cache it (in two places)
|
|
fixedTimeZones[identifier] = innerTz
|
|
let bridgedTz = _NSSwiftTimeZone(timeZone: TimeZone(inner: innerTz))
|
|
bridgedFixedTimeZones[identifier] = bridgedTz
|
|
return bridgedTz
|
|
}
|
|
#endif
|
|
return nil
|
|
}
|
|
|
|
mutating func bridgedOffsetFixed(_ offset: Int) -> _NSSwiftTimeZone? {
|
|
if let cached = bridgedOffsetTimeZones[offset] {
|
|
return cached
|
|
}
|
|
if let swiftCached = offsetTimeZones[offset] {
|
|
// If we don't have a bridged instance yet, check to see if we have a Swift one and re-use that
|
|
let bridged = _NSSwiftTimeZone(timeZone: TimeZone(inner: swiftCached))
|
|
bridgedOffsetTimeZones[offset] = bridged
|
|
return bridged
|
|
}
|
|
#if canImport(FoundationICU)
|
|
let maybeInnerTz = _TimeZoneGMTICU(secondsFromGMT: offset)
|
|
#else
|
|
let maybeInnerTz = _TimeZoneGMT(secondsFromGMT: offset)
|
|
#endif
|
|
if let innerTz = maybeInnerTz {
|
|
// In order to avoid bloating a cache with weird time zones, only cache values that are 30min offsets (including 1hr offsets).
|
|
let doCache = abs(offset) % 1800 == 0
|
|
|
|
// In this case, the offset is unique and we need to cache it (in two places)
|
|
let bridgedTz = _NSSwiftTimeZone(timeZone: TimeZone(inner: innerTz))
|
|
if doCache {
|
|
offsetTimeZones[offset] = innerTz
|
|
bridgedOffsetTimeZones[offset] = bridgedTz
|
|
}
|
|
return bridgedTz
|
|
}
|
|
|
|
return nil
|
|
}
|
|
#endif // FOUNDATION_FRAMEWORK
|
|
}
|
|
|
|
let lock: LockedState<State>
|
|
|
|
static let cache = TimeZoneCache()
|
|
|
|
fileprivate init() {
|
|
lock = LockedState(initialState: State())
|
|
}
|
|
|
|
func reset() -> TimeZone? {
|
|
return lock.withLock { $0.reset() }
|
|
}
|
|
|
|
var current: TimeZone {
|
|
lock.withLock { $0.current() }
|
|
}
|
|
|
|
var `default`: TimeZone {
|
|
lock.withLock { $0.default() }
|
|
}
|
|
|
|
func setDefault(_ tz: TimeZone?) {
|
|
let oldDefaultTimeZone = lock.withLock {
|
|
return $0.setDefaultTimeZone(tz)
|
|
}
|
|
|
|
CalendarCache.cache.reset()
|
|
#if FOUNDATION_FRAMEWORK
|
|
if let oldDefaultTimeZone {
|
|
let noteName = CFNotificationName(rawValue: "kCFTimeZoneSystemTimeZoneDidChangeNotification-2" as CFString)
|
|
let oldAsNS = oldDefaultTimeZone as NSTimeZone
|
|
let unmanaged = Unmanaged.passRetained(oldAsNS).autorelease()
|
|
CFNotificationCenterPostNotification(CFNotificationCenterGetLocalCenter(), noteName, unmanaged.toOpaque(), nil, true)
|
|
}
|
|
#endif // FOUNDATION_FRAMEWORK
|
|
}
|
|
|
|
func fixed(_ identifier: String) -> _TimeZoneProtocol? {
|
|
lock.withLock { $0.fixed(identifier) }
|
|
}
|
|
|
|
func offsetFixed(_ seconds: Int) -> (any _TimeZoneProtocol)? {
|
|
lock.withLock { $0.offsetFixed(seconds) }
|
|
}
|
|
|
|
func autoupdatingCurrent() -> _TimeZoneAutoupdating {
|
|
lock.withLock { $0.autoupdatingCurrent() }
|
|
}
|
|
|
|
func timeZoneAbbreviations() -> [String : String] {
|
|
lock.withLock { $0.timeZoneAbbreviations() }
|
|
}
|
|
|
|
func setTimeZoneAbbreviations(_ abbreviations: [String : String]) {
|
|
lock.withLock { $0.setTimeZoneAbbreviations(abbreviations) }
|
|
}
|
|
|
|
// MARK: - Cache for bridged types
|
|
#if FOUNDATION_FRAMEWORK
|
|
var bridgedCurrent: _NSSwiftTimeZone {
|
|
lock.withLock { $0.bridgedCurrent() }
|
|
}
|
|
|
|
var bridgedAutoupdatingCurrent: _NSSwiftTimeZone {
|
|
lock.withLock { $0.bridgedAutoupdatingCurrent() }
|
|
}
|
|
|
|
var bridgedDefault: _NSSwiftTimeZone {
|
|
lock.withLock { $0.bridgedDefault() }
|
|
}
|
|
|
|
func bridgedFixed(_ identifier: String) -> _NSSwiftTimeZone? {
|
|
lock.withLock { $0.bridgedFixed(identifier) }
|
|
}
|
|
|
|
func bridgedOffsetFixed(_ seconds: Int) -> _NSSwiftTimeZone? {
|
|
lock.withLock { $0.bridgedOffsetFixed(seconds) }
|
|
}
|
|
#endif // FOUNDATION_FRAMEWORK
|
|
}
|