如何创建日期时间戳并格式化为ISO 8601,RFC 3339,UTC时区?

joe*_*son 172 time date iso8601 rfc3339 swift

如何使用ISO 8601RFC 3339的格式标准生成日期时间戳?

目标是一个如下所示的字符串:

"2015-01-01T00:00:00.000Z"
Run Code Online (Sandbox Code Playgroud)

格式:

  • 年,月,日,如"XXXX-XX-XX"
  • 字母"T"作为分隔符
  • 小时,分钟,秒,毫秒,如"XX:XX:XX.XXX".
  • 字母"Z"作为零偏移的区域指示符,也就是UTC,GMT,Zulu时间.

最佳案例:

  • Swift源代码简单,简洁,直观.
  • 无需使用任何其他框架,子项目,cocoapod,C代码等.

我搜索过StackOverflow,Google,Apple等,并没有找到Swift的答案.

似乎最有前途的类NSDate,NSDateFormatter,NSTimeZone.

相关问答:如何在iOS中获得ISO 8601日期?

这是迄今为止我提出的最好的:

var now = NSDate()
var formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)
println(formatter.stringFromDate(now))
Run Code Online (Sandbox Code Playgroud)

Leo*_*bus 361

Xcode 9•Swift 4•iOS 11或更高版本

extension ISO8601DateFormatter {
    convenience init(_ formatOptions: Options, timeZone: TimeZone = TimeZone(secondsFromGMT: 0)!) {
        self.init()
        self.formatOptions = formatOptions
        self.timeZone = timeZone
    }
}
Run Code Online (Sandbox Code Playgroud)
extension Formatter {
    static let iso8601 = ISO8601DateFormatter([.withInternetDateTime, .withFractionalSeconds])
}
Run Code Online (Sandbox Code Playgroud)
extension Date {
    var iso8601: String {
        return Formatter.iso8601.string(from: self)
    }
}
Run Code Online (Sandbox Code Playgroud)
extension String {
    var iso8601: Date? {
        return Formatter.iso8601.date(from: self)
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

Date().description(with: .current)  //  Tuesday, February 5, 2019 at 10:35:01 PM Brasilia Summer Time"
let dateString = Date().iso8601   //  "2019-02-06T00:35:01.746Z"

if let date = dateString.iso8601 {
    date.description(with: .current) // "Tuesday, February 5, 2019 at 10:35:01 PM Brasilia Summer Time"
    print(date.iso8601)           //  "2019-02-06T00:35:01.746Z\n"
}
Run Code Online (Sandbox Code Playgroud)

iOS 9•Swift 3或更高版本

extension 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:ss.SSSXXXXX"
        return formatter
    }()
}
Run Code Online (Sandbox Code Playgroud)

可编程协议

如果在使用Codable协议时需要对此格式进行编码和解码,则可以创建自己的自定义日期编码/解码策略:

extension JSONDecoder.DateDecodingStrategy {
    static let iso8601withFractionalSeconds = custom {
        let container = try $0.singleValueContainer()
        let string = try container.decode(String.self)
        guard let date = Formatter.iso8601.date(from: string) else {
            throw DecodingError.dataCorruptedError(in: container,
                  debugDescription: "Invalid date: " + string)
        }
        return date
    }
}
Run Code Online (Sandbox Code Playgroud)

和编码策略

extension JSONEncoder.DateEncodingStrategy {
    static let iso8601withFractionalSeconds = custom {
        var container = $1.singleValueContainer()
        try container.encode(Formatter.iso8601.string(from: $0))
    }
}
Run Code Online (Sandbox Code Playgroud)

游乐场测试

let dates = [Date()]   // ["Feb 8, 2019 at 9:48 PM"]
Run Code Online (Sandbox Code Playgroud)

编码

let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601withFractionalSeconds
let data = try! encoder.encode(dates)
print(String(data: data, encoding: .utf8)!)
Run Code Online (Sandbox Code Playgroud)

解码

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601withFractionalSeconds
let decodedDates = try! decoder.decode([Date].self, from: data)  // ["Feb 8, 2019 at 9:48 PM"]
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

  • 不用说,如果你不需要毫秒,新的iOS 10`ISO8601DateFormatter`简化了这个过程.我已经向Apple发布了一个错误报告(27242248),要求他们扩展这个新的格式化程序以提供指定毫秒的能力(因为这个新的格式化程序在没有毫秒的情况下对我们中的许多人没用). (7认同)
  • 添加相反的转换扩展名是有用的:`extension String {var dateFormattedISO8601:NSDate?{return NSDate.Date.formatterISO8601.dateFromString(self)}}` (3认同)
  • @LeoDabus是的,但这是"Swift iso8601"的第一个结果.我的意见是为了警告未来遇到这种情况的其他开发人员,而不是针对OP. (3认同)

Rob*_*Rob 34

请记住将技术语言设置en_US_POSIX技术问答1480中所述.在Swift 3中:

let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.locale = Locale(identifier: "en_US_POSIX")
print(formatter.string(from: date))
Run Code Online (Sandbox Code Playgroud)

问题是,如果你是其中使用非公历的设备上,今年不会,除非你指定符合RFC3339/ISO8601 locale还有timeZonedateFormat字符串.

或者你可以ISO8601DateFormatter用来让你摆脱设置localetimeZone自己的杂草:

let date = Date()
let formatter = ISO8601DateFormatter()
formatter.formatOptions.insert(.withFractionalSeconds)  // this is only available effective iOS 11 and macOS 10.13
print(formatter.string(from: date))
Run Code Online (Sandbox Code Playgroud)

对于Swift 2的演绎,请参阅此答案的上一版本.

  • 好吧,您需要_some_一致的语言环境,ISO 8601 / RFC 3999标准的约定是`en_US_POSIX`提供的格式。这是用于在网络上交换日期的_lingua franca_。而且,如果保存日期字符串时在设备上使用了一个日历,而稍后又读回该字符串时,则在设备上使用了一个日历,就不会误解日期。另外,您需要一种永远不会更改的格式(这就是为什么使用`en_US_POSIX`而不是`en_US`的原因)。有关更多信息,请参阅[技术问答1480](https://developer.apple.com/library/ios/qa/qa1480/_index.html)或那些RFC / ISO标准。 (2认同)

Mat*_*ong 23

如果你想使用ISO8601DateFormatter()带有Rails 4+ JSON提要的日期(当然不需要毫秒),你需要在格式化程序上设置一些选项才能正常工作,否则该date(from: string)函数将返回nil.这是我正在使用的:

extension Date {
    init(dateString:String) {
        self = Date.iso8601Formatter.date(from: dateString)!
    }

    static let iso8601Formatter: ISO8601DateFormatter = {
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withFullDate,
                                          .withTime,
                                          .withDashSeparatorInDate,
                                          .withColonSeparatorInTime]
        return formatter
    }()
}
Run Code Online (Sandbox Code Playgroud)

以下是使用操场截图中不包含选项的结果:

在此输入图像描述


jrc*_*jrc 16

斯威夫特 5

如果你的目标的iOS 11.0+ / MacOS的10.13+,您只需使用ISO8601DateFormatterwithInternetDateTimewithFractionalSeconds选项,如下所示:

let date = Date()

let iso8601DateFormatter = ISO8601DateFormatter()
iso8601DateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let string = iso8601DateFormatter.string(from: date)

// string looks like "2020-03-04T21:39:02.112Z"
Run Code Online (Sandbox Code Playgroud)


Eli*_*rke 5

为了进一步赞美 Andrés Torres Marroquín 和 Leo Dabus,我有一个保留小数秒的版本。我在任何地方都找不到它的记录,但 Apple 将输入和输出的小数秒截断为微秒(3 位精度)(即使使用 SSSSSSS 指定,与Unicode tr35-31相反)。

我应该强调,这对于大多数用例来说可能不是必需的。在线日期通常不需要毫秒精度,当需要时,最好使用不同的数据格式。但有时必须以特定方式与预先存在的系统进行互操作。

Xcode 8/9 和 Swift 3.0-3.2

extension Date {
    struct Formatter {
        static let iso8601: DateFormatter = {
            let formatter = DateFormatter()
            formatter.calendar = Calendar(identifier: .iso8601)
            formatter.locale = Locale(identifier: "en_US_POSIX")
            formatter.timeZone = TimeZone(identifier: "UTC")
            formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXXXX"
            return formatter
        }()
    }

    var iso8601: String {
        // create base Date format 
         var formatted = DateFormatter.iso8601.string(from: self)

        // Apple returns millisecond precision. find the range of the decimal portion
         if let fractionStart = formatted.range(of: "."),
             let fractionEnd = formatted.index(fractionStart.lowerBound, offsetBy: 7, limitedBy: formatted.endIndex) {
             let fractionRange = fractionStart.lowerBound..<fractionEnd
            // replace the decimal range with our own 6 digit fraction output
             let microseconds = self.timeIntervalSince1970 - floor(self.timeIntervalSince1970)
             var microsecondsStr = String(format: "%.06f", microseconds)
             microsecondsStr.remove(at: microsecondsStr.startIndex)
             formatted.replaceSubrange(fractionRange, with: microsecondsStr)
        }
         return formatted
    }
}

extension String {
    var dateFromISO8601: Date? {
        guard let parsedDate = Date.Formatter.iso8601.date(from: self) else {
            return nil
        }

        var preliminaryDate = Date(timeIntervalSinceReferenceDate: floor(parsedDate.timeIntervalSinceReferenceDate))

        if let fractionStart = self.range(of: "."),
            let fractionEnd = self.index(fractionStart.lowerBound, offsetBy: 7, limitedBy: self.endIndex) {
            let fractionRange = fractionStart.lowerBound..<fractionEnd
            let fractionStr = self.substring(with: fractionRange)

            if var fraction = Double(fractionStr) {
                fraction = Double(floor(1000000*fraction)/1000000)
                preliminaryDate.addTimeInterval(fraction)
            }
        }
        return preliminaryDate
    }
}
Run Code Online (Sandbox Code Playgroud)


neo*_*eye 5

用途ISO8601DateFormatter上iOS10或更高版本。

用途DateFormatter上iOS9或以上。

斯威夫特 4

protocol DateFormatterProtocol {
    func string(from date: Date) -> String
    func date(from string: String) -> Date?
}

extension DateFormatter: DateFormatterProtocol {}

@available(iOS 10.0, *)
extension ISO8601DateFormatter: DateFormatterProtocol {}

struct DateFormatterShared {
    static let iso8601: DateFormatterProtocol = {
        if #available(iOS 10, *) {
            return ISO8601DateFormatter()
        } else {
            // iOS 9
            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
        }
    }()
}
Run Code Online (Sandbox Code Playgroud)