Tony Parker 90109a4cf6
Revert formatting performance improvements (#744)
There appears to be some kind of race or memory smash in ICU after these, and we need more time to investigate the full root cause.
2024-07-18 17:43:34 -07:00

422 lines
17 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 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
#endif
internal import _FoundationCShims
/**
`TimeZone` defines the behavior of a time zone. Time zone values represent geopolitical regions. Consequently, these values have names for these regions. Time zone values also represent a temporal offset, either plus or minus, from Greenwich Mean Time (GMT) and an abbreviation (such as PST for Pacific Standard Time).
`TimeZone` provides two static functions to get time zone values: `current` and `autoupdatingCurrent`. The `autoupdatingCurrent` time zone automatically tracks updates made by the user.
Note that time zone database entries such as "America/Los_Angeles" are IDs, not names. An example of a time zone name is "Pacific Daylight Time". Although many `TimeZone` functions include the word "name", they refer to IDs.
Cocoa does not provide any API to change the time zone of the computer, or of other applications.
*/
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
public struct TimeZone : Hashable, Equatable, Sendable {
private var _tz: _TimeZoneProtocol
// MARK: -
//
/// Returns a time zone initialized with a given identifier.
///
/// An example identifier is "America/Los_Angeles".
///
/// If `identifier` is an unknown identifier, then returns `nil`.
public init?(identifier: __shared String) {
// This will create/cache the value if it is valid
guard let cached = TimeZoneCache.cache.fixed(identifier) else {
return nil
}
_tz = cached
}
/// Directly instantiates a time zone without causing infinite recursion by checking the cache.
internal init(inner: some _TimeZoneProtocol) {
_tz = inner
}
/// Returns a time zone initialized with a specific number of seconds from GMT.
///
/// Time zones created with this never have daylight savings and the offset is constant no matter the date. The identifier and abbreviation do NOT follow the POSIX convention (of minutes-west).
///
/// - parameter seconds: The number of seconds from GMT.
/// - returns: A time zone, or `nil` if a valid time zone could not be created from `seconds`.
public init?(secondsFromGMT seconds: Int) {
guard let cached = TimeZoneCache.cache.offsetFixed(seconds) else {
return nil
}
_tz = cached
}
internal init?(name: String) {
// Try the cache first
if let cached = TimeZoneCache.cache.fixed(name) {
_tz = cached
} else {
return nil
}
}
/// Returns a time zone identified by a given abbreviation.
///
/// In general, you are discouraged from using abbreviations except for unique instances such as "GMT". Time Zone abbreviations are not standardized and so a given abbreviation may have multiple meanings--for example, "EST" refers to Eastern Time in both the United States and Australia
///
/// - parameter abbreviation: The abbreviation for the time zone.
/// - returns: A time zone identified by abbreviation determined by resolving the abbreviation to an identifier using the abbreviation dictionary and then returning the time zone for that identifier. Returns `nil` if there is no match for abbreviation.
public init?(abbreviation: __shared String) {
guard let id = TimeZone.identifierForAbbreviation(abbreviation) else {
return nil
}
self.init(identifier: id)
}
internal static func identifierForAbbreviation(_ abbreviation: String) -> String? {
if let name = TimeZone.abbreviationDictionary[abbreviation] {
return name
} else {
// No known time zone for this abbreviation
if let seconds = TimeZone.tryParseGMTName(abbreviation) {
if let gmtName = TimeZone.nameForSecondsFromGMT(seconds) {
return gmtName
}
}
}
return nil
}
#if FOUNDATION_FRAMEWORK
private init(reference: NSTimeZone) {
if let swift = reference as? _NSSwiftTimeZone {
_tz = swift.timeZone._tz
} else {
// This is a custom NSTimeZone subclass
_tz = _TimeZoneBridged(adoptingReference: reference)
}
}
#endif
/// The time zone currently used by the system.
public static var current : TimeZone {
TimeZone(inner: TimeZoneCache.cache.current._tz)
}
/// The time zone currently used by the system, automatically updating to the user's current preference.
///
/// If this time zone is mutated, then it no longer tracks the system time zone.
///
/// The autoupdating time zone only compares equal to itself.
public static var autoupdatingCurrent : TimeZone {
TimeZone(inner: TimeZoneCache.cache.autoupdatingCurrent())
}
/// The default time zone, settable via ObjC but not available in Swift API (because it's global mutable state).
/// The default time zone is not autoupdating, but it can change at any time when the ObjC `setDefaultTimeZone:` API is called.
package static var `default` : TimeZone! {
get {
TimeZoneCache.cache.default
}
set {
TimeZoneCache.cache.setDefault(newValue)
}
}
#if !FOUNDATION_FRAMEWORK
@_spi(SwiftCorelibsFoundation) public static var _default : TimeZone! {
get {
TimeZone.default
}
set {
TimeZone.default = newValue
}
}
#endif
// MARK: -
//
/// The geopolitical region identifier that identifies the time zone.
public var identifier: String {
_tz.identifier
}
/// Used for compatibility with `NSTimeZone`.
internal var data: Data? {
_tz.data
}
/// The current difference in seconds between the time zone and Greenwich Mean Time.
///
/// - parameter date: The date to use for the calculation. The default value is the current date.
public func secondsFromGMT(for date: Date = Date()) -> Int {
_tz.secondsFromGMT(for: date)
}
internal func rawAndDaylightSavingTimeOffset(for date: Date, repeatedTimePolicy: TimeZone.DaylightSavingTimePolicy = .former, skippedTimePolicy: TimeZone.DaylightSavingTimePolicy = .former) -> (rawOffset: Int, daylightSavingOffset: TimeInterval) {
_tz.rawAndDaylightSavingTimeOffset(for: date, repeatedTimePolicy: repeatedTimePolicy, skippedTimePolicy: skippedTimePolicy)
}
/// Returns the abbreviation for the time zone at a given date.
///
/// Note that the abbreviation may be different at different dates. For example, during daylight saving time the US/Eastern time zone has an abbreviation of "EDT." At other times, its abbreviation is "EST."
/// - parameter date: The date to use for the calculation. The default value is the current date.
public func abbreviation(for date: Date = Date()) -> String? {
_tz.abbreviation(for: date)
}
/// Returns a Boolean value that indicates whether the receiver uses daylight saving time at a given date.
///
/// - parameter date: The date to use for the calculation. The default value is the current date.
public func isDaylightSavingTime(for date: Date = Date()) -> Bool {
_tz.isDaylightSavingTime(for: date)
}
/// Returns the daylight saving time offset for a given date.
///
/// - parameter date: The date to use for the calculation. The default value is the current date.
public func daylightSavingTimeOffset(for date: Date = Date()) -> TimeInterval {
_tz.daylightSavingTimeOffset(for: date)
}
/// Returns the next daylight saving time transition after a given date.
///
/// - parameter date: A date.
/// - returns: The next daylight saving time transition after `date`. Depending on the time zone, this function may return a change of the time zone's offset from GMT. Returns `nil` if the time zone of the receiver does not observe daylight savings time as of `date`.
public func nextDaylightSavingTimeTransition(after date: Date) -> Date? {
_tz.nextDaylightSavingTimeTransition(after: date)
}
/// Returns the mapping of abbreviations to time zone identifiers.
public static var abbreviationDictionary : [String : String] {
get {
// TODO: We may want to consider changing this for Essentials, as it returns a list of abbreviations which only Internationalization supports
TimeZoneCache.cache.timeZoneAbbreviations()
}
set {
TimeZoneCache.cache.setTimeZoneAbbreviations(newValue)
}
}
/// Returns the date of the next (after the current instant) daylight saving time transition for the time zone. Depending on the time zone, the value of this property may represent a change of the time zone's offset from GMT. Returns `nil` if the time zone does not currently observe daylight saving time.
public var nextDaylightSavingTimeTransition: Date? {
_tz.nextDaylightSavingTimeTransition(after: Date.now)
}
/// Returns the name of the receiver localized for a given locale.
public func localizedName(for style: TimeZone.NameStyle, locale: Locale?) -> String? {
_tz.localizedName(for: style, locale: locale)
}
@_alwaysEmitIntoClient @_disfavoredOverload
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
public static var gmt: TimeZone { TimeZone(secondsFromGMT: 0)! }
package static let cldrKeywordKey = ICUCLDRKey("tz")
package static let legacyKeywordKey = ICULegacyKey("timezone")
// MARK: -
public func hash(into hasher: inout Hasher) {
_tz.hash(into: &hasher)
}
public static func ==(lhs: TimeZone, rhs: TimeZone) -> Bool {
// Autoupdating is only ever equal to autoupdating. Other time zones compare their values.
if lhs._tz.isAutoupdating && rhs._tz.isAutoupdating {
return true
} else if lhs._tz.isAutoupdating || rhs._tz.isAutoupdating {
return false
} else {
// If both have been initialized with data, then use it for comparison. Otherwise ignore it. Swift TimeZones, including autoupdating and current, do not set it.
if let lhsData = lhs.data, let rhsData = rhs.data {
return lhs.identifier == rhs.identifier && lhsData == rhsData
} else {
// Compare based on identifier only
return lhs.identifier == rhs.identifier
}
}
}
}
/// Constants you use to specify a style when presenting time zone names.
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
extension TimeZone {
#if !FOUNDATION_FRAMEWORK
/// Enum you use to specify different name style of a time zone.
public enum NameStyle : Int, Sendable {
/// Specifies a standard name style. For example, Central Standard Time for Central Time.
case standard
/// Specifies a short name style. For example, CST for Central Time.
case shortStandard
/// Specifies a daylight saving name style. For example, Central Daylight Time for Central Time.
case daylightSaving
/// Specifies a short daylight saving name style. For example, CDT for Central Time.
case shortDaylightSaving
/// Specifies a generic name style. For example, Central Time for Central Time.
case generic
/// Specifies a generic time zone name. For example, CT for Central Time.
case shortGeneric
}
#else
public typealias NameStyle = NSTimeZone.NameStyle
#endif
}
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
extension TimeZone : CustomStringConvertible, CustomDebugStringConvertible, CustomReflectable {
public var customMirror : Mirror {
let c: [(label: String?, value: Any)] = [
("identifier", identifier),
("tz", _tz),
("abbreviation", abbreviation() as Any),
("secondsFromGMT", secondsFromGMT()),
("isDaylightSavingTime", isDaylightSavingTime()),
]
return Mirror(self, children: c, displayStyle: Mirror.DisplayStyle.struct)
}
public var description: String {
return _tz.debugDescription
}
public var debugDescription : String {
return _tz.debugDescription
}
}
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
extension TimeZone : Codable {
private enum CodingKeys : Int, CodingKey {
case identifier
case autoupdating
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let autoupdating = try container.decodeIfPresent(Bool.self, forKey: .autoupdating), autoupdating {
self = TimeZone.autoupdatingCurrent
return
}
let identifier = try container.decode(String.self, forKey: .identifier)
guard let timeZone = TimeZone(identifier: identifier) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath,
debugDescription: "Invalid TimeZone identifier."))
}
self = timeZone
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
// Even if we are autoupdatingCurrent, encode the identifier for backward compatibility
try container.encode(self.identifier, forKey: .identifier)
if _tz.isAutoupdating {
try container.encode(true, forKey: .autoupdating)
}
}
}
// MARK: - Bridging
#if FOUNDATION_FRAMEWORK
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
extension TimeZone : ReferenceConvertible, _ObjectiveCBridgeable {
public typealias ReferenceType = NSTimeZone
@_semantics("convertToObjectiveC")
public func _bridgeToObjectiveC() -> NSTimeZone {
_tz.bridgeToNSTimeZone()
}
public static func _forceBridgeFromObjectiveC(_ input: NSTimeZone, result: inout TimeZone?) {
if !_conditionallyBridgeFromObjectiveC(input, result: &result) {
fatalError("Unable to bridge \(_ObjectiveCType.self) to \(self)")
}
}
public static func _conditionallyBridgeFromObjectiveC(_ input: NSTimeZone, result: inout TimeZone?) -> Bool {
result = TimeZone(reference: input)
return true
}
@_effects(readonly)
public static func _unconditionallyBridgeFromObjectiveC(_ source: NSTimeZone?) -> TimeZone {
var result: TimeZone?
_forceBridgeFromObjectiveC(source!, result: &result)
return result!
}
}
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
extension NSTimeZone : _HasCustomAnyHashableRepresentation {
// Must be @nonobjc to avoid infinite recursion during bridging.
@nonobjc
public func _toCustomAnyHashable() -> AnyHashable? {
return AnyHashable(self as TimeZone)
}
}
#endif
extension TimeZone {
// Specifies which occurrence of time to use when it falls into the repeated hour or the skipped hour during DST transition day
// For the skipped time frame when transitioning into DST (e.g. 1:00 - 3:00 AM for PDT), use `.former` if asking for the occurrence when DST hasn't happened yet
// For the repeated time frame when DST ends (e.g. 1:00 - 2:00 AM for PDT), use .former if asking for the instance before turning back the clock
package enum DaylightSavingTimePolicy {
case former
case latter
}
}
extension TimeZone {
internal static func dataFromTZFile(_ name: String) -> Data {
#if NO_TZFILE
return Data()
#else
let path = TZDIR + "/" + name
guard !path.contains("..") else {
// No good reason for .. to be present anywhere in the path
return Data()
}
return (try? Data(contentsOfFile: path)) ?? Data()
#endif
}
internal static func resetSystemTimeZone() -> TimeZone? {
let oldTimeZone = TimeZoneCache.cache.reset()
// Also reset the calendar cache, since the current calendar uses the current time zone
CalendarCache.cache.reset()
return oldTimeZone
}
#if !FOUNDATION_FRAMEWORK
@_spi(SwiftCorelibsFoundation) public static func _dataFromTZFile(_ name: String) -> Data {
TimeZone.dataFromTZFile(name)
}
@_spi(SwiftCorelibsFoundation) public static func _resetSystemTimeZone() -> TimeZone? {
TimeZone.resetSystemTimeZone()
}
#endif
}