Skip to content

Commit 853ebe7

Browse files
authored
Add support for ISO-8601-formatted dates (#8)
Add support for ISO-8601-formatted dates This change adds support for the common `ISO-8601` date format, by making it possible to pass a `ISO8601DateFormatter` when encoding or decoding a date (on supported platforms).
1 parent 0ba20b4 commit 853ebe7

3 files changed

Lines changed: 57 additions & 6 deletions

File tree

Sources/Codextended/Codextended.swift

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,13 @@ public extension Encoder {
4949

5050
/// Encode a date for a given key (specified as a string), using a specific formatter.
5151
/// To encode a date without using a specific formatter, simply encode it like any other value.
52-
func encode(_ date: Date, for key: String, using formatter: DateFormatter) throws {
52+
func encode<F: AnyDateFormatter>(_ date: Date, for key: String, using formatter: F) throws {
5353
try encode(date, for: AnyCodingKey(key), using: formatter)
5454
}
5555

5656
/// Encode a date for a given key (specified using a `CodingKey`), using a specific formatter.
5757
/// To encode a date without using a specific formatter, simply encode it like any other value.
58-
func encode<K: CodingKey>(_ date: Date, for key: K, using formatter: DateFormatter) throws {
58+
func encode<K: CodingKey, F: AnyDateFormatter>(_ date: Date, for key: K, using formatter: F) throws {
5959
let string = formatter.string(from: date)
6060
try encode(string, for: key)
6161
}
@@ -106,14 +106,14 @@ public extension Decoder {
106106
/// Decode a date from a string for a given key (specified as a string), using a
107107
/// specific formatter. To decode a date using the decoder's default settings,
108108
/// simply decode it like any other value instead of using this method.
109-
func decode(_ key: String, using formatter: DateFormatter) throws -> Date {
109+
func decode<F: AnyDateFormatter>(_ key: String, using formatter: F) throws -> Date {
110110
return try decode(AnyCodingKey(key), using: formatter)
111111
}
112112

113113
/// Decode a date from a string for a given key (specified as a `CodingKey`), using
114114
/// a specific formatter. To decode a date using the decoder's default settings,
115115
/// simply decode it like any other value instead of using this method.
116-
func decode<K: CodingKey>(_ key: K, using formatter: DateFormatter) throws -> Date {
116+
func decode<K: CodingKey, F: AnyDateFormatter>(_ key: K, using formatter: F) throws -> Date {
117117
let container = try self.container(keyedBy: K.self)
118118
let rawString = try container.decode(String.self, forKey: key)
119119

@@ -129,6 +129,24 @@ public extension Decoder {
129129
}
130130
}
131131

132+
// MARK: - Date formatters
133+
134+
/// Protocol acting as a common API for all types of date formatters,
135+
/// such as `DateFormatter` and `ISO8601DateFormatter`.
136+
public protocol AnyDateFormatter {
137+
/// Format a string into a date
138+
func date(from string: String) -> Date?
139+
/// Format a date into a string
140+
func string(from date: Date) -> String
141+
}
142+
143+
extension DateFormatter: AnyDateFormatter {}
144+
145+
@available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
146+
extension ISO8601DateFormatter: AnyDateFormatter {}
147+
148+
// MARK: - Private supporting types
149+
132150
private struct AnyCodingKey: CodingKey {
133151
var stringValue: String
134152
var intValue: Int?

Tests/CodextendedTests/CodextendedTests.swift

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,35 @@ final class CodextendedTests: XCTestCase {
127127
formatter.string(from: valueB.date))
128128
}
129129

130+
@available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
131+
func testDateWithISO8601Formatter() throws {
132+
struct Value: Codable, Equatable {
133+
let date: Date
134+
135+
init(date: Date) {
136+
self.date = date
137+
}
138+
139+
init(from decoder: Decoder) throws {
140+
let formatter = ISO8601DateFormatter()
141+
date = try decoder.decode("key", using: formatter)
142+
}
143+
144+
func encode(to encoder: Encoder) throws {
145+
let formatter = ISO8601DateFormatter()
146+
try encoder.encode(date, for: "key", using: formatter)
147+
}
148+
}
149+
150+
let valueA = Value(date: Date())
151+
let data = try valueA.encoded()
152+
let valueB = try data.decoded() as Value
153+
let formatter = ISO8601DateFormatter()
154+
155+
XCTAssertEqual(formatter.string(from: valueA.date),
156+
formatter.string(from: valueB.date))
157+
}
158+
130159
func testDecodingErrorThrownForInvalidDateString() {
131160
struct Value: Decodable {
132161
let date: Date
@@ -149,7 +178,7 @@ final class CodextendedTests: XCTestCase {
149178
}
150179

151180
func testAllTestsRunOnLinux() {
152-
verifyAllTestsRunOnLinux()
181+
verifyAllTestsRunOnLinux(excluding: ["testDateWithISO8601Formatter"])
153182
}
154183
}
155184

Tests/CodextendedTests/LinuxTestable.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ protocol LinuxTestable: XCTestCase {
1111
}
1212

1313
extension LinuxTestable {
14-
func verifyAllTestsRunOnLinux() {
14+
func verifyAllTestsRunOnLinux(excluding excludedTestNames: Set<String>) {
1515
#if os(macOS)
1616
let testNames = Set(Self.allTests.map { $0.0 })
1717

@@ -20,6 +20,10 @@ extension LinuxTestable {
2020
continue
2121
}
2222

23+
guard !excludedTestNames.contains(name) else {
24+
continue
25+
}
26+
2327
if !testNames.contains(name) {
2428
XCTFail("""
2529
Test case \(Self.self) does not include test \(name) on Linux.

0 commit comments

Comments
 (0)