Skip to content

Commit 1f8d9d2

Browse files
committed
Adopt ISO8601FormatStyle on newer platforms
1 parent d604dd0 commit 1f8d9d2

File tree

2 files changed

+76
-50
lines changed

2 files changed

+76
-50
lines changed

Benchmarks/Benchmarks/OpenAPIRuntimeBenchmarks/Benchmarks.swift

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,35 +27,8 @@ let benchmarks = {
2727
maxIterations: 5
2828
)
2929
) { benchmark in
30-
let transcoder = ISO8601DateTranscoder()
30+
let transcoder = ISO8601DateTranscoder(formatStyle: .iso8601)
3131
benchmark.startMeasurement()
3232
for _ in benchmark.scaledIterations { blackHole(try transcoder.encode(.distantFuture)) }
3333
}
34-
35-
Benchmark(
36-
"ISO8601DateFormatter.string(from:)",
37-
configuration: Benchmark.Configuration(
38-
metrics: defaultMetrics,
39-
scalingFactor: .kilo,
40-
maxDuration: .seconds(10_000_000),
41-
maxIterations: 5
42-
)
43-
) { benchmark in
44-
let formatter = ISO8601DateFormatter()
45-
benchmark.startMeasurement()
46-
for _ in benchmark.scaledIterations { blackHole(formatter.string(from: .distantFuture)) }
47-
}
48-
49-
Benchmark(
50-
"Date.ISO8601Format(_:)",
51-
configuration: Benchmark.Configuration(
52-
metrics: defaultMetrics,
53-
scalingFactor: .kilo,
54-
maxDuration: .seconds(10_000_000),
55-
maxIterations: 5
56-
)
57-
) { benchmark in
58-
benchmark.startMeasurement()
59-
for _ in benchmark.scaledIterations { blackHole(Date.distantFuture.ISO8601Format()) }
60-
}
6134
}

Sources/OpenAPIRuntime/Conversion/Configuration.swift

Lines changed: 75 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,42 +25,85 @@ public protocol DateTranscoder: Sendable {
2525
}
2626

2727
/// A transcoder for dates encoded as an ISO-8601 string (in RFC 3339 format).
28-
public struct ISO8601DateTranscoder: DateTranscoder, @unchecked Sendable {
28+
public struct ISO8601DateTranscoder: DateTranscoder {
29+
/// A transcoder using `ISO8601DateFormatter` for encoding and decoding.
30+
private struct DateFormatterTranscoder: DateTranscoder, @unchecked Sendable {
31+
/// The lock protecting the formatter.
32+
private let lock: NSLock
33+
34+
/// The underlying date formatter.
35+
private let locked_formatter: ISO8601DateFormatter
36+
37+
init(options: ISO8601DateFormatter.Options? = nil) {
38+
let formatter = ISO8601DateFormatter()
39+
if let options { formatter.formatOptions = options }
40+
lock = NSLock()
41+
lock.name = "com.apple.swift-openapi-generator.runtime.ISO8601DateTranscoder"
42+
locked_formatter = formatter
43+
}
44+
45+
func encode(_ date: Date) throws -> String {
46+
lock.lock()
47+
defer { lock.unlock() }
48+
return locked_formatter.string(from: date)
49+
}
50+
51+
func decode(_ dateString: String) throws -> Date {
52+
lock.lock()
53+
defer { lock.unlock() }
54+
guard let date = locked_formatter.date(from: dateString) else {
55+
throw DecodingError.dataCorrupted(
56+
.init(codingPath: [], debugDescription: "Expected date string to be ISO8601-formatted.")
57+
)
58+
}
59+
return date
60+
}
61+
}
2962

30-
/// The lock protecting the formatter.
31-
private let lock: NSLock
63+
@available(macOS 12, *)
64+
/// A transcoder using `Date.ISO8601FormatStyle` for encoding and decoding which
65+
/// is significantly faster than `DateFormatterTranscoder`.
66+
private struct DateFormatStyleTranscoder: DateTranscoder {
67+
private let formatStyle: Date.ISO8601FormatStyle
3268

33-
/// The underlying date formatter.
34-
private let locked_formatter: ISO8601DateFormatter
69+
init(formatStyle: Date.ISO8601FormatStyle) {
70+
self.formatStyle = formatStyle
71+
}
72+
73+
func encode(_ date: Date) throws -> String {
74+
date.formatted(formatStyle)
75+
}
76+
77+
func decode(_ dateString: String) throws -> Date {
78+
try formatStyle.parse(dateString)
79+
}
80+
}
81+
82+
private let dateTranscoder: any DateTranscoder
3583

3684
/// Creates a new transcoder with the provided options.
3785
/// - Parameter options: Options to override the default ones. If you provide nil here, the default options
3886
/// are used.
87+
@available(macOS, deprecated: 12, message: "Use .init(formatStyle:) instead.")
3988
public init(options: ISO8601DateFormatter.Options? = nil) {
40-
let formatter = ISO8601DateFormatter()
41-
if let options { formatter.formatOptions = options }
42-
lock = NSLock()
43-
lock.name = "com.apple.swift-openapi-generator.runtime.ISO8601DateTranscoder"
44-
locked_formatter = formatter
89+
self.dateTranscoder = DateFormatterTranscoder(options: options)
90+
}
91+
92+
/// Creates a new transcoder with the given ISO8601 format style.
93+
/// - Parameter formatStyle: The format style for encoding/decoding dates. Defaults to `Date.ISO8601FormatStyle()`.
94+
@available(macOS 12.0, *)
95+
public init(formatStyle: Date.ISO8601FormatStyle) {
96+
self.dateTranscoder = DateFormatStyleTranscoder(formatStyle: formatStyle)
4597
}
4698

4799
/// Creates and returns an ISO 8601 formatted string representation of the specified date.
48100
public func encode(_ date: Date) throws -> String {
49-
lock.lock()
50-
defer { lock.unlock() }
51-
return locked_formatter.string(from: date)
101+
try self.dateTranscoder.encode(date)
52102
}
53103

54104
/// Creates and returns a date object from the specified ISO 8601 formatted string representation.
55105
public func decode(_ dateString: String) throws -> Date {
56-
lock.lock()
57-
defer { lock.unlock() }
58-
guard let date = locked_formatter.date(from: dateString) else {
59-
throw DecodingError.dataCorrupted(
60-
.init(codingPath: [], debugDescription: "Expected date string to be ISO8601-formatted.")
61-
)
62-
}
63-
return date
106+
try self.dateTranscoder.decode(dateString)
64107
}
65108
}
66109

@@ -70,7 +113,17 @@ extension DateTranscoder where Self == ISO8601DateTranscoder {
70113

71114
/// A transcoder that transcodes dates as ISO-8601–formatted string (in RFC 3339 format) with fractional seconds.
72115
public static var iso8601WithFractionalSeconds: Self {
73-
ISO8601DateTranscoder(options: [.withInternetDateTime, .withFractionalSeconds])
116+
if #available(macOS 12, *) {
117+
let formatStyle = Date.ISO8601FormatStyle(
118+
dateSeparator: .dash,
119+
timeSeparator: .colon,
120+
timeZoneSeparator: .colon,
121+
includingFractionalSeconds: true
122+
)
123+
return ISO8601DateTranscoder(formatStyle: formatStyle)
124+
} else {
125+
return ISO8601DateTranscoder(options: [.withInternetDateTime, .withFractionalSeconds])
126+
}
74127
}
75128
}
76129

0 commit comments

Comments
 (0)