@@ -43,7 +43,10 @@ internal final class _TimeZoneICU: _TimeZoneProtocol, Sendable {
4343 init ? ( secondsFromGMT: Int ) {
4444 fatalError ( " Unexpected init " )
4545 }
46-
46+
47+ // This is safe because it's only mutated at deinit time
48+ nonisolated ( unsafe) private let _timeZone : UnsafePointer < UTimeZone ? >
49+
4750 // This type is safely sendable because it is guarded by a lock in _TimeZoneICU and we never vend it outside of the lock so it can only ever be accessed from within the lock
4851 struct State : @unchecked Sendable {
4952 /// Access must be serialized
@@ -82,6 +85,9 @@ internal final class _TimeZoneICU: _TimeZoneProtocol, Sendable {
8285 guard let c = $0. calendar ( identifier) else { return }
8386 ucal_close ( c)
8487 }
88+
89+ let mutableT = UnsafeMutablePointer ( mutating: _timeZone)
90+ uatimezone_close ( mutableT)
8591 }
8692
8793 required init ? ( identifier: String ) {
@@ -99,6 +105,20 @@ internal final class _TimeZoneICU: _TimeZoneProtocol, Sendable {
99105 }
100106 }
101107
108+ var status = U_ZERO_ERROR
109+ let timeZone : UnsafeMutablePointer < UTimeZone ? > ? = Array ( identifier. utf16) . withUnsafeBufferPointer {
110+ let uatimezone = uatimezone_open ( $0. baseAddress, Int32 ( $0. count) , & status)
111+ guard status. isSuccess else {
112+ return nil
113+ }
114+ return uatimezone
115+ }
116+
117+ guard let timeZone else {
118+ return nil
119+ }
120+
121+ self . _timeZone = UnsafePointer ( timeZone)
102122 self . name = name
103123 lock = LockedState ( initialState: State ( ) )
104124 }
@@ -113,27 +133,15 @@ internal final class _TimeZoneICU: _TimeZoneProtocol, Sendable {
113133 }
114134
115135 func secondsFromGMT( for date: Date ) -> Int {
116- return lock. withLock {
117- let udate = date. udate
118- guard let c = $0. calendar ( identifier) else {
119- return 0
120- }
121-
122- var status = U_ZERO_ERROR
123- ucal_setMillis ( c, udate, & status)
124-
125- let zoneOffset = ucal_get ( c, UCAL_ZONE_OFFSET, & status)
126- guard status. isSuccess else {
127- return 0
128- }
129-
130- status = U_ZERO_ERROR
131- let dstOffset = ucal_get ( c, UCAL_DST_OFFSET, & status)
132- guard status. isSuccess else {
133- return 0
134- }
135- return Int ( ( zoneOffset + dstOffset) / 1000 )
136+ var rawOffset : Int32 = 0
137+ var dstOffset : Int32 = 0
138+ var status : UErrorCode = U_ZERO_ERROR
139+ uatimezone_getOffset ( _timeZone, date. udate, 0 , & rawOffset, & dstOffset, & status)
140+ guard status. checkSuccessAndLogError ( " error getting uatimezone offset " ) else {
141+ return 0
136142 }
143+
144+ return Int ( ( rawOffset + dstOffset) / 1000 )
137145 }
138146
139147 func abbreviation( for date: Date ) -> String ? {
@@ -149,60 +157,51 @@ internal final class _TimeZoneICU: _TimeZoneProtocol, Sendable {
149157 }
150158
151159 func daylightSavingTimeOffset( for date: Date ) -> TimeInterval {
152- lock. withLock {
153- let udate = date. udate
154-
155- guard let c = $0. calendar ( identifier) else { return 0.0 }
156- var status = U_ZERO_ERROR
157- ucal_setMillis ( c, udate, & status)
158- let offset = ucal_get ( c, UCAL_DST_OFFSET, & status)
159- if status. isSuccess {
160- return TimeInterval ( Double ( offset) / 1000.0 )
161- } else {
162- return 0.0
163- }
160+ var rawOffset_unused : Int32 = 0
161+ var dstOffset : Int32 = 0
162+ var status = U_ZERO_ERROR
163+ uatimezone_getOffset ( _timeZone, date. udate, 0 , & rawOffset_unused, & dstOffset, & status)
164+ if status. isSuccess {
165+ return TimeInterval ( Double ( dstOffset) / 1000.0 )
166+ } else {
167+ return 0.0
164168 }
165169 }
166170
167171 func nextDaylightSavingTimeTransition( after date: Date ) -> Date ? {
168- lock. withLock {
169- guard let c = $0. calendar ( identifier) else { return nil }
170- return Self . nextDaylightSavingTimeTransition ( forLocked: c, startingAt: date, limit: Date . validCalendarRange. upperBound)
171- }
172- }
172+ var status = U_ZERO_ERROR
173+ var answer = UDate ( 0.0 )
174+ let success = uatimezone_getTimeZoneTransitionDate ( _timeZone, date. udate, UCAL_TZ_TRANSITION_NEXT, & answer, & status)
173175
174- func rawAndDaylightSavingTimeOffset( for date: Date , repeatedTimePolicy: TimeZone . DaylightSavingTimePolicy = . former, skippedTimePolicy: TimeZone . DaylightSavingTimePolicy = . former) -> ( rawOffset: Int , daylightSavingOffset: TimeInterval ) {
175- return lock. withLock {
176- guard let calendar = $0. calendar ( identifier) else { return ( 0 , 0 ) }
177- var rawOffset : Int32 = 0
178- var dstOffset : Int32 = 0
179- var status = U_ZERO_ERROR
180- let origMillis = ucal_getMillis ( calendar, & status)
181- defer {
182- ucal_setMillis ( calendar, origMillis, & status)
183- }
184- ucal_setMillis ( calendar, date. udate, & status)
185-
186- let icuDuplicatedTime : UTimeZoneLocalOption
187- switch repeatedTimePolicy {
188- case . former:
189- icuDuplicatedTime = UCAL_TZ_LOCAL_FORMER
190- case . latter:
191- icuDuplicatedTime = UCAL_TZ_LOCAL_LATTER
192- }
176+ let limit = Date . validCalendarRange. upperBound
177+ guard ( success != 0 ) && status. isSuccess && answer < limit. udate else {
178+ return nil
179+ }
180+ return Date ( udate: answer)
181+ }
193182
194- let icuSkippedTime : UTimeZoneLocalOption
195- switch skippedTimePolicy {
196- case . former:
197- icuSkippedTime = UCAL_TZ_LOCAL_FORMER
198- case . latter:
199- icuSkippedTime = UCAL_TZ_LOCAL_LATTER
200- }
201-
202- ucal_getTimeZoneOffsetFromLocal ( calendar, icuSkippedTime, icuDuplicatedTime, & rawOffset, & dstOffset, & status)
183+ func rawAndDaylightSavingTimeOffset( for date: Date , repeatedTimePolicy: TimeZone . DaylightSavingTimePolicy = . former, skippedTimePolicy: TimeZone . DaylightSavingTimePolicy = . former) -> ( rawOffset: Int , daylightSavingOffset: TimeInterval ) {
184+ var rawOffset : Int32 = 0
185+ var dstOffset : Int32 = 0
186+ var status = U_ZERO_ERROR
187+ let icuDuplicatedTime : UTimeZoneLocalOption
188+ switch repeatedTimePolicy {
189+ case . former:
190+ icuDuplicatedTime = UCAL_TZ_LOCAL_FORMER
191+ case . latter:
192+ icuDuplicatedTime = UCAL_TZ_LOCAL_LATTER
193+ }
203194
204- return ( Int ( rawOffset / 1000 ) , TimeInterval ( dstOffset / 1000 ) )
195+ let icuSkippedTime : UTimeZoneLocalOption
196+ switch skippedTimePolicy {
197+ case . former:
198+ icuSkippedTime = UCAL_TZ_LOCAL_FORMER
199+ case . latter:
200+ icuSkippedTime = UCAL_TZ_LOCAL_LATTER
205201 }
202+
203+ uatimezone_getOffsetFromLocal ( _timeZone, icuSkippedTime, icuDuplicatedTime, date. udate, & rawOffset, & dstOffset, & status)
204+ return ( Int ( rawOffset / 1000 ) , TimeInterval ( dstOffset / 1000 ) )
206205 }
207206
208207 func localizedName( for style: TimeZone . NameStyle , locale: Locale ? ) -> String ? {
0 commit comments