如何在 Swift 中使用 Codable 转换带有可选小数秒的日期字符串? [英] How to convert a date string with optional fractional seconds using Codable in Swift?

查看:25
本文介绍了如何在 Swift 中使用 Codable 转换带有可选小数秒的日期字符串?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am replacing my old JSON parsing code with Swift's Codable and am running into a bit of a snag. I guess it isn't as much a Codable question as it is a DateFormatter question.

从结构开始

 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

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

But if we change the date so that it has fractional seconds, for example:

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

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

Now it breaks. The dates sometimes come back with fractional seconds and sometimes do not. The way I used to solve it was in my mapping code I had a transform function that tried both dateFormats with and without the fractional seconds. I am not quite sure how to approach it using Codable however. Any suggestions?

推荐答案

您可以使用两种不同的日期格式化程序(带和不带小数秒)并创建自定义 DateDecodingStrategy.如果解析 API 返回的日期失败,您可以按照@PauloMattos 在评论中的建议抛出 DecodingError:

You can use two different date formatters (with and without fraction seconds) and create a custom DateDecodingStrategy. In case of failure when parsing the date returned by the API you can throw a DecodingError as suggested by @PauloMattos in comments:

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

自定义ISO8601 DateFormatter:

The custom ISO8601 DateFormatter:

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 的习惯如上所示.


The customs DateDecodingStrategy and DateEncodingStrategy would be the same as shown above.

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

这篇关于如何在 Swift 中使用 Codable 转换带有可选小数秒的日期字符串?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆