diff --git a/Sources/StructuredFieldValues/Decoder/BareInnerListDecoder.swift b/Sources/StructuredFieldValues/Decoder/BareInnerListDecoder.swift index 7f12f3e..5eb4d25 100644 --- a/Sources/StructuredFieldValues/Decoder/BareInnerListDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/BareInnerListDecoder.swift @@ -72,6 +72,9 @@ extension BareInnerListDecoder: UnkeyedDecodingContainer { } else if type is Decimal.Type { let container = try self.decoder.singleValueContainer() return try container.decode(Decimal.self) as! T + } else if type is Date.Type { + let container = try self.decoder.singleValueContainer() + return try container.decode(Date.self) as! T } else { return try type.init(from: self.decoder) } diff --git a/Sources/StructuredFieldValues/Decoder/BareItemDecoder.swift b/Sources/StructuredFieldValues/Decoder/BareItemDecoder.swift index 4503d40..3136010 100644 --- a/Sources/StructuredFieldValues/Decoder/BareItemDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/BareItemDecoder.swift @@ -133,6 +133,14 @@ extension BareItemDecoder: SingleValueDecodingContainer { ) } + func decode(_: Date.Type) throws -> Date { + guard case .date(let date) = self.item else { + throw StructuredHeaderError.invalidTypeForItem + } + + return Date(timeIntervalSince1970: Double(date)) + } + func decodeNil() -> Bool { // Items are never nil. false @@ -172,6 +180,8 @@ extension BareItemDecoder: SingleValueDecodingContainer { return try self.decode(Data.self) as! T case is Decimal.Type: return try self.decode(Decimal.self) as! T + case is Date.Type: + return try self.decode(Date.self) as! T default: throw StructuredHeaderError.invalidTypeForItem } diff --git a/Sources/StructuredFieldValues/Decoder/DictionaryKeyedContainer.swift b/Sources/StructuredFieldValues/Decoder/DictionaryKeyedContainer.swift index 85f2459..20b1860 100644 --- a/Sources/StructuredFieldValues/Decoder/DictionaryKeyedContainer.swift +++ b/Sources/StructuredFieldValues/Decoder/DictionaryKeyedContainer.swift @@ -55,6 +55,9 @@ extension DictionaryKeyedContainer: KeyedDecodingContainerProtocol { } else if type is Decimal.Type { let container = try self.decoder.singleValueContainer() return try container.decode(Decimal.self) as! T + } else if type is Date.Type { + let container = try self.decoder.singleValueContainer() + return try container.decode(Date.self) as! T } else { return try type.init(from: self.decoder) } diff --git a/Sources/StructuredFieldValues/Decoder/KeyedInnerListDecoder.swift b/Sources/StructuredFieldValues/Decoder/KeyedInnerListDecoder.swift index 3a55379..5f4cb09 100644 --- a/Sources/StructuredFieldValues/Decoder/KeyedInnerListDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/KeyedInnerListDecoder.swift @@ -60,6 +60,9 @@ extension KeyedInnerListDecoder: KeyedDecodingContainerProtocol { } else if type is Decimal.Type { let container = try self.decoder.singleValueContainer() return try container.decode(Decimal.self) as! T + } else if type is Date.Type { + let container = try self.decoder.singleValueContainer() + return try container.decode(Date.self) as! T } else { return try type.init(from: self.decoder) } diff --git a/Sources/StructuredFieldValues/Decoder/KeyedItemDecoder.swift b/Sources/StructuredFieldValues/Decoder/KeyedItemDecoder.swift index 46ad78b..b46b197 100644 --- a/Sources/StructuredFieldValues/Decoder/KeyedItemDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/KeyedItemDecoder.swift @@ -60,6 +60,9 @@ extension KeyedItemDecoder: KeyedDecodingContainerProtocol { } else if type is Decimal.Type { let container = try self.decoder.singleValueContainer() return try container.decode(Decimal.self) as! T + } else if type is Date.Type { + let container = try self.decoder.singleValueContainer() + return try container.decode(Date.self) as! T } else { return try type.init(from: self.decoder) } diff --git a/Sources/StructuredFieldValues/Decoder/KeyedTopLevelListDecoder.swift b/Sources/StructuredFieldValues/Decoder/KeyedTopLevelListDecoder.swift index c06e467..af2c781 100644 --- a/Sources/StructuredFieldValues/Decoder/KeyedTopLevelListDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/KeyedTopLevelListDecoder.swift @@ -60,6 +60,9 @@ extension KeyedTopLevelListDecoder: KeyedDecodingContainerProtocol { } else if type is Decimal.Type { let container = try self.decoder.singleValueContainer() return try container.decode(Decimal.self) as! T + } else if type is Date.Type { + let container = try self.decoder.singleValueContainer() + return try container.decode(Date.self) as! T } else { return try type.init(from: self.decoder) } diff --git a/Sources/StructuredFieldValues/Decoder/ParametersDecoder.swift b/Sources/StructuredFieldValues/Decoder/ParametersDecoder.swift index 653ad48..10d35b7 100644 --- a/Sources/StructuredFieldValues/Decoder/ParametersDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/ParametersDecoder.swift @@ -55,6 +55,9 @@ extension ParametersDecoder: KeyedDecodingContainerProtocol { } else if type is Decimal.Type { let container = try self.decoder.singleValueContainer() return try container.decode(Decimal.self) as! T + } else if type is Date.Type { + let container = try self.decoder.singleValueContainer() + return try container.decode(Date.self) as! T } else { return try type.init(from: self.decoder) } diff --git a/Sources/StructuredFieldValues/Decoder/StructuredFieldValueDecoder.swift b/Sources/StructuredFieldValues/Decoder/StructuredFieldValueDecoder.swift index 267b20b..87d9b69 100644 --- a/Sources/StructuredFieldValues/Decoder/StructuredFieldValueDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/StructuredFieldValueDecoder.swift @@ -117,6 +117,9 @@ extension StructuredFieldValueDecoder { } else if type is Decimal.Type { let container = try decoder.singleValueContainer() return try container.decode(Decimal.self) as! StructuredField + } else if type is Date.Type { + let container = try decoder.singleValueContainer() + return try container.decode(Date.self) as! StructuredField } else { return try type.init(from: decoder) } diff --git a/Sources/StructuredFieldValues/Decoder/TopLevelListDecoder.swift b/Sources/StructuredFieldValues/Decoder/TopLevelListDecoder.swift index 62e3688..83fd430 100644 --- a/Sources/StructuredFieldValues/Decoder/TopLevelListDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/TopLevelListDecoder.swift @@ -72,6 +72,9 @@ extension TopLevelListDecoder: UnkeyedDecodingContainer { } else if type is Decimal.Type { let container = try self.decoder.singleValueContainer() return try container.decode(Decimal.self) as! T + } else if type is Date.Type { + let container = try self.decoder.singleValueContainer() + return try container.decode(Date.self) as! T } else { return try type.init(from: self.decoder) } diff --git a/Sources/StructuredFieldValues/Encoder/StructuredFieldValueEncoder.swift b/Sources/StructuredFieldValues/Encoder/StructuredFieldValueEncoder.swift index e34b168..3ab1ae1 100644 --- a/Sources/StructuredFieldValues/Encoder/StructuredFieldValueEncoder.swift +++ b/Sources/StructuredFieldValues/Encoder/StructuredFieldValueEncoder.swift @@ -160,6 +160,8 @@ class _StructuredFieldEncoder { try self.encode(value) } else if let value = data as? Decimal { try self.encode(value) + } else if let value = data as? Date { + try self.encode(value) } else { try data.encode(to: self) } @@ -309,6 +311,11 @@ extension _StructuredFieldEncoder: SingleValueEncodingContainer { try self.currentStackEntry.storage.insertBareItem(.decimal(pd)) } + func encode(_ data: Date) throws { + let date = Int(data.timeIntervalSince1970) + try self.currentStackEntry.storage.insertBareItem(.date(date)) + } + func encode(_ value: T) throws where T: Encodable { switch value { case let value as UInt8: @@ -343,6 +350,8 @@ extension _StructuredFieldEncoder: SingleValueEncodingContainer { try self.encode(value) case let value as Decimal: try self.encode(value) + case let value as Date: + try self.encode(value) default: throw StructuredHeaderError.invalidTypeForItem } @@ -466,6 +475,11 @@ extension _StructuredFieldEncoder { try self.currentStackEntry.storage.appendBareItem(.decimal(pd)) } + func append(_ value: Date) throws { + let date = Int(value.timeIntervalSince1970) + try self.currentStackEntry.storage.appendBareItem(.date(date)) + } + func append(_ value: T) throws where T: Encodable { switch value { case let value as UInt8: @@ -500,6 +514,8 @@ extension _StructuredFieldEncoder { try self.append(value) case let value as Decimal: try self.append(value) + case let value as Date: + try self.append(value) default: // Some other codable type. switch self.currentStackEntry.storage { @@ -636,6 +652,12 @@ extension _StructuredFieldEncoder { try self.currentStackEntry.storage.insertBareItem(.decimal(pd), atKey: key) } + func encode(_ value: Date, forKey key: String) throws { + let key = self.sanitizeKey(key) + let date = Int(value.timeIntervalSince1970) + try self.currentStackEntry.storage.insertBareItem(.date(date), atKey: key) + } + func encode(_ value: T, forKey key: String) throws where T: Encodable { let key = self.sanitizeKey(key) @@ -672,6 +694,8 @@ extension _StructuredFieldEncoder { try self.encode(value, forKey: key) case let value as Decimal: try self.encode(value, forKey: key) + case let value as Date: + try self.encode(value, forKey: key) default: // Ok, we don't know what this is. This can only happen for a dictionary, or // for anything with parameters, or for lists, or for inner lists. diff --git a/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift b/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift index 645d52e..5d14101 100644 --- a/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift +++ b/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift @@ -490,4 +490,125 @@ final class StructuredFieldDecoderTests: XCTestCase { try StructuredFieldValueDecoder().decode(DictionaryField.self, from: Array(headerField.utf8)) ) } + + func testDecodingDateAsTopLevelData() throws { + let headerField = "@4294967296" + XCTAssertEqual( + ItemField(Date(timeIntervalSince1970: 4_294_967_296)), + try StructuredFieldValueDecoder().decode(from: Array(headerField.utf8)) + ) + } + + func testDecodingDateAsParameterisedData() throws { + struct Item: StructuredFieldValue, Equatable { + static let structuredFieldType: StructuredFieldType = .item + var item: Date + var parameters: [String: Float] + } + + let headerFieldNoParameters = "@4294967296" + let headerFieldParameters = "@4294967296;q=0.8" + + XCTAssertEqual( + Item( + item: Date(timeIntervalSince1970: 4_294_967_296), + parameters: [:] + ), + try StructuredFieldValueDecoder().decode( + Item.self, + from: Array(headerFieldNoParameters.utf8) + ) + ) + + XCTAssertEqual( + Item(item: Date(timeIntervalSince1970: 4_294_967_296), parameters: ["q": 0.8]), + try StructuredFieldValueDecoder().decode( + Item.self, + from: Array(headerFieldParameters.utf8) + ) + ) + } + + func testDecodingDateInParameterField() throws { + struct Item: StructuredFieldValue, Equatable { + static let structuredFieldType: StructuredFieldType = .item + var item: Int + var parameters: [String: Date] + } + + let headerField = "1;q=@4294967296" + XCTAssertEqual( + Item(item: 1, parameters: ["q": Date(timeIntervalSince1970: 4_294_967_296)]), + try StructuredFieldValueDecoder().decode(Item.self, from: Array(headerField.utf8)) + ) + } + + func testDecodingDateInOuterListRaw() throws { + let headerField = "@4294967296, @-1659578233" + XCTAssertEqual( + List( + [ + Date(timeIntervalSince1970: 4_294_967_296), + Date(timeIntervalSince1970: -1_659_578_233), + ] + ), + try StructuredFieldValueDecoder().decode(from: Array(headerField.utf8)) + ) + } + + func testDecodingDateInInnerListRaw() throws { + let headerField = "(@4294967296 @-1659578233), (@4294967296 @-1659578233)" + XCTAssertEqual( + List( + Array( + repeating: [ + Date(timeIntervalSince1970: 4_294_967_296), + Date(timeIntervalSince1970: -1_659_578_233), + ], + count: 2 + ) + ), + try StructuredFieldValueDecoder().decode(from: Array(headerField.utf8)) + ) + } + + func testDecodingDateInInnerListKeyed() throws { + struct ListField: Codable, Equatable { + var items: [Date] + var parameters: [String: Bool] + } + let headerField = "(@4294967296 @-1659578233);foo, (@4294967296 @-1659578233);foo" + XCTAssertEqual( + List( + Array( + repeating: ListField( + items: [ + Date(timeIntervalSince1970: 4_294_967_296), + Date(timeIntervalSince1970: -1_659_578_233), + ], + parameters: ["foo": true] + ), + count: 2 + ) + ), + try StructuredFieldValueDecoder().decode(from: Array(headerField.utf8)) + ) + } + + func testDecodingDateInDictionaries() throws { + struct DictionaryField: StructuredFieldValue, Equatable { + static let structuredFieldType: StructuredFieldType = .dictionary + var bin: Date + var box: Date + } + + let headerField = "bin=@4294967296, box=@-1659578233" + XCTAssertEqual( + DictionaryField( + bin: Date(timeIntervalSince1970: 4_294_967_296), + box: Date(timeIntervalSince1970: -1_659_578_233) + ), + try StructuredFieldValueDecoder().decode(from: Array(headerField.utf8)) + ) + } } diff --git a/Tests/StructuredFieldValuesTests/StructuredFieldEncoderTests.swift b/Tests/StructuredFieldValuesTests/StructuredFieldEncoderTests.swift index e660fcd..af12517 100644 --- a/Tests/StructuredFieldValuesTests/StructuredFieldEncoderTests.swift +++ b/Tests/StructuredFieldValuesTests/StructuredFieldEncoderTests.swift @@ -43,6 +43,16 @@ final class StructuredFieldEncoderTests: XCTestCase { // Binary Data XCTAssertEqual(Array(":AQIDBA==:".utf8), try encoder.encode(ItemField(Data([1, 2, 3, 4])))) + + // Date + XCTAssertEqual( + Array("@4294967296".utf8), + try encoder.encode(ItemField(Date(timeIntervalSince1970: 4_294_967_296))) + ) + XCTAssertEqual( + Array("@-1659578233".utf8), + try encoder.encode(ItemField(Date(timeIntervalSince1970: -1_659_578_233))) + ) } func testEncodeKeyedItemHeader() throws { @@ -96,6 +106,26 @@ final class StructuredFieldEncoderTests: XCTestCase { Array(":AQIDBA==:".utf8), try encoder.encode(KeyedItem(item: Data([1, 2, 3, 4]), parameters: [:])) ) + + // Date + XCTAssertEqual( + Array("@4294967296;x".utf8), + try encoder.encode( + KeyedItem( + item: Date(timeIntervalSince1970: 4_294_967_296), + parameters: ["x": true] + ) + ) + ) + XCTAssertEqual( + Array("@-1659578233".utf8), + try encoder.encode( + KeyedItem( + item: Date(timeIntervalSince1970: -1_659_578_233), + parameters: [:] + ) + ) + ) } func testEncodeKeyedItemHeaderWithParamsAsStruct() throws { @@ -189,6 +219,19 @@ final class StructuredFieldEncoderTests: XCTestCase { Array(":AQIDBA==:, :BQYHCA==:".utf8), try encoder.encode(List([Data([1, 2, 3, 4]), Data([5, 6, 7, 8])])) ) + + // Date + XCTAssertEqual( + Array("@4294967296, @-1659578233".utf8), + try encoder.encode( + List( + [ + Date(timeIntervalSince1970: 4_294_967_296), + Date(timeIntervalSince1970: -1_659_578_233), + ] + ) + ) + ) } func testListFieldInnerItemsWithDict() throws {