Skip to content

Commit 5d662e9

Browse files
authored
CBL-7429 : Swift Codable decoding fails for some ISO8601 formats (Port) (#3450)
* Port the fix from release/3.2 branch for CBL-7061. * Fix Codable ISO8601 format * Add test to decode Swift ISO8601 default format * Co-authored-by: callumbirks <[email protected]>
1 parent 386416e commit 5d662e9

File tree

3 files changed

+65
-3
lines changed

3 files changed

+65
-3
lines changed

Swift/FleeceDecoder.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,12 @@ private struct SingleValueContainer: SingleValueDecodingContainer {
328328
func decode(_ type: Date.Type) throws -> Date {
329329
switch decoder.fleeceValue {
330330
case .string(let string):
331-
if let date = ISO8601DateFormatter().date(from: string) {
331+
if let date = ISO8601DateFormatter.couchbase.date(from: string) {
332+
return date
333+
} else if let date = ISO8601DateFormatter().date(from: string) {
334+
// CBL-7061. Because of an issue introduced in 3.2.3 which used the default formatter for dates, some customers may have dates in their documents which are
335+
// encoded using the default formatter (rather than the `.couchbase` formatter).
336+
// We can remove this extra check once we are sure no customers have default formatter dates in their databases.
332337
return date
333338
} else {
334339
throw CBLError.create(CBLError.decodingError, description: "Failed to parse ISO8601 Date from '\(string)'")
@@ -525,3 +530,16 @@ enum FleeceValue {
525530
}
526531
}
527532
}
533+
534+
extension ISO8601DateFormatter {
535+
/// The variation of ISO8601 used by Couchbase.
536+
/// YYYY-mm-ddThh:mm:ss.SSSZ
537+
static let couchbase: ISO8601DateFormatter = {
538+
let formatter = ISO8601DateFormatter()
539+
formatter.formatOptions = [
540+
.withInternetDateTime,
541+
.withFractionalSeconds
542+
]
543+
return formatter
544+
}()
545+
}

Swift/FleeceEncoder.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,7 @@ internal class FleeceEncoder : Encoder {
9090
case is Blob:
9191
try _writeNSObject((value as! Blob).impl)
9292
case is Date:
93-
let formatter = ISO8601DateFormatter()
94-
let string = formatter.string(from: value as! Date)
93+
let string = ISO8601DateFormatter.couchbase.string(from: value as! Date)
9594
try _writeNSObject(string as NSString)
9695
case is NSObject:
9796
try _writeNSObject(value as! NSObject)

Swift/Tests/CodableTest.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,51 @@ class CodableTest: CBLTestCase {
766766
XCTAssertEqual(loadedReportFile.report.body, body)
767767
}
768768

769+
// CBL-7061
770+
// Test that Codable can decode Date when it was encoded using Document methods.
771+
func testCollectionDecodeISO8601Date() throws {
772+
// Create a ReportFile document
773+
let body = Blob(contentType: "text/plain", data: Data("Hello, World!".utf8))
774+
let report = MutableDictionaryObject()
775+
report.setValue(NSNull(), forKey: "id")
776+
report.setString("My Report", forKey: "title")
777+
report.setBoolean(false, forKey: "filed")
778+
report.setBlob(body, forKey: "body")
779+
let document = MutableDocument()
780+
let now = Date()
781+
document.setDate(now, forKey: "dateFiled")
782+
document.setDictionary(report, forKey: "report")
783+
// Save to the default collection
784+
try defaultCollection!.save(document: document)
785+
// Load the object from the collection
786+
let reportFile = try defaultCollection!.document(id: document.id, as: ReportFile.self)!
787+
// Assert the loaded object Date is identical to the source (to accuracy of 1 millisecond)
788+
XCTAssertEqual(reportFile.dateFiled.timeIntervalSince1970, now.timeIntervalSince1970, accuracy: 0.001)
789+
}
790+
791+
// CBL-7061
792+
// Test that Codable can decode Date when it was encoded using Swift's default ISO8601 formatter.
793+
func testCollectionDecodeISO8601DateWithDefaultFormatter() throws {
794+
// Create a ReportFile document
795+
let body = Blob(contentType: "text/plain", data: Data("Hello, World!".utf8))
796+
let report = MutableDictionaryObject()
797+
report.setValue(NSNull(), forKey: "id")
798+
report.setString("My Report", forKey: "title")
799+
report.setBoolean(false, forKey: "filed")
800+
report.setBlob(body, forKey: "body")
801+
let document = MutableDocument()
802+
let now = Date()
803+
let nowString = ISO8601DateFormatter().string(from: now)
804+
document.setString(nowString, forKey: "dateFiled")
805+
document.setDictionary(report, forKey: "report")
806+
// Save to the default collection
807+
try defaultCollection!.save(document: document)
808+
// Load the object from the collection
809+
let reportFile = try defaultCollection!.document(id: document.id, as: ReportFile.self)!
810+
// Assert the loaded object Date is identical to the source (to accuracy of 1 second)
811+
XCTAssertEqual(reportFile.dateFiled.timeIntervalSince1970, now.timeIntervalSince1970, accuracy: 1)
812+
}
813+
769814
// 23. TestCollectionEncodeAndDecodeArrayNestedBlob
770815
func testCollectionEncodeAndDecodeArrayNestedBlob() throws {
771816
// 1. Create a Note object with array of 3 blobs

0 commit comments

Comments
 (0)