如何在Swift4中使用Codable将日期字符串转换为可选的小数秒

发布于 2021-01-31 23:56:51

我正在用Swift的Codable替换旧的JSON解析代码,并且遇到了一些麻烦。我想这不是一个Codable问题,而是一个DateFormatter问题。

从结构开始

 struct JustADate: Codable {
    var date: Date
 }

和一个json字符串

let json = """
  { "date": "2017-06-19T18:43:19Z" }
"""

现在让我们解码

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601

let data = json.data(using: .utf8)!
let justADate = try! decoder.decode(JustADate.self, from: data) //all good

但是,如果我们更改日期,使其具有小数秒,例如:

let json = """
  { "date": "2017-06-19T18:43:19.532Z" }
"""

现在坏了。日期有时会以小数秒返回,有时却不会。我用来解决该问题的方法是在映射代码中,我有一个转换函数,该函数尝试使用带和不带小数秒的dateFormats。我不太确定如何使用Codable处理它。有什么建议?

关注者
0
被浏览
127
1 个回答
  • 面试哥
    面试哥 2021-01-31
    为面试而生,有面试问题,就找面试哥。

    您可以使用两个不同的日期格式器(带和不带小数秒)并创建自定义的DateDecodingStrategy。如果在解析API返回的日期时失败,则可以按@PauloMattos的建议在注释中引发DecodingError:

    iOS 9,macOS 10.9,tvOS 9,watchOS 2,Xcode 9或更高版本

    自定义ISO8601DateFormatter:

    extension Formatter {
        static let iso8601withFractionalSeconds: DateFormatter = {
            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"
            return formatter
        }()
        static let iso8601: DateFormatter = {
            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:ssXXXXX"
            return formatter
        }()
    }
    

    习惯DateDecodingStrategy

    extension JSONDecoder.DateDecodingStrategy {
        static let customISO8601 = custom {
            let container = try $0.singleValueContainer()
            let string = try container.decode(String.self)
            if let date = Formatter.iso8601withFractionalSeconds.date(from: string) ?? Formatter.iso8601.date(from: string) {
                return date
            }
            throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid date: \(string)")
        }
    }
    

    习惯DateEncodingStrategy

    extension JSONEncoder.DateEncodingStrategy {
        static let customISO8601 = custom {
            var container = $1.singleValueContainer()
            try container.encode(Formatter.iso8601withFractionalSeconds.string(from: $0))
        }
    }
    

    编辑/更新

    Xcode 10•Swift 4.2或更高版本•iOS 11.2.1或更高版本

    ISO8601DateFormatter现在支持formatOptions .withFractionalSeconds

    extension Formatter {
        static let iso8601withFractionalSeconds: ISO8601DateFormatter = {
            let formatter = ISO8601DateFormatter()
            formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
            return formatter
        }()
        static let iso8601: ISO8601DateFormatter = {
            let formatter = ISO8601DateFormatter()
            formatter.formatOptions = [.withInternetDateTime]
            return formatter
        }()
    }
    

    习惯DateDecodingStrategyDateEncodingStrategy将与上面显示的相同。


    // Playground testing
    struct ISODates: Codable {
        let dateWith9FS: Date
        let dateWith3FS: Date
        let dateWith2FS: Date
        let dateWithoutFS: Date
    }
    

    let isoDatesJSON = """
    {
    "dateWith9FS": "2017-06-19T18:43:19.532123456Z",
    "dateWith3FS": "2017-06-19T18:43:19.532Z",
    "dateWith2FS": "2017-06-19T18:43:19.53Z",
    "dateWithoutFS": "2017-06-19T18:43:19Z",
    }
    """
    

    let isoDatesData = Data(isoDatesJSON.utf8)
    
    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .customISO8601
    
    do {
        let isoDates = try decoder.decode(ISODates.self, from: isoDatesData)
        print(Formatter.iso8601withFractionalSeconds.string(from: isoDates.dateWith9FS))   // 2017-06-19T18:43:19.532Z
        print(Formatter.iso8601withFractionalSeconds.string(from: isoDates.dateWith3FS))   // 2017-06-19T18:43:19.532Z
        print(Formatter.iso8601withFractionalSeconds.string(from: isoDates.dateWith2FS))   // 2017-06-19T18:43:19.530Z
        print(Formatter.iso8601withFractionalSeconds.string(from: isoDates.dateWithoutFS)) // 2017-06-19T18:43:19.000Z
    } catch {
        print(error)
    }
    


知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看