Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Swift4] ParseError, "Date string does not match format expected by formatter." when date string has not miliseconds #9178

Open
tholostaran opened this issue Feb 15, 2019 · 2 comments

Comments

@tholostaran
Copy link

Description

When CodableHelper decode a model with date, if that Date String hasn't milisecond (ex. "2019-02-14T00:00:00") the decoder throws a decode error.

Swagger-codegen version

tag -> 2.4.1 and later

Swagger declaration file content or url
  ModelV2:
    type: object
    properties:
      Name:
        type: string
      Url:
        type: string
      Description:
        type: string
      StartDate:
        format: date-time
        type: string
      EndDate:
        format: date-time
        type: string
      Type:
        enum:
          - XXXXType
          - XXXXType2
        type: string
      Size:
        type: string
      Order:
        format: int32
        type: integer
Steps to reproduce

Parse this model throws decode error:

{
   "Name": "XXXXXXX",
   "Url": "https://XXXXXX",
   "Description": "XXXXXDescription",
   "StartDate": "2019-02-14T00:00:00",
   "EndDate": "2019-02-28T00:00:00",
   "Type": "XXXXType",
   "Size": "375x255",
   "Order": 1
}
Suggest a fix/enhancement

I propouse this fix on CodableHelper.swift

Actual Code

    open class func decode<T>(_ type: T.Type, from data: Data) -> (decodableObj: T?, error: Error?) where T : Decodable {
        var returnedDecodable: T? = nil
        var returnedError: Error? = nil

        let decoder = JSONDecoder()
        if let df = self.dateformatter {
            decoder.dateDecodingStrategy = .formatted(df)
        } else {
            decoder.dataDecodingStrategy = .base64
            let formatter = DateFormatter()
            formatter.calendar = Calendar(identifier: .iso8601)
            formatter.locale = Locale(identifier: "en_US_POSIX")
            formatter.timeZone = TimeZone(secondsFromGMT: 0)
            formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
            decoder.dateDecodingStrategy = .formatted(formatter)
        }

        do {
            returnedDecodable = try decoder.decode(type, from: data)
        } catch {
            returnedError = error
        }

        return (returnedDecodable, returnedError)
    }

New fix Code

    open class func decode<T>(_ type: T.Type, from data: Data) -> (decodableObj: T?, error: Error?) where T : Decodable {
        var returnedDecodable: T? = nil
        var returnedError: Error? = nil

        let decoder = JSONDecoder()
        if let df = self.dateformatter {
            decoder.dateDecodingStrategy = .formatted(df)
        } else {
            decoder.dataDecodingStrategy = .base64
            decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
                let container = try decoder.singleValueContainer()
                let dateStr = try container.decode(String.self)
                
                let formatter = DateFormatter()
                formatter.calendar = Calendar(identifier: .iso8601)
                formatter.locale = Locale(identifier: "en_US_POSIX")
                formatter.timeZone = TimeZone(secondsFromGMT: 0)
                
                formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
                if let date = formatter.date(from: dateStr) {
                    return date
                }
                
                formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"
                if let date = formatter.date(from: dateStr) {
                    return date
                }
                
                formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssXXXXX"
                if let date = formatter.date(from: dateStr) {
                    return date
                }
                
                formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
                if let date = formatter.date(from: dateStr) {
                    return date
                }
                
                throw DateError.invalidDate
            })
        }

        do {
            returnedDecodable = try decoder.decode(type, from: data)
        } catch {
            returnedError = error
        }

        return (returnedDecodable, returnedError)
    }
@tholostaran tholostaran changed the title [Swift4] ParseError, "could not decode date" when date string has not miliseconds [Swift4] ParseError, "Date string does not match format expected by formatter." when date string has not miliseconds Feb 15, 2019
@wuf810
Copy link
Contributor

wuf810 commented Jun 6, 2019

This error is also returned for a date string that is set to null.

Whilst this is a different issue, it is worth noting for anyone coming here having searched for the error message.

@Spikuria
Copy link

Spikuria commented Jul 4, 2019

Hi,

I suggest this code to be compliant with most date formats AND allow multiple date formats in the same class :

open class func decode<T>(_ type: T.Type, from data: Data) -> (decodableObj: T?, error: Error?) where T : Decodable {
        var returnedDecodable: T? = nil
        var returnedError: Error? = nil

        let decoder = JSONDecoder()
        
        if let df = self.dateformatter {
            decoder.dateDecodingStrategy = .formatted(df)
        } else {
            decoder.dataDecodingStrategy = .base64
            decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
                let container = try decoder.singleValueContainer()
                let dateStr = try container.decode(String.self)
                
                let formatters = [
                    "yyyy-MM-dd",
                    "yyyy-MM-dd'T'HH:mm:ssZZZZZ",
                    "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ",
                    "yyyy-MM-dd'T'HH:mm:ss'Z'",
                    "yyyy-MM-dd'T'HH:mm:ss.SSS",
                    "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
                    "yyyy-MM-dd HH:mm:ss"
                    ].map { (format: String) -> DateFormatter in
                        let formatter = DateFormatter()
                        formatter.locale = Locale(identifier: "en_US_POSIX")
                        formatter.dateFormat = format
                        return formatter
                }
                
                for formatter in formatters {
                    
                    if let date = formatter.date(from: dateStr) {
                        return date
                    }
                }
                
                throw DateError.invalidDate
            })
        }
        
        do {
            returnedDecodable = try decoder.decode(type, from: data)
        } catch {
            returnedError = error
        }

        return (returnedDecodable, returnedError)
    }

Note : you have to create the DateError.invalidDate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants