Skip to content

Commit 245ec37

Browse files
authored
Merge pull request #1575 from swiftlang/automerge/merge-main-2025-11-05_09-05
Merge `main` into `future`
2 parents f819caa + 1763514 commit 245ec37

File tree

5 files changed

+160
-70
lines changed

5 files changed

+160
-70
lines changed

Benchmarks/Benchmarks/Internationalization/BenchmarkCalendar.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ import FoundationInternationalization
2020
import Foundation
2121
#endif
2222

23+
#if FOUNDATION_FRAMEWORK
24+
// FOUNDATION_FRAMEWORK has a scheme per benchmark file, so only include one benchmark here.
25+
let benchmarks = {
26+
calendarBenchmarks()
27+
}
28+
#endif
29+
2330
func calendarBenchmarks() {
2431

2532
Benchmark.defaultConfiguration.maxIterations = 1_000
@@ -130,7 +137,6 @@ func calendarBenchmarks() {
130137
}
131138

132139
// MARK: - Allocations
133-
134140
let reference = Date(timeIntervalSince1970: 1474666555.0) //2016-09-23T14:35:55-0700
135141

136142
let allocationsConfiguration = Benchmark.Configuration(
@@ -208,7 +214,6 @@ func calendarBenchmarks() {
208214
assert(identifier == "en_US")
209215
}
210216
}
211-
212217
// MARK: - Identifiers
213218

214219
Benchmark("identifierFromComponents", configuration: .init(scalingFactor: .mega)) { benchmark in

Benchmarks/Benchmarks/Internationalization/BenchmarkLocale.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ import FoundationInternationalization
2020
import Foundation
2121
#endif
2222

23+
#if FOUNDATION_FRAMEWORK
24+
// FOUNDATION_FRAMEWORK has a scheme per benchmark file, so only include one benchmark here.
25+
let benchmarks = {
26+
localeBenchmarks()
27+
}
28+
#endif
29+
2330
func localeBenchmarks() {
2431
Benchmark.defaultConfiguration.maxIterations = 1_000
2532
Benchmark.defaultConfiguration.maxDuration = .seconds(3)
@@ -57,4 +64,3 @@ func localeBenchmarks() {
5764
}
5865
}
5966
}
60-
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2022-2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Benchmark
14+
import func Benchmark.blackHole
15+
16+
#if os(macOS) && USE_PACKAGE
17+
import FoundationEssentials
18+
import FoundationInternationalization
19+
#else
20+
import Foundation
21+
#endif
22+
23+
#if FOUNDATION_FRAMEWORK
24+
// FOUNDATION_FRAMEWORK has a scheme per benchmark file, so only include one benchmark here.
25+
let benchmarks = {
26+
timeZoneBenchmarks()
27+
}
28+
#endif
29+
30+
let testDates = {
31+
var now = Date.now
32+
var dates: [Date] = []
33+
for i in 0...10000 {
34+
dates.append(Date(timeInterval: Double(i * 3600), since: now))
35+
}
36+
return dates
37+
}()
38+
39+
func timeZoneBenchmarks() {
40+
41+
Benchmark.defaultConfiguration.maxIterations = 1_000
42+
Benchmark.defaultConfiguration.maxDuration = .seconds(3)
43+
Benchmark.defaultConfiguration.scalingFactor = .kilo
44+
Benchmark.defaultConfiguration.metrics = [.cpuTotal, .mallocCountTotal, .throughput, .peakMemoryResident]
45+
46+
guard let t = TimeZone(identifier: "America/Los_Angeles") else {
47+
fatalError("unexpected failure when creating time zone")
48+
}
49+
50+
Benchmark("secondsFromGMT", configuration: .init(scalingFactor: .mega)) { benchmark in
51+
for d in testDates {
52+
let s = t.secondsFromGMT(for: d)
53+
blackHole(s)
54+
}
55+
}
56+
57+
Benchmark("creatingTimeZones", configuration: .init(scalingFactor: .mega)) { benchmark in
58+
for name in NSTimeZone.knownTimeZoneNames {
59+
let t = TimeZone(identifier: name)
60+
blackHole(t)
61+
}
62+
}
63+
64+
Benchmark("secondsFromGMT_manyTimeZones", configuration: .init(scalingFactor: .mega)) { benchmark in
65+
for name in NSTimeZone.knownTimeZoneNames {
66+
let t = TimeZone(identifier: name)!
67+
for d in testDates {
68+
let s = t.secondsFromGMT(for: d)
69+
blackHole(s)
70+
}
71+
blackHole(t)
72+
}
73+
}
74+
}
75+
76+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import Benchmark
22

3+
4+
#if os(macOS) && USE_PACKAGE
35
let benchmarks = {
46
calendarBenchmarks()
57
localeBenchmarks()
8+
timeZoneBenchmarks()
69
}
10+
#endif

Sources/FoundationInternationalization/TimeZone/TimeZone_ICU.swift

Lines changed: 66 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)