Search code examples
swiftdatedecode

Decode yyyy-MM-dd date


I am trying to parse a "yyyy-MM-dd" date string but it always returns the date with hours.

struct Foo: Decodable {
    let date: Date
}
let string = """
        {"date": "2023-10-19"}
        """
let data = Data(string.utf8)
let foo: Foo = try parse(data: data)

let formatter = DateFormatter()      
formatter.dateFormat = "yyyy-MM-dd"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)

let swiftObject = try decoder.decode(T.self, from: data)

Returns 2000-01-01 00:00:00 +0000

JSON: 
data: {
     date: 2023-10-19 
}

Solution

  • So, there are two questions here:

    1. How come when you print the Date object, you see hours, minutes and seconds, too?

      That is because when you print a Date object it always includes that information, in that format. If you want to display a Date object in your UI, though, you would use a separate UI-specific date formatter for that.

    2. How come when you parse a JSON with the date of “2023-10-19” you ended up with a date of 2000-01-01?

      This must be a mistake introduced when composing the question, because there is no way you would have ended up with that date given that input.

      That having been said, you must set the locale of the formatter in case your user is using a device which is not using a Gregorian calendar. By default, we use the en_US_POSIX locale when encoding/decoding JSON, just to make sure we’re using a consistent format. See TN1480. That is for Objective-C, but it outlines the locale concerns.

    Thus, we end up with:

    struct Foo: Decodable {
        let date: Date
    }
    
    func parse<T: Decodable>(data: Data) throws -> T {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "yyyy-MM-dd"
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .formatted(formatter)
    
        return try decoder.decode(T.self, from: data)
    }
    
    do {
        let string = """
            {"date": "2023-10-19"}
            """
        let data = Data(string.utf8)
        let foo: Foo = try parse(data: data)
        print(foo.date)                         // 2023-10-19 07:00:00 +0000; this is just the default description for the `Date` type
        
        let formatter = DateFormatter()
        formatter.dateStyle = .medium           // note, we generally avoid using `dateFormat` strings for date formatters for our UI, but rather `dateStyle`
        print(formatter.string(from: foo.date)) // Oct 19, 2023
    } catch {
        print(error)
    }