【问题标题】:ISO8601DateFormatter doesn't parse ISO date stringISO8601DateFormatter 不解析 ISO 日期字符串
【发布时间】:2017-06-10 09:38:55
【问题描述】:

我正在尝试解析这个

2017-01-23T10:12:31.484Z

使用 iOS 10 提供的原生 ISO8601DateFormatter 类,但总是失败。 如果字符串不包含毫秒,则Date 对象的创建没有问题。

我尝试过这个和许多options 组合,但总是失败...

let formatter = ISO8601DateFormatter()
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.formatOptions = [.withInternetDateTime, .withDashSeparatorInDate, .withColonSeparatorInTime, .withColonSeparatorInTimeZone, .withFullTime]

有什么想法吗? 谢谢!

【问题讨论】:

标签: ios swift date swift3 ios10


【解决方案1】:

在 macOS 10.13 / iOS 11 ISO8601DateFormatter 之前不支持包含毫秒的日期字符串。

一种解决方法是使用正则表达式删除毫秒部分。

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)

在 macOS 10.13+ / iOS 11+ 中添加了一个新选项以支持小数秒:

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)

【讨论】:

  • 什么时候添加的?大约 3 周前我提交了一份雷达,因为它没有小数秒。不过,它似乎仍然不允许选择小数位数 :( openradar.appspot.com/radar?id=6095965782016000
  • 它是用macOS 10.13 / iOS 11 SDK添加的
  • 不,真的不是。 3周前还没有。必须是最近添加的。它可能支持回到 11.0,但它肯定是在过去几天才添加的。我什至向一位同事展示了它不是上周才出现的。 :)
  • 小心FractionalSeconds 崩溃到11.2。它已在 11.2+ 中修复
  • 我也为我们崩溃了,文档具有误导性,谢谢@hash3r
【解决方案2】:

对于尚未准备好使用 iOS 11 的人,您可以随时创建自己的格式化程序来处理毫秒,例如:

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

用法:

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

这比编写一个正则表达式从输入字符串中去除毫秒要优雅一些。

【讨论】:

    【解决方案3】:

    也许这将有助于解码略有不同的格式:

    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
        }
    }
    

    发件人:https://gist.github.com/th3m477/442a0d1da6354dd3b84e3b71df5dca6a

    【讨论】:

    【解决方案4】:

    几个月前我遇到了同样的问题。这是我的参考解决方案:

    // *****************************************
    // 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)
    

    【讨论】:

    • formatter.timeZone = TimeZone.current 是错误的。您认为 Apple 为什么将 ISO8601DateFormatter 时区默认设置为距 GMT 零秒?
    猜你喜欢
    • 2021-01-17
    • 2021-07-16
    • 1970-01-01
    • 1970-01-01
    • 2011-07-21
    • 1970-01-01
    • 1970-01-01
    • 2013-05-14
    相关资源
    最近更新 更多