Skip to content

Commit 6b4cc2e

Browse files
authored
fix: date formatter breaking change (#435)
1 parent fea42cf commit 6b4cc2e

File tree

7 files changed

+65
-40
lines changed

7 files changed

+65
-40
lines changed

Sources/Auth/Defaults.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,23 @@
55
// Created by Guilherme Souza on 14/12/23.
66
//
77

8+
import ConcurrencyExtras
89
import Foundation
910
import Helpers
1011

1112
extension AuthClient.Configuration {
13+
private static let supportedDateFormatters: [UncheckedSendable<ISO8601DateFormatter>] = [
14+
ISO8601DateFormatter.iso8601WithFractionalSeconds,
15+
ISO8601DateFormatter.iso8601,
16+
]
17+
1218
/// The default JSONEncoder instance used by the ``AuthClient``.
1319
public static let jsonEncoder: JSONEncoder = {
1420
let encoder = JSONEncoder()
1521
encoder.keyEncodingStrategy = .convertToSnakeCase
1622
encoder.dateEncodingStrategy = .custom { date, encoder in
23+
let string = ISO8601DateFormatter.iso8601WithFractionalSeconds.value.string(from: date)
1724
var container = encoder.singleValueContainer()
18-
let string = DateFormatter.iso8601.string(from: date)
1925
try container.encode(string)
2026
}
2127
return encoder
@@ -29,10 +35,8 @@ extension AuthClient.Configuration {
2935
let container = try decoder.singleValueContainer()
3036
let string = try container.decode(String.self)
3137

32-
let supportedFormatters: [DateFormatter] = [.iso8601, .iso8601_noMilliseconds]
33-
34-
for formatter in supportedFormatters {
35-
if let date = formatter.date(from: string) {
38+
for formatter in supportedDateFormatters {
39+
if let date = formatter.value.date(from: string) {
3640
return date
3741
}
3842
}

Sources/Helpers/AnyJSON/AnyJSON+Codable.swift

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,11 @@ extension AnyJSON {
1616
let container = try decoder.singleValueContainer()
1717
let dateString = try container.decode(String.self)
1818

19-
let date = DateFormatter.iso8601.date(from: dateString) ?? DateFormatter
20-
.iso8601_noMilliseconds.date(from: dateString)
19+
let date = ISO8601DateFormatter.iso8601WithFractionalSeconds.value.date(from: dateString) ?? ISO8601DateFormatter.iso8601.value.date(from: dateString)
2120

2221
guard let decodedDate = date else {
23-
throw DecodingError.typeMismatch(
24-
Date.self,
25-
DecodingError.Context(
26-
codingPath: container.codingPath,
27-
debugDescription: "String is not a valid Date"
28-
)
22+
throw DecodingError.dataCorruptedError(
23+
in: container, debugDescription: "Invalid date format: \(dateString)"
2924
)
3025
}
3126

@@ -38,7 +33,11 @@ extension AnyJSON {
3833
public static let encoder: JSONEncoder = {
3934
let encoder = JSONEncoder()
4035
encoder.dataEncodingStrategy = .base64
41-
encoder.dateEncodingStrategy = .formatted(DateFormatter.iso8601)
36+
encoder.dateEncodingStrategy = .custom { date, encoder in
37+
let string = ISO8601DateFormatter.iso8601WithFractionalSeconds.value.string(from: date)
38+
var container = encoder.singleValueContainer()
39+
try container.encode(string)
40+
}
4241
return encoder
4342
}()
4443
}

Sources/Helpers/DateFormatter.swift

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,19 @@
55
// Created by Guilherme Souza on 28/12/23.
66
//
77

8+
import ConcurrencyExtras
89
import Foundation
910

10-
extension DateFormatter {
11-
/// DateFormatter class that generates and parses string representations of dates following the
12-
/// ISO 8601 standard
13-
package static let iso8601: DateFormatter = {
14-
let iso8601DateFormatter = DateFormatter()
15-
16-
iso8601DateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
17-
iso8601DateFormatter.locale = Locale(identifier: "en_US_POSIX")
18-
iso8601DateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
19-
return iso8601DateFormatter
11+
extension ISO8601DateFormatter {
12+
package static let iso8601: UncheckedSendable<ISO8601DateFormatter> = {
13+
let formatter = ISO8601DateFormatter()
14+
formatter.formatOptions = [.withInternetDateTime]
15+
return UncheckedSendable(formatter)
2016
}()
2117

22-
package static let iso8601_noMilliseconds: DateFormatter = {
23-
let iso8601DateFormatter = DateFormatter()
24-
25-
iso8601DateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
26-
iso8601DateFormatter.locale = Locale(identifier: "en_US_POSIX")
27-
iso8601DateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
28-
return iso8601DateFormatter
18+
package static let iso8601WithFractionalSeconds: UncheckedSendable<ISO8601DateFormatter> = {
19+
let formatter = ISO8601DateFormatter()
20+
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
21+
return UncheckedSendable(formatter)
2922
}()
3023
}

Sources/Helpers/SupabaseLogger.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public struct SupabaseLogMessage: Codable, CustomStringConvertible, Sendable {
5555
}
5656

5757
public var description: String {
58-
let date = DateFormatter.iso8601_noMilliseconds.string(from: Date(timeIntervalSince1970: timestamp))
58+
let date = ISO8601DateFormatter.iso8601.value.string(from: Date(timeIntervalSince1970: timestamp))
5959
let file = fileID.split(separator: ".", maxSplits: 1).first.map(String.init) ?? fileID
6060
var description = "\(date) [\(level)] [\(system)] [\(file).\(function):\(line)] \(message)"
6161
if !additionalContext.isEmpty {

Sources/PostgREST/Defaults.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@
55
// Created by Guilherme Souza on 14/12/23.
66
//
77

8+
import ConcurrencyExtras
89
import Foundation
910
import Helpers
1011

1112
let version = Helpers.version
1213

1314
extension PostgrestClient.Configuration {
14-
private static let supportedDateFormatters: [DateFormatter] = [
15-
.iso8601,
16-
.iso8601_noMilliseconds,
15+
private static let supportedDateFormatters: [UncheckedSendable<ISO8601DateFormatter>] = [
16+
ISO8601DateFormatter.iso8601WithFractionalSeconds,
17+
ISO8601DateFormatter.iso8601,
1718
]
1819

1920
/// The default `JSONDecoder` instance for ``PostgrestClient`` responses.
@@ -24,7 +25,7 @@ extension PostgrestClient.Configuration {
2425
let string = try container.decode(String.self)
2526

2627
for formatter in supportedDateFormatters {
27-
if let date = formatter.date(from: string) {
28+
if let date = formatter.value.date(from: string) {
2829
return date
2930
}
3031
}

Tests/PostgRESTTests/JSONTests.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// JSONTests.swift
3+
//
4+
//
5+
// Created by Guilherme Souza on 01/07/24.
6+
//
7+
8+
@testable import PostgREST
9+
import XCTest
10+
11+
final class JSONTests: XCTestCase {
12+
func testDecodeJSON() throws {
13+
let json = """
14+
{
15+
"created_at": "2024-06-15T18:12:04+00:00"
16+
}
17+
""".data(using: .utf8)!
18+
19+
struct Value: Decodable {
20+
var createdAt: Date
21+
22+
enum CodingKeys: String, CodingKey {
23+
case createdAt = "created_at"
24+
}
25+
}
26+
_ = try PostgrestClient.Configuration.jsonDecoder.decode(Value.self, from: json)
27+
}
28+
}

supabase-swift.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)