diff --git a/Sources/Auth/Defaults.swift b/Sources/Auth/Defaults.swift index 0675ee456..ae7e00fd2 100644 --- a/Sources/Auth/Defaults.swift +++ b/Sources/Auth/Defaults.swift @@ -5,17 +5,23 @@ // Created by Guilherme Souza on 14/12/23. // +import ConcurrencyExtras import Foundation import Helpers extension AuthClient.Configuration { + private static let supportedDateFormatters: [UncheckedSendable] = [ + ISO8601DateFormatter.iso8601WithFractionalSeconds, + ISO8601DateFormatter.iso8601, + ] + /// The default JSONEncoder instance used by the ``AuthClient``. public static let jsonEncoder: JSONEncoder = { let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase encoder.dateEncodingStrategy = .custom { date, encoder in + let string = ISO8601DateFormatter.iso8601WithFractionalSeconds.value.string(from: date) var container = encoder.singleValueContainer() - let string = DateFormatter.iso8601.string(from: date) try container.encode(string) } return encoder @@ -29,10 +35,8 @@ extension AuthClient.Configuration { let container = try decoder.singleValueContainer() let string = try container.decode(String.self) - let supportedFormatters: [DateFormatter] = [.iso8601, .iso8601_noMilliseconds] - - for formatter in supportedFormatters { - if let date = formatter.date(from: string) { + for formatter in supportedDateFormatters { + if let date = formatter.value.date(from: string) { return date } } diff --git a/Sources/Helpers/AnyJSON/AnyJSON+Codable.swift b/Sources/Helpers/AnyJSON/AnyJSON+Codable.swift index 082d5e073..064bb4c97 100644 --- a/Sources/Helpers/AnyJSON/AnyJSON+Codable.swift +++ b/Sources/Helpers/AnyJSON/AnyJSON+Codable.swift @@ -16,16 +16,11 @@ extension AnyJSON { let container = try decoder.singleValueContainer() let dateString = try container.decode(String.self) - let date = DateFormatter.iso8601.date(from: dateString) ?? DateFormatter - .iso8601_noMilliseconds.date(from: dateString) + let date = ISO8601DateFormatter.iso8601WithFractionalSeconds.value.date(from: dateString) ?? ISO8601DateFormatter.iso8601.value.date(from: dateString) guard let decodedDate = date else { - throw DecodingError.typeMismatch( - Date.self, - DecodingError.Context( - codingPath: container.codingPath, - debugDescription: "String is not a valid Date" - ) + throw DecodingError.dataCorruptedError( + in: container, debugDescription: "Invalid date format: \(dateString)" ) } @@ -38,7 +33,11 @@ extension AnyJSON { public static let encoder: JSONEncoder = { let encoder = JSONEncoder() encoder.dataEncodingStrategy = .base64 - encoder.dateEncodingStrategy = .formatted(DateFormatter.iso8601) + encoder.dateEncodingStrategy = .custom { date, encoder in + let string = ISO8601DateFormatter.iso8601WithFractionalSeconds.value.string(from: date) + var container = encoder.singleValueContainer() + try container.encode(string) + } return encoder }() } diff --git a/Sources/Helpers/DateFormatter.swift b/Sources/Helpers/DateFormatter.swift index 7122a38a3..5d5acc841 100644 --- a/Sources/Helpers/DateFormatter.swift +++ b/Sources/Helpers/DateFormatter.swift @@ -5,26 +5,19 @@ // Created by Guilherme Souza on 28/12/23. // +import ConcurrencyExtras import Foundation -extension DateFormatter { - /// DateFormatter class that generates and parses string representations of dates following the - /// ISO 8601 standard - package static let iso8601: DateFormatter = { - let iso8601DateFormatter = DateFormatter() - - iso8601DateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" - iso8601DateFormatter.locale = Locale(identifier: "en_US_POSIX") - iso8601DateFormatter.timeZone = TimeZone(secondsFromGMT: 0) - return iso8601DateFormatter +extension ISO8601DateFormatter { + package static let iso8601: UncheckedSendable = { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime] + return UncheckedSendable(formatter) }() - package static let iso8601_noMilliseconds: DateFormatter = { - let iso8601DateFormatter = DateFormatter() - - iso8601DateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'" - iso8601DateFormatter.locale = Locale(identifier: "en_US_POSIX") - iso8601DateFormatter.timeZone = TimeZone(secondsFromGMT: 0) - return iso8601DateFormatter + package static let iso8601WithFractionalSeconds: UncheckedSendable = { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + return UncheckedSendable(formatter) }() } diff --git a/Sources/Helpers/SupabaseLogger.swift b/Sources/Helpers/SupabaseLogger.swift index f78737af5..1b0077f03 100644 --- a/Sources/Helpers/SupabaseLogger.swift +++ b/Sources/Helpers/SupabaseLogger.swift @@ -55,7 +55,7 @@ public struct SupabaseLogMessage: Codable, CustomStringConvertible, Sendable { } public var description: String { - let date = DateFormatter.iso8601_noMilliseconds.string(from: Date(timeIntervalSince1970: timestamp)) + let date = ISO8601DateFormatter.iso8601.value.string(from: Date(timeIntervalSince1970: timestamp)) let file = fileID.split(separator: ".", maxSplits: 1).first.map(String.init) ?? fileID var description = "\(date) [\(level)] [\(system)] [\(file).\(function):\(line)] \(message)" if !additionalContext.isEmpty { diff --git a/Sources/PostgREST/Defaults.swift b/Sources/PostgREST/Defaults.swift index d9782dd42..238047a29 100644 --- a/Sources/PostgREST/Defaults.swift +++ b/Sources/PostgREST/Defaults.swift @@ -5,15 +5,16 @@ // Created by Guilherme Souza on 14/12/23. // +import ConcurrencyExtras import Foundation import Helpers let version = Helpers.version extension PostgrestClient.Configuration { - private static let supportedDateFormatters: [DateFormatter] = [ - .iso8601, - .iso8601_noMilliseconds, + private static let supportedDateFormatters: [UncheckedSendable] = [ + ISO8601DateFormatter.iso8601WithFractionalSeconds, + ISO8601DateFormatter.iso8601, ] /// The default `JSONDecoder` instance for ``PostgrestClient`` responses. @@ -24,7 +25,7 @@ extension PostgrestClient.Configuration { let string = try container.decode(String.self) for formatter in supportedDateFormatters { - if let date = formatter.date(from: string) { + if let date = formatter.value.date(from: string) { return date } } diff --git a/Tests/PostgRESTTests/JSONTests.swift b/Tests/PostgRESTTests/JSONTests.swift new file mode 100644 index 000000000..dabf2d97f --- /dev/null +++ b/Tests/PostgRESTTests/JSONTests.swift @@ -0,0 +1,28 @@ +// +// JSONTests.swift +// +// +// Created by Guilherme Souza on 01/07/24. +// + +@testable import PostgREST +import XCTest + +final class JSONTests: XCTestCase { + func testDecodeJSON() throws { + let json = """ + { + "created_at": "2024-06-15T18:12:04+00:00" + } + """.data(using: .utf8)! + + struct Value: Decodable { + var createdAt: Date + + enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + } + } + _ = try PostgrestClient.Configuration.jsonDecoder.decode(Value.self, from: json) + } +} diff --git a/supabase-swift.xcworkspace/xcshareddata/swiftpm/Package.resolved b/supabase-swift.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0de58df6c..43bcdb22e 100644 --- a/supabase-swift.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/supabase-swift.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/gtm-session-fetcher.git", "state" : { - "revision" : "0382ca27f22fb3494cf657d8dc356dc282cd1193", - "version" : "3.4.1" + "revision" : "a2ab612cb980066ee56d90d60d8462992c07f24b", + "version" : "3.5.0" } }, { @@ -77,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-crypto.git", "state" : { - "revision" : "bc1c29221f6dfeb0ebbfbc98eb95cd3d4967868e", - "version" : "3.4.0" + "revision" : "33f65a3cbc52f8c19295723af9cbecc2195484e1", + "version" : "3.5.0" } }, {