Skip to content

Commit 9790cea

Browse files
committed
DateDecodingStrategy
1 parent 21af7d1 commit 9790cea

File tree

2 files changed

+67
-7
lines changed

2 files changed

+67
-7
lines changed

Sources/KeyValueDecoder.swift

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,18 @@ public struct KeyValueDecoder: Sendable {
3838
/// Contextual user-provided information for use during encoding.
3939
public var userInfo: [CodingUserInfoKey: any Sendable]
4040

41-
/// The strategy to use for decoding `nil`. Defaults to `Optional<Any>.none` which can be decoded to any optional type.
42-
public var nilDecodingStrategy: NilDecodingStrategy = .default
41+
/// The strategy to use for decoding Date types.
42+
public var dateDecodingStrategy: DateDecodingStrategy = .date
4343

4444
/// The strategy to use for decoding BinaryInteger types. Defaults to `.exact` for lossless conversion between types.
4545
public var intDecodingStrategy: IntDecodingStrategy = .exact
4646

4747
/// The strategy to use for decoding each types keys.
4848
public var keyDecodingStrategy: KeyDecodingStrategy = .useDefaultKeys
4949

50+
/// The strategy to use for decoding `nil`. Defaults to `Optional<Any>.none` which can be decoded to any optional type.
51+
public var nilDecodingStrategy: NilDecodingStrategy = .default
52+
5053
/// Initializes `self` with default strategy.
5154
public init () {
5255
self.userInfo = [:]
@@ -94,6 +97,21 @@ public struct KeyValueDecoder: Sendable {
9497
/// A key encoding strategy that doesn’t change key names during encoding.
9598
case useDefaultKeys
9699
}
100+
101+
public enum DateDecodingStrategy: Sendable {
102+
103+
/// Decodes dates by casting from Any.
104+
case date
105+
106+
/// Decodes dates from ISO8601 strings.
107+
case iso8601(options: ISO8601DateFormatter.Options = [.withInternetDateTime])
108+
109+
/// Decodes dates in terms of milliseconds since midnight UTC on January 1st, 1970.
110+
case millisecondsSince1970
111+
112+
/// Decodes dates in terms of seconds since midnight UTC on January 1st, 1970.
113+
case secondsSince1970
114+
}
97115
}
98116

99117
#if canImport(Combine)
@@ -118,13 +136,15 @@ private extension KeyValueDecoder {
118136
var optionals: NilDecodingStrategy
119137
var integers: IntDecodingStrategy
120138
var keys: KeyDecodingStrategy
139+
var dates: DateDecodingStrategy
121140
}
122141

123142
var strategy: DecodingStrategy {
124143
DecodingStrategy(
125144
optionals: nilDecodingStrategy,
126145
integers: intDecodingStrategy,
127-
keys: keyDecodingStrategy
146+
keys: keyDecodingStrategy,
147+
dates: dateDecodingStrategy
128148
)
129149
}
130150

@@ -336,7 +356,23 @@ private extension KeyValueDecoder {
336356
}
337357

338358
func decode(_ type: Date.Type) throws -> Date {
339-
try getValue()
359+
switch strategy.dates {
360+
case .date:
361+
return try getValue()
362+
case .iso8601(options: let options):
363+
let string = try decode(String.self)
364+
let formatter = ISO8601DateFormatter()
365+
formatter.formatOptions = options
366+
guard let date = formatter.date(from: string) else {
367+
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "Failed to decode Date from ISO8601 string \(string)"))
368+
}
369+
return date
370+
case .millisecondsSince1970:
371+
return try Date(timeIntervalSince1970: TimeInterval(decode(Int.self)) / 1000)
372+
373+
case .secondsSince1970:
374+
return try Date(timeIntervalSince1970: TimeInterval(decode(Int.self)))
375+
}
340376
}
341377

342378
func decode(_ type: Data.Type) throws -> Data {

Tests/KeyValueDecoderTests.swift

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,11 +403,35 @@ struct KeyValueDecoderTests {
403403

404404
@Test
405405
func decodes_Date() throws {
406-
let decoder = KeyValueDecoder()
406+
var decoder = KeyValueDecoder()
407+
let referenceDate = Date(timeIntervalSinceReferenceDate: 0)
408+
409+
decoder.dateDecodingStrategy = .date
410+
#expect(
411+
try decoder.decode(Date.self, from: referenceDate) == referenceDate
412+
)
413+
414+
decoder.dateDecodingStrategy = .iso8601()
415+
#expect(
416+
try decoder.decode(Date.self, from: "2001-01-01T00:00:00Z") == referenceDate
417+
)
418+
#expect(throws: DecodingError.self) {
419+
try decoder.decode(Date.self, from: "2001-01-01")
420+
}
421+
422+
decoder.dateDecodingStrategy = .iso8601(options: [.withInternetDateTime, .withFractionalSeconds])
423+
#expect(
424+
try decoder.decode(Date.self, from: "2001-01-01T00:00:00.000Z") == referenceDate
425+
)
426+
427+
decoder.dateDecodingStrategy = .millisecondsSince1970
428+
#expect(
429+
try decoder.decode(Date.self, from: 978307200000) == referenceDate
430+
)
407431

408-
let date = Date(timeIntervalSinceReferenceDate: 0)
432+
decoder.dateDecodingStrategy = .secondsSince1970
409433
#expect(
410-
try decoder.decode(Date.self, from: date) == date
434+
try decoder.decode(Date.self, from: 978307200) == referenceDate
411435
)
412436
}
413437

0 commit comments

Comments
 (0)