mirror of
https://github.com/apple/swift-foundation.git
synced 2025-05-26 08:01:20 +08:00
135 lines
5.2 KiB
Swift
135 lines
5.2 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 FOUNDATION_FRAMEWORK
|
|
@_implementationOnly import _ForSwiftFoundation
|
|
import CoreFoundation
|
|
#endif
|
|
|
|
/// Singleton which listens for notifications about preference changes for Calendar and holds cached singletons for the current locale, calendar, and time zone.
|
|
struct CalendarCache : Sendable {
|
|
|
|
// MARK: - Concrete Classes
|
|
|
|
// _CalendarICU, if present
|
|
static var calendarICUClass: _CalendarProtocol.Type = {
|
|
#if FOUNDATION_FRAMEWORK && canImport(FoundationICU)
|
|
_CalendarICU.self
|
|
#else
|
|
if let name = _typeByName("FoundationInternationalization._CalendarICU"), let t = name as? _CalendarProtocol.Type {
|
|
return t
|
|
} else {
|
|
// Use the default gregorian class
|
|
return _CalendarGregorian.self
|
|
}
|
|
#endif
|
|
}()
|
|
|
|
// MARK: - State
|
|
|
|
struct State : Sendable {
|
|
// If nil, the calendar has been invalidated and will be created next time State.current() is called
|
|
private var currentCalendar: (any _CalendarProtocol)?
|
|
private var autoupdatingCurrentCalendar: _CalendarAutoupdating?
|
|
private var fixedCalendars: [Calendar.Identifier: any _CalendarProtocol] = [:]
|
|
private var noteCount = -1
|
|
private var wasResetManually = false
|
|
|
|
mutating func check() {
|
|
#if FOUNDATION_FRAMEWORK
|
|
// On Darwin we listen for certain distributed notifications to reset the current Calendar.
|
|
let newNoteCount = _CFLocaleGetNoteCount() + _CFTimeZoneGetNoteCount() + Int(_CFCalendarGetMidnightNoteCount())
|
|
#else
|
|
let newNoteCount = 1
|
|
#endif
|
|
if newNoteCount != noteCount || wasResetManually {
|
|
// rdar://102017659
|
|
// Don't create `currentCalendar` here to avoid deadlocking when retrieving a fixed
|
|
// calendar. Creating the current calendar gets the current locale, decodes a plist
|
|
// from CFPreferences, and may call +[NSDate initialize] on a separate thread. This
|
|
// leads to a deadlock if we are also initializing a class on the current thread
|
|
currentCalendar = nil
|
|
fixedCalendars = [:]
|
|
|
|
noteCount = newNoteCount
|
|
wasResetManually = false
|
|
}
|
|
}
|
|
|
|
mutating func current() -> any _CalendarProtocol {
|
|
check()
|
|
if let currentCalendar {
|
|
return currentCalendar
|
|
} else {
|
|
let id = Locale.current._calendarIdentifier
|
|
let calendar = CalendarCache.calendarICUClass.init(identifier: id, timeZone: nil, locale: Locale.current, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil)
|
|
currentCalendar = calendar
|
|
return calendar
|
|
}
|
|
}
|
|
|
|
mutating func autoupdatingCurrent() -> any _CalendarProtocol {
|
|
if let autoupdatingCurrentCalendar {
|
|
return autoupdatingCurrentCalendar
|
|
} else {
|
|
let calendar = _CalendarAutoupdating()
|
|
autoupdatingCurrentCalendar = calendar
|
|
return calendar
|
|
}
|
|
}
|
|
|
|
mutating func fixed(_ id: Calendar.Identifier) -> any _CalendarProtocol {
|
|
check()
|
|
if let cached = fixedCalendars[id] {
|
|
return cached
|
|
} else {
|
|
let new = CalendarCache.calendarICUClass.init(identifier: id, timeZone: nil, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil)
|
|
fixedCalendars[id] = new
|
|
return new
|
|
}
|
|
}
|
|
|
|
mutating func reset() {
|
|
wasResetManually = true
|
|
}
|
|
}
|
|
|
|
let lock: LockedState<State>
|
|
|
|
static let cache = CalendarCache()
|
|
|
|
fileprivate init() {
|
|
lock = LockedState(initialState: State())
|
|
}
|
|
|
|
func reset() {
|
|
lock.withLock { $0.reset() }
|
|
}
|
|
|
|
var current: any _CalendarProtocol {
|
|
lock.withLock { $0.current() }
|
|
}
|
|
|
|
var autoupdatingCurrent: any _CalendarProtocol {
|
|
lock.withLock { $0.autoupdatingCurrent() }
|
|
}
|
|
|
|
func fixed(_ id: Calendar.Identifier) -> any _CalendarProtocol {
|
|
lock.withLock { $0.fixed(id) }
|
|
}
|
|
|
|
func fixed(identifier: Calendar.Identifier, locale: Locale?, timeZone: TimeZone?, firstWeekday: Int?, minimumDaysInFirstWeek: Int?, gregorianStartDate: Date?) -> any _CalendarProtocol {
|
|
// Note: Only the ObjC NSCalendar initWithCoder supports gregorian start date values. For Swift it is always nil.
|
|
return CalendarCache.calendarICUClass.init(identifier: identifier, timeZone: timeZone, locale: locale, firstWeekday: firstWeekday, minimumDaysInFirstWeek: minimumDaysInFirstWeek, gregorianStartDate: gregorianStartDate)
|
|
}
|
|
}
|