Skip to content

Commit

Permalink
Add support for ISO-8601-formatted dates (#8)
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
JohnSundell authored Apr 10, 2019
1 parent 0ba20b4 commit 853ebe7
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 6 deletions.
26 changes: 22 additions & 4 deletions Sources/Codextended/Codextended.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ public extension Encoder {

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

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

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

Expand All @@ -129,6 +129,24 @@ public extension Decoder {
}
}

// MARK: - Date formatters

/// Protocol acting as a common API for all types of date formatters,
/// such as `DateFormatter` and `ISO8601DateFormatter`.
public protocol AnyDateFormatter {
/// Format a string into a date
func date(from string: String) -> Date?
/// Format a date into a string
func string(from date: Date) -> String
}

extension DateFormatter: AnyDateFormatter {}

@available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
extension ISO8601DateFormatter: AnyDateFormatter {}

// MARK: - Private supporting types

private struct AnyCodingKey: CodingKey {
var stringValue: String
var intValue: Int?
Expand Down
31 changes: 30 additions & 1 deletion Tests/CodextendedTests/CodextendedTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,35 @@ final class CodextendedTests: XCTestCase {
formatter.string(from: valueB.date))
}

@available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
func testDateWithISO8601Formatter() throws {
struct Value: Codable, Equatable {
let date: Date

init(date: Date) {
self.date = date
}

init(from decoder: Decoder) throws {
let formatter = ISO8601DateFormatter()
date = try decoder.decode("key", using: formatter)
}

func encode(to encoder: Encoder) throws {
let formatter = ISO8601DateFormatter()
try encoder.encode(date, for: "key", using: formatter)
}
}

let valueA = Value(date: Date())
let data = try valueA.encoded()
let valueB = try data.decoded() as Value
let formatter = ISO8601DateFormatter()

XCTAssertEqual(formatter.string(from: valueA.date),
formatter.string(from: valueB.date))
}

func testDecodingErrorThrownForInvalidDateString() {
struct Value: Decodable {
let date: Date
Expand All @@ -149,7 +178,7 @@ final class CodextendedTests: XCTestCase {
}

func testAllTestsRunOnLinux() {
verifyAllTestsRunOnLinux()
verifyAllTestsRunOnLinux(excluding: ["testDateWithISO8601Formatter"])
}
}

Expand Down
6 changes: 5 additions & 1 deletion Tests/CodextendedTests/LinuxTestable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ protocol LinuxTestable: XCTestCase {
}

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

Expand All @@ -20,6 +20,10 @@ extension LinuxTestable {
continue
}

guard !excludedTestNames.contains(name) else {
continue
}

if !testNames.contains(name) {
XCTFail("""
Test case \(Self.self) does not include test \(name) on Linux.
Expand Down

0 comments on commit 853ebe7

Please sign in to comment.