ISO8601DateFormatter doesn't parse ISO date string

前端 未结 4 957
长情又很酷
长情又很酷 2020-12-17 07:40

I\'m trying to parse this

2017-01-23T10:12:31.484Z

using native ISO8601DateFormatter class provided by iOS 1

相关标签:
4条回答
  • 2020-12-17 08:19

    Prior to macOS 10.13 / iOS 11 ISO8601DateFormatter does not support date strings including milliseconds.

    A workaround is to remove the millisecond part with regular expression.

    let isoDateString = "2017-01-23T10:12:31.484Z"
    let trimmedIsoString = isoDateString.replacingOccurrences(of: "\\.\\d+", with: "", options: .regularExpression)
    let formatter = ISO8601DateFormatter()
    let date = formatter.date(from: trimmedIsoString)
    

    In macOS 10.13+ / iOS 11+ a new option is added to support fractional seconds:

    static var withFractionalSeconds: ISO8601DateFormatter.Options { get }

    let isoDateString = "2017-01-23T10:12:31.484Z"
    let formatter = ISO8601DateFormatter()
    formatter.formatOptions =  [.withInternetDateTime, .withFractionalSeconds]
    let date = formatter.date(from: isoDateString)
    
    0 讨论(0)
  • 2020-12-17 08:19

    I encountered same issue some months ago. And here's my solution for reference:

    // *****************************************
    // MARK: - Formatter extension
    // *****************************************
    extension Formatter {
        static let iso8601: ISO8601DateFormatter = {
            let formatter = ISO8601DateFormatter()
            formatter.timeZone = TimeZone.current 
            formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
            return formatter
        }()
        static let iso8601NoSecond: ISO8601DateFormatter = {
            let formatter = ISO8601DateFormatter()
            formatter.timeZone = TimeZone.current 
            formatter.formatOptions = [.withInternetDateTime]
            return formatter
        }()
    }
    
    // *****************************************
    // MARK: - ISO8601 helper
    // *****************************************
        func getDateFrom(DateString8601 dateString:String) -> Date?
        {
            if let date = Formatter.iso8601.date(from: dateString)  {
                return date
            }
            if let date = Formatter.iso8601NoSecond.date(from: dateString)  {
                return date
            }
            return nil
        }
    
    // *****************************************
    // usage
    // *****************************************
        let d = getDateFrom(DateString8601: "2017-01-23T10:12:31.484Z")
        print("2017-01-23T10:12:31.484Z millis= ", d?.timeIntervalSinceReferenceDate)
    
        let d2 = getDateFrom(DateString8601: "2017-01-23T10:12:31Z")
        print("2017-01-23T10:12:31Z millis= ", d2?.timeIntervalSinceReferenceDate)
    
    
    // *****************************************
    // result
    // *****************************************
    2017-01-23T10:12:31.484Z millis=  Optional(506859151.48399997)
    2017-01-23T10:12:31Z millis=  Optional(506859151.0)
    
    0 讨论(0)
  • 2020-12-17 08:23

    Maybe this will help to decode slightly different formats:

    extension JSONDecoder {
        enum DateDecodeError: String, Error {
            case invalidDate
        }
    
        static var bestDateAttemptDecoder: JSONDecoder {
            let decoder = JSONDecoder()
            decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
                let container = try decoder.singleValueContainer()
                if let dateSecs = try? container.decode(Double.self) {
                    return Date(timeIntervalSince1970: dateSecs)
                }
    
                if let dateSecs = try? container.decode(UInt.self) {
                    return Date(timeIntervalSince1970: TimeInterval(dateSecs))
                }
    
                let dateStr = try container.decode(String.self)
                let isoFormatter = ISO8601DateFormatter()
                isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
                if let date = isoFormatter.date(from: dateStr) {
                    return date
                }
    
                isoFormatter.formatOptions = [.withInternetDateTime ]
                if let date = isoFormatter.date(from: dateStr) {
                    return date
                }
    
                log.warning("Cannot decode date");
                throw DateDecodeError.invalidDate
            })
    
            return decoder
        }
    }
    

    From: https://gist.github.com/th3m477/442a0d1da6354dd3b84e3b71df5dca6a

    0 讨论(0)
  • 2020-12-17 08:43

    For people that are not ready to go to iOS 11 yet, you can always create your own formatter to handle milliseconds, e.g.:

    extension DateFormatter {
        static var iSO8601DateWithMillisec: DateFormatter {
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
            return dateFormatter
        }
    }
    

    Usage:

    let formater = DateFormatter.iSO8601DateWithMillisec
    let date = formater.date(from: "2017-01-23T10:12:31.484Z")!
    print(date) // output: 2017-01-23 10:12:31 +0000
    

    It is slightly more elegant than writing a regex to strip out the milliseconds from the input string.

    0 讨论(0)
提交回复
热议问题