Skip to content

Instantly share code, notes, and snippets.

@hishma
Last active February 10, 2020 22:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hishma/f7c0dff762866735b29679081b39cd06 to your computer and use it in GitHub Desktop.
Save hishma/f7c0dff762866735b29679081b39cd06 to your computer and use it in GitHub Desktop.

TL;DR

If you are using the iso8601 strategy, you may not want to rely on the default Equatable implementation for Date (==) when comparing dates. You can use a good old fashion calendar comparison instead.

Strategies

JSONEncoder and JSONDecoder can both be configured with a strategy (dateEncodingStrategy and dateDecodingStrategy) for encoding and decoding dates. There are four named types in addition to allowing you to provide custom options.

Strategy Comment
deferredToDate Defer to Date for choosing an encoding. This is the default strategy.
millisecondsSince1970 Encode the Date as UNIX millisecond timestamp (as a JSON number).
secondsSince1970 Encode the Date as a UNIX timestamp (as a JSON number).
iso8601 Encode the Date as an ISO-8601-formatted string (in RFC 3339 format).

The defaut is .deferredToDate which uses a reference date of January 1st, 2001.

Examples of the JSON generated for each strategy when encoding a date:

Strategy JSON Example
deferredToDate {"date":586761866.88579297}
millisecondsSince1970 {"date":1565069066885.793}
secondsSince1970 {"date":1565069066.885793}
iso8601 {"date":"2019-08-06T05:24:26Z"}

Comparison

After encoding and decoding the data back into a Date:

Strategy timeIntervalSinceReferenceDate timeIntervalSince1970 hashValue
Original Date 586761866.885793 1565069066.885793 -549226158746601189
deferredToDate 586761866.885793 1565069066.885793 -549226158746601189
millisecondsSince1970 586761866.885793 1565069066.885793 -549226158746601189
secondsSince1970 586761866.885793 1565069066.885793 -549226158746601189
iso8601 586761866.0 1565069066.0 1557521680301490026

Comparing the original date to the decoded date using both ==, and a calandar comparison:

Strategy == orderedSame
deferredToDate true true
millisecondsSince1970 true true
secondsSince1970 true true
iso8601 false true

date == decodedDate Calendar.current.compare(date, to: decodedDate, toGranularity: .nanosecond) == .orderedSame) : orderedSame

Code

Sample code I used

struct StrategeryResult {
    let date: Date
    let encodeStrategy: JSONEncoder.DateEncodingStrategy
    let decodeStrategy: JSONDecoder.DateDecodingStrategy
    
    let data: Data
    let json: String
    let decodedDate: Date
    
    init(date: Date, encodeStrategy: JSONEncoder.DateEncodingStrategy, decodeStrategy: JSONDecoder.DateDecodingStrategy) throws {
    	// simple container to encode/decode with
        struct Foo: Codable {
            let date: Date
        }
        
        let foo = Foo(date: date)
        
        self.date = date
        self.encodeStrategy = encodeStrategy
        self.decodeStrategy = decodeStrategy
        
        let encoder = JSONEncoder()
        encoder.dateEncodingStrategy = encodeStrategy
        self.data = try encoder.encode(foo)
        
        self.json = String(data: data, encoding: .utf8)!
        
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = decodeStrategy
        let decodedFoo = try decoder.decode(Foo.self, from: data)
        self.decodedDate = decodedFoo.date
    }
}

extension StrategeryResult: CustomDebugStringConvertible {
    var debugDescription: String {
        var components: [String] = ["StrategeryResult:"]
        
        components.append("\(encodeStrategy) : encodeStrategy")
        components.append("\(decodeStrategy) : decodeStrategy")
        components.append("\(date) : date")
        components.append("\(decodedDate) : decodedDate")
        components.append("\(json) : json")
        components.append("\(date.hashValue) : date.hashValue")
        components.append("\(decodedDate.hashValue) : decodedDate.hashValue")
        components.append("\(date.timeIntervalSinceReferenceDate) : date.timeIntervalSinceReferenceDate")
        components.append("\(decodedDate.timeIntervalSinceReferenceDate) : decodedDate.timeIntervalSinceReferenceDate")
        components.append("\(date.timeIntervalSince1970) : date.timeIntervalSince1970")
        components.append("\(decodedDate.timeIntervalSince1970) : decodedDate.timeIntervalSince1970")
        components.append("\(date == decodedDate) : Equatable (==)")
        components.append("\(Calendar.current.compare(date, to: decodedDate, toGranularity: .nanosecond) == .orderedSame) : orderedSame")
            
        return components.joined(separator: "\n * ")
    }
}

// Usage:

let now = Date()

var results = try! StrategeryResult(date: now, encodeStrategy: .iso8601, decodeStrategy: .iso8601)

print(String(reflecting: results))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment