Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ import FoundationInternationalization
import Foundation
#endif

#if FOUNDATION_FRAMEWORK
// FOUNDATION_FRAMEWORK has a scheme per benchmark file, so only include one benchmark here.
let benchmarks = {
calendarBenchmarks()
}
#endif

func calendarBenchmarks() {

Benchmark.defaultConfiguration.maxIterations = 1_000
Expand Down Expand Up @@ -130,7 +137,6 @@ func calendarBenchmarks() {
}

// MARK: - Allocations

let reference = Date(timeIntervalSince1970: 1474666555.0) //2016-09-23T14:35:55-0700

let allocationsConfiguration = Benchmark.Configuration(
Expand Down Expand Up @@ -208,7 +214,6 @@ func calendarBenchmarks() {
assert(identifier == "en_US")
}
}

// MARK: - Identifiers

Benchmark("identifierFromComponents", configuration: .init(scalingFactor: .mega)) { benchmark in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ import FoundationInternationalization
import Foundation
#endif

#if FOUNDATION_FRAMEWORK
// FOUNDATION_FRAMEWORK has a scheme per benchmark file, so only include one benchmark here.
let benchmarks = {
localeBenchmarks()
}
#endif

func localeBenchmarks() {
Benchmark.defaultConfiguration.maxIterations = 1_000
Benchmark.defaultConfiguration.maxDuration = .seconds(3)
Expand Down Expand Up @@ -57,4 +64,3 @@ func localeBenchmarks() {
}
}
}

76 changes: 76 additions & 0 deletions Benchmarks/Benchmarks/Internationalization/BenchmarkTimeZone.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2022-2023 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
//
//===----------------------------------------------------------------------===//

import Benchmark
import func Benchmark.blackHole

#if os(macOS) && USE_PACKAGE
import FoundationEssentials
import FoundationInternationalization
#else
import Foundation
#endif

#if FOUNDATION_FRAMEWORK
// FOUNDATION_FRAMEWORK has a scheme per benchmark file, so only include one benchmark here.
let benchmarks = {
timeZoneBenchmarks()
}
#endif

let testDates = {
var now = Date.now
var dates: [Date] = []
for i in 0...10000 {
dates.append(Date(timeInterval: Double(i * 3600), since: now))
}
return dates
}()

func timeZoneBenchmarks() {

Benchmark.defaultConfiguration.maxIterations = 1_000
Benchmark.defaultConfiguration.maxDuration = .seconds(3)
Benchmark.defaultConfiguration.scalingFactor = .kilo
Benchmark.defaultConfiguration.metrics = [.cpuTotal, .mallocCountTotal, .throughput, .peakMemoryResident]

guard let t = TimeZone(identifier: "America/Los_Angeles") else {
fatalError("unexpected failure when creating time zone")
}

Benchmark("secondsFromGMT", configuration: .init(scalingFactor: .mega)) { benchmark in
for d in testDates {
let s = t.secondsFromGMT(for: d)
blackHole(s)
}
}

Benchmark("creatingTimeZones", configuration: .init(scalingFactor: .mega)) { benchmark in
for name in NSTimeZone.knownTimeZoneNames {
let t = TimeZone(identifier: name)
blackHole(t)
}
}

Benchmark("secondsFromGMT_manyTimeZones", configuration: .init(scalingFactor: .mega)) { benchmark in
for name in NSTimeZone.knownTimeZoneNames {
let t = TimeZone(identifier: name)!
for d in testDates {
let s = t.secondsFromGMT(for: d)
blackHole(s)
}
blackHole(t)
}
}
}


Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import Benchmark


#if os(macOS) && USE_PACKAGE
let benchmarks = {
calendarBenchmarks()
localeBenchmarks()
timeZoneBenchmarks()
}
#endif
133 changes: 66 additions & 67 deletions Sources/FoundationInternationalization/TimeZone/TimeZone_ICU.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ internal final class _TimeZoneICU: _TimeZoneProtocol, Sendable {
init?(secondsFromGMT: Int) {
fatalError("Unexpected init")
}


// This is safe because it's only mutated at deinit time
nonisolated(unsafe) private let _timeZone : UnsafePointer<UTimeZone?>

// 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
struct State : @unchecked Sendable {
/// Access must be serialized
Expand Down Expand Up @@ -82,6 +85,9 @@ internal final class _TimeZoneICU: _TimeZoneProtocol, Sendable {
guard let c = $0.calendar(identifier) else { return }
ucal_close(c)
}

let mutableT = UnsafeMutablePointer(mutating: _timeZone)
uatimezone_close(mutableT)
}

required init?(identifier: String) {
Expand All @@ -99,6 +105,20 @@ internal final class _TimeZoneICU: _TimeZoneProtocol, Sendable {
}
}

var status = U_ZERO_ERROR
let timeZone : UnsafeMutablePointer<UTimeZone?>? = Array(identifier.utf16).withUnsafeBufferPointer {
let uatimezone = uatimezone_open($0.baseAddress, Int32($0.count), &status)
guard status.isSuccess else {
return nil
}
return uatimezone
}

guard let timeZone else {
return nil
}

self._timeZone = UnsafePointer(timeZone)
self.name = name
lock = LockedState(initialState: State())
}
Expand All @@ -113,27 +133,15 @@ internal final class _TimeZoneICU: _TimeZoneProtocol, Sendable {
}

func secondsFromGMT(for date: Date) -> Int {
return lock.withLock {
let udate = date.udate
guard let c = $0.calendar(identifier) else {
return 0
}

var status = U_ZERO_ERROR
ucal_setMillis(c, udate, &status)

let zoneOffset = ucal_get(c, UCAL_ZONE_OFFSET, &status)
guard status.isSuccess else {
return 0
}

status = U_ZERO_ERROR
let dstOffset = ucal_get(c, UCAL_DST_OFFSET, &status)
guard status.isSuccess else {
return 0
}
return Int((zoneOffset + dstOffset) / 1000)
var rawOffset: Int32 = 0
var dstOffset: Int32 = 0
var status: UErrorCode = U_ZERO_ERROR
uatimezone_getOffset(_timeZone, date.udate, 0, &rawOffset, &dstOffset, &status)
guard status.checkSuccessAndLogError("error getting uatimezone offset") else {
return 0
}

return Int((rawOffset + dstOffset) / 1000)
}

func abbreviation(for date: Date) -> String? {
Expand All @@ -149,60 +157,51 @@ internal final class _TimeZoneICU: _TimeZoneProtocol, Sendable {
}

func daylightSavingTimeOffset(for date: Date) -> TimeInterval {
lock.withLock {
let udate = date.udate

guard let c = $0.calendar(identifier) else { return 0.0 }
var status = U_ZERO_ERROR
ucal_setMillis(c, udate, &status)
let offset = ucal_get(c, UCAL_DST_OFFSET, &status)
if status.isSuccess {
return TimeInterval(Double(offset) / 1000.0)
} else {
return 0.0
}
var rawOffset_unused: Int32 = 0
var dstOffset: Int32 = 0
var status = U_ZERO_ERROR
uatimezone_getOffset(_timeZone, date.udate, 0, &rawOffset_unused, &dstOffset, &status)
if status.isSuccess {
return TimeInterval(Double(dstOffset) / 1000.0)
} else {
return 0.0
}
}

func nextDaylightSavingTimeTransition(after date: Date) -> Date? {
lock.withLock {
guard let c = $0.calendar(identifier) else { return nil }
return Self.nextDaylightSavingTimeTransition(forLocked: c, startingAt: date, limit: Date.validCalendarRange.upperBound)
}
}
var status = U_ZERO_ERROR
var answer = UDate(0.0)
let success = uatimezone_getTimeZoneTransitionDate(_timeZone, date.udate, UCAL_TZ_TRANSITION_NEXT, &answer, &status)

func rawAndDaylightSavingTimeOffset(for date: Date, repeatedTimePolicy: TimeZone.DaylightSavingTimePolicy = .former, skippedTimePolicy: TimeZone.DaylightSavingTimePolicy = .former) -> (rawOffset: Int, daylightSavingOffset: TimeInterval) {
return lock.withLock {
guard let calendar = $0.calendar(identifier) else { return (0, 0) }
var rawOffset: Int32 = 0
var dstOffset: Int32 = 0
var status = U_ZERO_ERROR
let origMillis = ucal_getMillis(calendar, &status)
defer {
ucal_setMillis(calendar, origMillis, &status)
}
ucal_setMillis(calendar, date.udate, &status)

let icuDuplicatedTime: UTimeZoneLocalOption
switch repeatedTimePolicy {
case .former:
icuDuplicatedTime = UCAL_TZ_LOCAL_FORMER
case .latter:
icuDuplicatedTime = UCAL_TZ_LOCAL_LATTER
}
let limit = Date.validCalendarRange.upperBound
guard (success != 0) && status.isSuccess && answer < limit.udate else {
return nil
}
return Date(udate: answer)
}

let icuSkippedTime: UTimeZoneLocalOption
switch skippedTimePolicy {
case .former:
icuSkippedTime = UCAL_TZ_LOCAL_FORMER
case .latter:
icuSkippedTime = UCAL_TZ_LOCAL_LATTER
}

ucal_getTimeZoneOffsetFromLocal(calendar, icuSkippedTime, icuDuplicatedTime, &rawOffset, &dstOffset, &status)
func rawAndDaylightSavingTimeOffset(for date: Date, repeatedTimePolicy: TimeZone.DaylightSavingTimePolicy = .former, skippedTimePolicy: TimeZone.DaylightSavingTimePolicy = .former) -> (rawOffset: Int, daylightSavingOffset: TimeInterval) {
var rawOffset: Int32 = 0
var dstOffset: Int32 = 0
var status = U_ZERO_ERROR
let icuDuplicatedTime: UTimeZoneLocalOption
switch repeatedTimePolicy {
case .former:
icuDuplicatedTime = UCAL_TZ_LOCAL_FORMER
case .latter:
icuDuplicatedTime = UCAL_TZ_LOCAL_LATTER
}

return (Int(rawOffset / 1000), TimeInterval(dstOffset / 1000))
let icuSkippedTime: UTimeZoneLocalOption
switch skippedTimePolicy {
case .former:
icuSkippedTime = UCAL_TZ_LOCAL_FORMER
case .latter:
icuSkippedTime = UCAL_TZ_LOCAL_LATTER
}

uatimezone_getOffsetFromLocal(_timeZone, icuSkippedTime, icuDuplicatedTime, date.udate, &rawOffset, &dstOffset, &status)
return (Int(rawOffset / 1000), TimeInterval(dstOffset / 1000))
}

func localizedName(for style: TimeZone.NameStyle, locale: Locale?) -> String? {
Expand Down