diff --git a/Sources/TypedDate/TypedDate+Codable.swift b/Sources/TypedDate/TypedDate+Codable.swift new file mode 100644 index 0000000..917e373 --- /dev/null +++ b/Sources/TypedDate/TypedDate+Codable.swift @@ -0,0 +1,118 @@ +import Foundation + +extension TypedDate: Codable { + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let date = try container.decode(Date.self, forKey: .date) + let calendar = Calendar.current + + switch Components.self { + case is (Year).Type: + let dc = calendar.dateComponents([.year], from: date) + guard let date = calendar.date(from: dc), + let year = dc.year + else { + throw TypedDateDecodingError.invalidDate + } + self.date = date + self.components = Year(year) as! Components + + case is (Year, Month).Type: + let dc = calendar.dateComponents([.year, .month], from: date) + guard let date = calendar.date(from: dc), + let year = dc.year, + let month = dc.month + else { + throw TypedDateDecodingError.invalidDate + } + self.date = date + self.components = (Year(year), Month(month)) as! Components + + case is (Year, Month, Day).Type: + let dc = calendar.dateComponents([.year, .month, .day], from: date) + guard let date = calendar.date(from: dc), + let year = dc.year, + let month = dc.month, + let day = dc.day + else { + throw TypedDateDecodingError.invalidDate + } + self.date = date + self.components = (Year(year), Month(month), Day(day)) as! Components + + case is (Year, Month, Day, Hour).Type: + let dc = calendar.dateComponents([.year, .month, .day, .hour], from: date) + guard let date = calendar.date(from: dc), + let year = dc.year, + let month = dc.month, + let day = dc.day, + let hour = dc.hour + else { + throw TypedDateDecodingError.invalidDate + } + self.date = date + self.components = (Year(year), Month(month), Day(day), Hour(hour)) as! Components + + case is (Year, Month, Day, Hour, Minute).Type: + let dc = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: date) + guard let date = calendar.date(from: dc), + let year = dc.year, + let month = dc.month, + let day = dc.day, + let hour = dc.hour, + let minute = dc.minute + else { + throw TypedDateDecodingError.invalidDate + } + self.date = date + self.components = (Year(year), Month(month), Day(day), Hour(hour), Minute(minute)) as! Components + + case is (Year, Month, Day, Hour, Minute, Second).Type: + let dc = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date) + guard let date = calendar.date(from: dc), + let year = dc.year, + let month = dc.month, + let day = dc.day, + let hour = dc.hour, + let minute = dc.minute, + let second = dc.second + else { + throw TypedDateDecodingError.invalidDate + } + self.date = date + self.components = (Year(year), Month(month), Day(day), Hour(hour), Minute(minute), Second(second)) as! Components + + case is (Year, Month, Day, Hour, Minute, Second, Nanosecond).Type: + let dc = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second, .nanosecond], from: date) + guard let date = calendar.date(from: dc), + let year = dc.year, + let month = dc.month, + let day = dc.day, + let hour = dc.hour, + let minute = dc.minute, + let second = dc.second + else { + throw TypedDateDecodingError.invalidDate + } + let nanosecond = dc.nanosecond ?? 0 + self.date = date + self.components = (Year(year), Month(month), Day(day), Hour(hour), Minute(minute), Second(second), Nanosecond(nanosecond)) as! Components + + default: throw TypedDateDecodingError.invalidComponents + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(date, forKey: .date) + } + + public enum CodingKeys: CodingKey { + case date + } +} + +public enum TypedDateDecodingError: Error { + case invalidDate + case invalidComponents +} diff --git a/Sources/TypedDate/TypedDate+fillIn.swift b/Sources/TypedDate/TypedDate+fill.swift similarity index 100% rename from Sources/TypedDate/TypedDate+fillIn.swift rename to Sources/TypedDate/TypedDate+fill.swift diff --git a/Sources/TypedDate/TypedDate.swift b/Sources/TypedDate/TypedDate.swift index 29a800b..3e2c30f 100644 --- a/Sources/TypedDate/TypedDate.swift +++ b/Sources/TypedDate/TypedDate.swift @@ -5,119 +5,3 @@ public struct TypedDate: Sendable { let components: Components } - -extension TypedDate: Codable { - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let date = try container.decode(Date.self, forKey: .date) - let calendar = Calendar.current - - switch Components.self { - case is (Year).Type: - let dc = calendar.dateComponents([.year], from: date) - guard let date = calendar.date(from: dc), - let year = dc.year - else { - throw TypedDateDecodingError.invalidDate - } - self.date = date - self.components = Year(year) as! Components - - case is (Year, Month).Type: - let dc = calendar.dateComponents([.year, .month], from: date) - guard let date = calendar.date(from: dc), - let year = dc.year, - let month = dc.month - else { - throw TypedDateDecodingError.invalidDate - } - self.date = date - self.components = (Year(year), Month(month)) as! Components - - case is (Year, Month, Day).Type: - let dc = calendar.dateComponents([.year, .month, .day], from: date) - guard let date = calendar.date(from: dc), - let year = dc.year, - let month = dc.month, - let day = dc.day - else { - throw TypedDateDecodingError.invalidDate - } - self.date = date - self.components = (Year(year), Month(month), Day(day)) as! Components - - case is (Year, Month, Day, Hour).Type: - let dc = calendar.dateComponents([.year, .month, .day, .hour], from: date) - guard let date = calendar.date(from: dc), - let year = dc.year, - let month = dc.month, - let day = dc.day, - let hour = dc.hour - else { - throw TypedDateDecodingError.invalidDate - } - self.date = date - self.components = (Year(year), Month(month), Day(day), Hour(hour)) as! Components - - case is (Year, Month, Day, Hour, Minute).Type: - let dc = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: date) - guard let date = calendar.date(from: dc), - let year = dc.year, - let month = dc.month, - let day = dc.day, - let hour = dc.hour, - let minute = dc.minute - else { - throw TypedDateDecodingError.invalidDate - } - self.date = date - self.components = (Year(year), Month(month), Day(day), Hour(hour), Minute(minute)) as! Components - - case is (Year, Month, Day, Hour, Minute, Second).Type: - let dc = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date) - guard let date = calendar.date(from: dc), - let year = dc.year, - let month = dc.month, - let day = dc.day, - let hour = dc.hour, - let minute = dc.minute, - let second = dc.second - else { - throw TypedDateDecodingError.invalidDate - } - self.date = date - self.components = (Year(year), Month(month), Day(day), Hour(hour), Minute(minute), Second(second)) as! Components - - case is (Year, Month, Day, Hour, Minute, Second, Nanosecond).Type: - let dc = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second, .nanosecond], from: date) - guard let date = calendar.date(from: dc), - let year = dc.year, - let month = dc.month, - let day = dc.day, - let hour = dc.hour, - let minute = dc.minute, - let second = dc.second - else { - throw TypedDateDecodingError.invalidDate - } - let nanosecond = dc.nanosecond ?? 0 - self.date = date - self.components = (Year(year), Month(month), Day(day), Hour(hour), Minute(minute), Second(second), Nanosecond(nanosecond)) as! Components - - default: throw TypedDateDecodingError.invalidDate - } - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(date, forKey: .date) - } - - public enum CodingKeys: CodingKey { - case date - } -} - -public enum TypedDateDecodingError: Error { - case invalidDate -} diff --git a/Tests/TypedDateTests/TypedDateCodableTests.swift b/Tests/TypedDateTests/TypedDateCodableTests.swift new file mode 100644 index 0000000..a8a0350 --- /dev/null +++ b/Tests/TypedDateTests/TypedDateCodableTests.swift @@ -0,0 +1,37 @@ +import Foundation +import Testing +@testable import TypedDate + +@Suite +struct TypedDateCodableTests { + let calendar: Calendar + let testSupport: TypedDateTestSupport + + init() { + calendar = Calendar(identifier: .gregorian) + testSupport = TypedDateTestSupport.init(calendar: calendar) + } + + @Test + func testCodable() throws { + let typedDate = testSupport.generateTypedNanosecondDate() + let jsonEncoder = JSONEncoder() + let data = try jsonEncoder.encode(typedDate) + let decoded1 = try JSONDecoder().decode(TypedDate<(Year, Month, Day, Hour, Minute, Second, Nanosecond)>.self, from: data) + testSupport.assertTypedDate(for: decoded1) + let decoded2 = try JSONDecoder().decode(TypedDate<(Year, Month, Day, Hour, Minute, Second)>.self, from: data) + testSupport.assertTypedDate(for: decoded2) + let decoded3 = try JSONDecoder().decode(TypedDate<(Year, Month, Day, Hour, Minute)>.self, from: data) + testSupport.assertTypedDate(for: decoded3) + let decoded4 = try JSONDecoder().decode(TypedDate<(Year, Month, Day, Hour)>.self, from: data) + testSupport.assertTypedDate(for: decoded4) + let decoded5 = try JSONDecoder().decode(TypedDate<(Year, Month, Day)>.self, from: data) + testSupport.assertTypedDate(for: decoded5) + let decoded6 = try JSONDecoder().decode(TypedDate<(Year, Month)>.self, from: data) + testSupport.assertTypedDate(for: decoded6) + let decoded7 = try JSONDecoder().decode(TypedDate<(Year, Month)>.self, from: data) + testSupport.assertTypedDate(for: decoded7) + let decoded8 = try JSONDecoder().decode(TypedDate<(Year)>.self, from: data) + testSupport.assertTypedDate(for: decoded8) + } +}