From c3f33eccb05ba0bb75d3412ec8dc1d1ad4869360 Mon Sep 17 00:00:00 2001 From: Arthur Crocquevieille Date: Sat, 5 Oct 2024 22:45:49 +0200 Subject: [PATCH 1/2] Adopt ISO8601FormatStyle on newer platforms --- .../OpenAPIRuntimeBenchmarks/Benchmarks.swift | 29 +----- .../Conversion/Configuration.swift | 99 ++++++++++++++----- 2 files changed, 78 insertions(+), 50 deletions(-) diff --git a/Benchmarks/Benchmarks/OpenAPIRuntimeBenchmarks/Benchmarks.swift b/Benchmarks/Benchmarks/OpenAPIRuntimeBenchmarks/Benchmarks.swift index 634a0d65..7736f10f 100644 --- a/Benchmarks/Benchmarks/OpenAPIRuntimeBenchmarks/Benchmarks.swift +++ b/Benchmarks/Benchmarks/OpenAPIRuntimeBenchmarks/Benchmarks.swift @@ -27,35 +27,8 @@ let benchmarks = { maxIterations: 5 ) ) { benchmark in - let transcoder = ISO8601DateTranscoder() + let transcoder = ISO8601DateTranscoder(formatStyle: .iso8601) benchmark.startMeasurement() for _ in benchmark.scaledIterations { blackHole(try transcoder.encode(.distantFuture)) } } - - Benchmark( - "ISO8601DateFormatter.string(from:)", - configuration: Benchmark.Configuration( - metrics: defaultMetrics, - scalingFactor: .kilo, - maxDuration: .seconds(10_000_000), - maxIterations: 5 - ) - ) { benchmark in - let formatter = ISO8601DateFormatter() - benchmark.startMeasurement() - for _ in benchmark.scaledIterations { blackHole(formatter.string(from: .distantFuture)) } - } - - Benchmark( - "Date.ISO8601Format(_:)", - configuration: Benchmark.Configuration( - metrics: defaultMetrics, - scalingFactor: .kilo, - maxDuration: .seconds(10_000_000), - maxIterations: 5 - ) - ) { benchmark in - benchmark.startMeasurement() - for _ in benchmark.scaledIterations { blackHole(Date.distantFuture.ISO8601Format()) } - } } diff --git a/Sources/OpenAPIRuntime/Conversion/Configuration.swift b/Sources/OpenAPIRuntime/Conversion/Configuration.swift index 2ee7ab00..750bc1b1 100644 --- a/Sources/OpenAPIRuntime/Conversion/Configuration.swift +++ b/Sources/OpenAPIRuntime/Conversion/Configuration.swift @@ -25,42 +25,85 @@ public protocol DateTranscoder: Sendable { } /// A transcoder for dates encoded as an ISO-8601 string (in RFC 3339 format). -public struct ISO8601DateTranscoder: DateTranscoder, @unchecked Sendable { +public struct ISO8601DateTranscoder: DateTranscoder { + /// A transcoder using `ISO8601DateFormatter` for encoding and decoding. + private struct DateFormatterTranscoder: DateTranscoder, @unchecked Sendable { + /// The lock protecting the formatter. + private let lock: NSLock + + /// The underlying date formatter. + private let locked_formatter: ISO8601DateFormatter + + init(options: ISO8601DateFormatter.Options? = nil) { + let formatter = ISO8601DateFormatter() + if let options { formatter.formatOptions = options } + lock = NSLock() + lock.name = "com.apple.swift-openapi-generator.runtime.ISO8601DateTranscoder" + locked_formatter = formatter + } + + func encode(_ date: Date) throws -> String { + lock.lock() + defer { lock.unlock() } + return locked_formatter.string(from: date) + } - /// The lock protecting the formatter. - private let lock: NSLock + func decode(_ dateString: String) throws -> Date { + lock.lock() + defer { lock.unlock() } + guard let date = locked_formatter.date(from: dateString) else { + throw DecodingError.dataCorrupted( + .init(codingPath: [], debugDescription: "Expected date string to be ISO8601-formatted.") + ) + } + return date + } + } - /// The underlying date formatter. - private let locked_formatter: ISO8601DateFormatter + @available(macOS 12, *) + /// A transcoder using `Date.ISO8601FormatStyle` for encoding and decoding which is significantly faster than `DateFormatterTranscoder`. + private struct DateFormatStyleTranscoder: DateTranscoder, Sendable { + private let formatStyle: Date.ISO8601FormatStyle + + init(formatStyle: Date.ISO8601FormatStyle) { + self.formatStyle = formatStyle + } + + func encode(_ date: Date) throws -> String { + date.formatted(formatStyle) + } + + func decode(_ dateString: String) throws -> Date { + try formatStyle.parse(dateString) + } + } + + private let dateTranscoder: any DateTranscoder /// Creates a new transcoder with the provided options. /// - Parameter options: Options to override the default ones. If you provide nil here, the default options /// are used. + @available(macOS, deprecated: 12, message: "Use .init(formatStyle:) instead.") + @_disfavoredOverload public init(options: ISO8601DateFormatter.Options? = nil) { - let formatter = ISO8601DateFormatter() - if let options { formatter.formatOptions = options } - lock = NSLock() - lock.name = "com.apple.swift-openapi-generator.runtime.ISO8601DateTranscoder" - locked_formatter = formatter + self.dateTranscoder = DateFormatterTranscoder(options: options) + } + + /// Creates a new transcoder with the given ISO8601 format style. + /// - Parameter formatStyle: The format style for encoding/decoding dates. Defaults to `Date.ISO8601FormatStyle()`. + @available(macOS 12.0, *) + public init(formatStyle: Date.ISO8601FormatStyle = .iso8601) { + self.dateTranscoder = DateFormatStyleTranscoder(formatStyle: formatStyle) } /// Creates and returns an ISO 8601 formatted string representation of the specified date. public func encode(_ date: Date) throws -> String { - lock.lock() - defer { lock.unlock() } - return locked_formatter.string(from: date) + try self.dateTranscoder.encode(date) } /// Creates and returns a date object from the specified ISO 8601 formatted string representation. public func decode(_ dateString: String) throws -> Date { - lock.lock() - defer { lock.unlock() } - guard let date = locked_formatter.date(from: dateString) else { - throw DecodingError.dataCorrupted( - .init(codingPath: [], debugDescription: "Expected date string to be ISO8601-formatted.") - ) - } - return date + try self.dateTranscoder.decode(dateString) } } @@ -70,7 +113,19 @@ extension DateTranscoder where Self == ISO8601DateTranscoder { /// A transcoder that transcodes dates as ISO-8601–formatted string (in RFC 3339 format) with fractional seconds. public static var iso8601WithFractionalSeconds: Self { - ISO8601DateTranscoder(options: [.withInternetDateTime, .withFractionalSeconds]) + if #available(macOS 12, *) { + let formatStyle = Date.ISO8601FormatStyle.iso8601 + .year() + .month() + .day() + .time(includingFractionalSeconds: true) + .dateSeparator(.dash) + .timeSeparator(.colon) + .timeZoneSeparator(.colon) + return ISO8601DateTranscoder(formatStyle: formatStyle) + } else { + return ISO8601DateTranscoder(options: [.withInternetDateTime, .withFractionalSeconds]) + } } } From 9d04d2cb0b65727926b006ec6c89a3d077851c5d Mon Sep 17 00:00:00 2001 From: Arthur Crocquevieille Date: Sun, 6 Oct 2024 09:56:47 +0200 Subject: [PATCH 2/2] Run swift format --- .../OpenAPIRuntimeBenchmarks/Benchmarks.swift | 2 +- .../Conversion/Configuration.swift | 40 +++++-------------- 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/Benchmarks/Benchmarks/OpenAPIRuntimeBenchmarks/Benchmarks.swift b/Benchmarks/Benchmarks/OpenAPIRuntimeBenchmarks/Benchmarks.swift index 7736f10f..2b8eaac0 100644 --- a/Benchmarks/Benchmarks/OpenAPIRuntimeBenchmarks/Benchmarks.swift +++ b/Benchmarks/Benchmarks/OpenAPIRuntimeBenchmarks/Benchmarks.swift @@ -27,7 +27,7 @@ let benchmarks = { maxIterations: 5 ) ) { benchmark in - let transcoder = ISO8601DateTranscoder(formatStyle: .iso8601) + let transcoder = ISO8601DateTranscoder() benchmark.startMeasurement() for _ in benchmark.scaledIterations { blackHole(try transcoder.encode(.distantFuture)) } } diff --git a/Sources/OpenAPIRuntime/Conversion/Configuration.swift b/Sources/OpenAPIRuntime/Conversion/Configuration.swift index 750bc1b1..c3057fb6 100644 --- a/Sources/OpenAPIRuntime/Conversion/Configuration.swift +++ b/Sources/OpenAPIRuntime/Conversion/Configuration.swift @@ -65,17 +65,11 @@ public struct ISO8601DateTranscoder: DateTranscoder { private struct DateFormatStyleTranscoder: DateTranscoder, Sendable { private let formatStyle: Date.ISO8601FormatStyle - init(formatStyle: Date.ISO8601FormatStyle) { - self.formatStyle = formatStyle - } + init(formatStyle: Date.ISO8601FormatStyle) { self.formatStyle = formatStyle } - func encode(_ date: Date) throws -> String { - date.formatted(formatStyle) - } + func encode(_ date: Date) throws -> String { date.formatted(formatStyle) } - func decode(_ dateString: String) throws -> Date { - try formatStyle.parse(dateString) - } + func decode(_ dateString: String) throws -> Date { try formatStyle.parse(dateString) } } private let dateTranscoder: any DateTranscoder @@ -83,28 +77,21 @@ public struct ISO8601DateTranscoder: DateTranscoder { /// Creates a new transcoder with the provided options. /// - Parameter options: Options to override the default ones. If you provide nil here, the default options /// are used. - @available(macOS, deprecated: 12, message: "Use .init(formatStyle:) instead.") - @_disfavoredOverload - public init(options: ISO8601DateFormatter.Options? = nil) { - self.dateTranscoder = DateFormatterTranscoder(options: options) - } + @available(macOS, deprecated: 12, message: "Use .init(formatStyle:) instead.") @_disfavoredOverload public init( + options: ISO8601DateFormatter.Options? = nil + ) { self.dateTranscoder = DateFormatterTranscoder(options: options) } /// Creates a new transcoder with the given ISO8601 format style. /// - Parameter formatStyle: The format style for encoding/decoding dates. Defaults to `Date.ISO8601FormatStyle()`. - @available(macOS 12.0, *) - public init(formatStyle: Date.ISO8601FormatStyle = .iso8601) { + @available(macOS 12.0, *) public init(formatStyle: Date.ISO8601FormatStyle = .iso8601) { self.dateTranscoder = DateFormatStyleTranscoder(formatStyle: formatStyle) } /// Creates and returns an ISO 8601 formatted string representation of the specified date. - public func encode(_ date: Date) throws -> String { - try self.dateTranscoder.encode(date) - } + public func encode(_ date: Date) throws -> String { try self.dateTranscoder.encode(date) } /// Creates and returns a date object from the specified ISO 8601 formatted string representation. - public func decode(_ dateString: String) throws -> Date { - try self.dateTranscoder.decode(dateString) - } + public func decode(_ dateString: String) throws -> Date { try self.dateTranscoder.decode(dateString) } } extension DateTranscoder where Self == ISO8601DateTranscoder { @@ -114,13 +101,8 @@ extension DateTranscoder where Self == ISO8601DateTranscoder { /// A transcoder that transcodes dates as ISO-8601–formatted string (in RFC 3339 format) with fractional seconds. public static var iso8601WithFractionalSeconds: Self { if #available(macOS 12, *) { - let formatStyle = Date.ISO8601FormatStyle.iso8601 - .year() - .month() - .day() - .time(includingFractionalSeconds: true) - .dateSeparator(.dash) - .timeSeparator(.colon) + let formatStyle = Date.ISO8601FormatStyle.iso8601.year().month().day() + .time(includingFractionalSeconds: true).dateSeparator(.dash).timeSeparator(.colon) .timeZoneSeparator(.colon) return ISO8601DateTranscoder(formatStyle: formatStyle) } else {