如何在 Swift 中解析/创建以小数秒 UTC 时区(ISO 8601、RFC 3339)格式化的日期时间戳?

How can I parse / create a date time stamp formatted with fractional seconds UTC timezone (ISO 8601, RFC 3339) in Swift?

提问人:joelparkerhenderson 提问时间:1/19/2015 最后编辑:Communityjoelparkerhenderson 更新时间:11/20/2023 访问量:177126

问:

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

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

"2015-01-01T00:00:00.000Z"

格式:

  • 年、月、日,如“XXXX-XX-XX”
  • 字母“T”作为分隔符
  • 时、分、秒、毫秒,如“XX:XX:XX.XXX”。
  • 字母“Z”作为零偏移的区域指示符,又名 UTC、GMT、祖鲁时间。

最佳案例:

  • 简单、简短、直接的 Swift 源代码。
  • 无需使用任何额外的框架、子项目、cocoapod、C 代码等。

我搜索了 StackOverflow、Google、Apple 等,但没有找到 Swift 的答案。

看起来最有前途的类是 NSDate、NSDateFormatterNSTimeZone

相关问答:如何在 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))
日期 SWIFT TIME ISO8601 RFC3339

评论

7赞 Fattie 4/27/2017
请注意,iOS10+ 仅包含内置的 ISO 8601 ..它只会为您自动完成。
3赞 smat88dd 6/9/2017
@Fattie 而且 - 它如何处理时间戳的最后 .234Z 毫秒 Zulu/UTC 部分?答案:马特·朗斯@stackoverflow.com/a/42101630/3078330
1赞 Fattie 6/10/2017
@smat88dd -- 很棒的提示,谢谢。我不知道有“格式化程序上的选项”,奇怪而狂野!
0赞 neoneye 9/20/2018
我正在寻找一种适用于 linux 的解决方案。
0赞 Leo Dabus 10/31/2018
@neoneye 只需使用旧版本(普通 DateFormatter)并将日历 iso8601 更改为公历 stackoverflow.com/a/28016692/2303865

答:

58赞 Rob 1/19/2015 #1

请记住按照技术 Q&A1480 中的说明设置区域设置。在 Swift 3 中:en_US_POSIX

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

问题是,如果您使用的是使用非公历的设备,则年份将不符合 RFC3339/ISO8601,除非您指定 以及 和 字符串。localetimeZonedateFormat

或者你可以用它来让你摆脱环境和你自己的杂草:ISO8601DateFormatterlocaletimeZone

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

有关 Swift 2 演绎版,请参阅此答案的先前修订版。

评论

0赞 axunic 12/13/2017
为什么我们应该将语言环境设置为en_US_POSIX?即使我们不在美国?
2赞 Rob 12/13/2017
好吧,您需要一些一致的语言环境,而 ISO 8601/RFC 3999 标准的约定就是 提供的格式。这是在网络上交换日期的通用语言。而且,如果在保存日期字符串时在设备上使用了一个日历,而在以后读回该字符串时使用另一个日历,则不能误解日期。此外,您需要一种保证永远不会改变的格式(这就是您使用而不是 ).有关详细信息,请参阅技术 Q&A 1480 或那些 RFC/ISO 标准。en_US_POSIXen_US_POSIXen_US
0赞 Rob S. 12/27/2022
.withFractionalSeconds- 这是我支持处理毫秒所需的。谢谢!
484赞 Leo Dabus 1/19/2015 #2

Swift 5.5 • iOS 15 • Xcode 13 或更高版本

extension Date.ISO8601FormatStyle {
    static let iso8601withFractionalSeconds: Self = .init(includingFractionalSeconds: true)
}

extension ParseStrategy where Self == Date.ISO8601FormatStyle {
    static var iso8601withFractionalSeconds: Date.ISO8601FormatStyle { .iso8601withFractionalSeconds }
}

extension FormatStyle where Self == Date.ISO8601FormatStyle {
    static var iso8601withFractionalSeconds: Date.ISO8601FormatStyle { .iso8601withFractionalSeconds }
}

extension Date {

    init(iso8601withFractionalSeconds parseInput: ParseStrategy.ParseInput) throws {
        try self.init(parseInput, strategy: .iso8601withFractionalSeconds)
    }

    var iso8601withFractionalSeconds: String {
        formatted(.iso8601withFractionalSeconds)
    }
}

extension String {
    func iso8601withFractionalSeconds() throws -> Date {
        try .init(iso8601withFractionalSeconds: self)
    }
}

extension JSONDecoder.DateDecodingStrategy {
    static let iso8601withFractionalSeconds = custom {
        try .init(iso8601withFractionalSeconds: $0.singleValueContainer().decode(String.self))
    }
}

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

用法:

let date: Date = .now  // "19 Nov 2023 at 11:29 PM"
date.description(with: .current)  // "Sunday, 19 November 2023 at 11:29:40 PM Brasilia Standard Time"
let dateString = date.iso8601withFractionalSeconds  // "2023-11-20T02:29:40.920Z"

if let date = try? dateString.iso8601withFractionalSeconds() {
    date.description(with: .current) // "Sunday, 19 November 2023 at 11:29:40 PM Brasilia Standard Time"
    print(date.iso8601withFractionalSeconds)  // "2023-11-20T02:29:40.920Z\n"
}


Swift 4 • iOS 11.2.1 或更高版本

extension ISO8601DateFormatter {
    convenience init(_ formatOptions: Options) {
        self.init()
        self.formatOptions = formatOptions
    }
}

extension Formatter {
    static let iso8601withFractionalSeconds = ISO8601DateFormatter([.withInternetDateTime, .withFractionalSeconds])
}

extension Date {
    var iso8601withFractionalSeconds: String { return Formatter.iso8601withFractionalSeconds.string(from: self) }
}

extension String {
    var iso8601withFractionalSeconds: Date? { return Formatter.iso8601withFractionalSeconds.date(from: self) }
}

用法:

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

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

let dates: [Date] = [.now]

let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601withFractionalSeconds
let data = try! encoder.encode(dates)
print(String(data: data, encoding: .utf8)!)  // "["2023-11-20T02:11:29.158Z"]\n"

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601withFractionalSeconds
let decodedDates = try! decoder.decode([Date].self, from: data)
print(decodedDates)   // "[2023-11-20 02:11:29 +0000]\n"

iOS 9 • Swift 3 或更高版本

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

可编码协议

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

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

和编码策略

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

操场测试

let dates = [Date()]   // ["Feb 8, 2019 at 9:48 PM"]

编码

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

译码

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

enter image description here

评论

3赞 Nat 5/30/2016
添加相反的转换扩展会很有用:extension String { var dateFormattedISO8601: NSDate? {return NSDate.Date.formatterISO8601.dateFromString(self)} }
1赞 pixelrevision 6/19/2016
请注意,这会降低一点精度,因此确保通过生成的字符串而不是 timeInterval 来比较日期的相等性非常重要。let now = NSDate() let stringFromDate = now.iso8601 let dateFromString = stringFromDate.dateFromISO8601! XCTAssertEqual(now.timeIntervalSince1970, dateFromString.timeIntervalSince1970)
1赞 manRo 9/29/2016
RFC3339中,我们可以找到一个注释“注意:ISO 8601定义了用”T“分隔的日期和时间。为了可读性,使用此语法的应用程序可以选择指定一个用空格字符分隔的完整日期和完整时间。它是否也涵盖了日期格式,而没有例如:?T2016-09-21 21:05:10+00:00
1赞 thislooksfun 7/4/2017
这在 LINUX 上不起作用。如果您也以 Linux 为目标,则需要删除该行,否则它将出现段错误并崩溃。Calendar(identifier: .iso8601)
3赞 thislooksfun 7/5/2017
@LeoDabus是的,但这是“Swift iso8601”的第一个结果。我的评论旨在警告将来遇到这种情况的其他开发人员,而不是针对 OP。
3赞 ioopl 4/15/2016 #3

就我而言,我必须将 DynamoDB - lastUpdated 列(Unix 时间戳)转换为正常时间。

lastUpdated 的初始值为: 1460650607601 - 通过以下方式转换为 2016-04-14 16:16:47 +0000:

   if let lastUpdated : String = userObject.lastUpdated {

                let epocTime = NSTimeInterval(lastUpdated)! / 1000 // convert it from milliseconds dividing it by 1000

                let unixTimestamp = NSDate(timeIntervalSince1970: epocTime) //convert unix timestamp to Date
                let dateFormatter = NSDateFormatter()
                dateFormatter.timeZone = NSTimeZone()
                dateFormatter.locale = NSLocale.currentLocale() // NSLocale(localeIdentifier: "en_US_POSIX")
                dateFormatter.dateFormat =  "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
                dateFormatter.dateFromString(String(unixTimestamp))

                let updatedTimeStamp = unixTimestamp
                print(updatedTimeStamp)

            }
1赞 Andrés Torres Marroquín 11/4/2016 #4

为了补充 Leo Dabus 的版本,我添加了对 Swift 和 Objective-C 编写项目的支持,还添加了对可选毫秒的支持,这可能不是最好的,但你会明白的:

Xcode 8 和 Swift 3

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(secondsFromGMT: 0)
            formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
            return formatter
        }()
    }

    var iso8601: String {
        return Formatter.iso8601.string(from: self)
    }
}


extension String {
    var dateFromISO8601: Date? {
        var data = self
        if self.range(of: ".") == nil {
            // Case where the string doesn't contain the optional milliseconds
            data = data.replacingOccurrences(of: "Z", with: ".000000Z")
        }
        return Date.Formatter.iso8601.date(from: data)
    }
}


extension NSString {
    var dateFromISO8601: Date? {
        return (self as String).dateFromISO8601
    }
}
3赞 Gary Davies 12/5/2016 #5

将来可能需要更改格式,这可能是一个小小的头疼,因为 date.dateFromISO8601 在应用程序中的任何地方都调用。使用类和协议来包装实现,在一个地方更改日期时间格式调用会更简单。如果可能,请使用RFC3339,这是一个更完整的表示形式。DateFormatProtocol 和 DateFormat 非常适合依赖项注入。

class AppDelegate: UIResponder, UIApplicationDelegate {

    internal static let rfc3339DateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
    internal static let localeEnUsPosix = "en_US_POSIX"
}

import Foundation

protocol DateFormatProtocol {

    func format(date: NSDate) -> String
    func parse(date: String) -> NSDate?

}


import Foundation

class DateFormat:  DateFormatProtocol {

    func format(date: NSDate) -> String {
        return date.rfc3339
    }

    func parse(date: String) -> NSDate? {
        return date.rfc3339
    }

}


extension NSDate {

    struct Formatter {
        static let rfc3339: NSDateFormatter = {
            let formatter = NSDateFormatter()
            formatter.calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierISO8601)
            formatter.locale = NSLocale(localeIdentifier: AppDelegate.localeEnUsPosix)
            formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)
            formatter.dateFormat = rfc3339DateFormat
            return formatter
        }()
    }

    var rfc3339: String { return Formatter.rfc3339.stringFromDate(self) }
}

extension String {
    var rfc3339: NSDate? {
        return NSDate.Formatter.rfc3339.dateFromString(self)
    }
}



class DependencyService: DependencyServiceProtocol {

    private var dateFormat: DateFormatProtocol?

    func setDateFormat(dateFormat: DateFormatProtocol) {
        self.dateFormat = dateFormat
    }

    func getDateFormat() -> DateFormatProtocol {
        if let dateFormatObject = dateFormat {

            return dateFormatObject
        } else {
            let dateFormatObject = DateFormat()
            dateFormat = dateFormatObject

            return dateFormatObject
        }
    }

}
47赞 Matt Long 2/8/2017 #6

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

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

以下是使用选项与不在 Playground 屏幕截图中的结果:

enter image description here

评论

0赞 Leo Dabus 10/12/2017
您还需要在选项中包括 但我已经尝试过了,它不断抛出错误。.withFractionalSecondslibc++abi.dylib: terminating with uncaught exception of type NSException
0赞 Matt Long 11/7/2017
@MEnnabah 它在 Swift 4 中对我来说效果很好。你遇到错误了吗?
0赞 freeman 12/13/2017
@LeoDabus,遇到了和你一样的错误,你解决了吗?
0赞 Leo Dabus 12/13/2017
自定义 JSONDecoder DateDecodingStrategy stackoverflow.com/a/46458771/2303865
0赞 Leo Dabus 12/13/2017
@freeman 如果您想保留日期及其所有小数秒,我建议在将日期保存/接收到服务器时使用双倍(自参考日期以来的时间间隔)。并在使用 Codable 协议时使用默认的日期解码策略.deferredToDate
5赞 Eli Burke 4/14/2017 #7

为了进一步赞美安德烈斯·托雷斯·马罗金和里奥·达布斯,我有一个保留了小数秒的版本。我在任何地方都找不到它的记录,但 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
    }
}

评论

0赞 Michael A. McCloskey 9/30/2017
在我看来,这是最好的答案,因为它允许人们达到微秒级的精度,而所有其他解决方案都以毫秒为单位截断。
0赞 Leo Dabus 12/13/2017
如果要保留日期及其所有小数秒,则在将日期保存/接收到服务器时,应仅使用双倍(自参考日期以来的时间间隔)。
0赞 Eli Burke 12/14/2017
@LeoDabus是的,如果您控制整个系统并且不需要互操作。就像我在答案中说的,这对大多数用户来说不是必需的。但是,我们并不总是能够控制 Web API 中的数据格式,并且由于 Android 和 Python(至少)保留了 6 位小数精度,因此有时有必要效仿。
0赞 Jay Lee 3/23/2023
这有一个严重的错误,微秒几乎接近,并且值的 FP 表示不精确。请在此处查看修订后的答案1
3赞 Thomas Szabo 6/28/2017 #8

有一个新类,可让您创建一个只有一行的字符串。为了向后兼容,我使用了旧的 C 库。我希望这对某人有用。ISO8601DateFormatter

斯威夫特 3.0

extension Date {
    var iso8601: String {
        if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
            return ISO8601DateFormatter.string(from: self, timeZone: TimeZone.current, formatOptions: .withInternetDateTime)
        } else {
            var buffer = [CChar](repeating: 0, count: 25)
            var time = time_t(self.timeIntervalSince1970)
            strftime_l(&buffer, buffer.count, "%FT%T%z", localtime(&time), nil)
            return String(cString: buffer)
        }
    }
}
5赞 neoneye 3/8/2018 #9

在 iOS10 或更高版本上使用。ISO8601DateFormatter

在 iOS9 或更早版本上使用。DateFormatter

斯威夫特 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
        }
    }()
}
-1赞 Dmitrii Z 12/4/2018 #10

无需一些手动字符串掩码或 TimeFormatters

import Foundation

struct DateISO: Codable {
    var date: Date
}

extension Date{
    var isoString: String {
        let encoder = JSONEncoder()
        encoder.dateEncodingStrategy = .iso8601
        guard let data = try? encoder.encode(DateISO(date: self)),
        let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as?  [String: String]
            else { return "" }
        return json?.first?.value ?? ""
    }
}

let dateString = Date().isoString

评论

0赞 Stefan Arentz 5/25/2020
这是一个很好的答案,但使用将不包括毫秒。.iso8601
31赞 jrc 3/5/2020 #11

斯威夫特 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"
0赞 aaronium112 3/25/2020 #12

基于对象范式中可接受的答案

class ISO8601Format
{
    let format: ISO8601DateFormatter

    init() {
        let format = ISO8601DateFormatter()
        format.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        format.timeZone = TimeZone(secondsFromGMT: 0)!
        self.format = format
    }

    func date(from string: String) -> Date {
        guard let date = format.date(from: string) else { fatalError() }
        return date
    }

    func string(from date: Date) -> String { return format.string(from: date) }
}


class ISO8601Time
{
    let date: Date
    let format = ISO8601Format() //FIXME: Duplication

    required init(date: Date) { self.date = date }

    convenience init(string: String) {
        let format = ISO8601Format() //FIXME: Duplication
        let date = format.date(from: string)
        self.init(date: date)
    }

    func concise() -> String { return format.string(from: date) }

    func description() -> String { return date.description(with: .current) }
}

呼叫站点

let now = Date()
let time1 = ISO8601Time(date: now)
print("time1.concise(): \(time1.concise())")
print("time1: \(time1.description())")


let time2 = ISO8601Time(string: "2020-03-24T23:16:17.661Z")
print("time2.concise(): \(time2.concise())")
print("time2: \(time2.description())")
4赞 scuac 8/17/2022 #13

现在是 2022 年,但我正在寻找这个问题的答案(即如何将日期转换为包含秒数的ISO8601)。事实证明,现在的答案是一句话:

var somedate: Date = Date.now
var isodate = somedate.ISO8601Format(Date.ISO8601FormatStyle(includingFractionalSeconds: true))

所以这将打印类似的东西2022-08-16T17:45:08.548Z

评论

0赞 Duncan C 1/8/2023
好!我不知道.(已投票)ISOFormatStyle(includingFractionalSeconds:)
0赞 Ammaiappan 8/4/2023
感谢 fractionSec,我投票了。Locale(标识符: “en_US_POSIX”) 在 “ISO8601Format” 中可用?
2赞 Jay Lee 3/23/2023 #14

下面的代码保留了微秒精度。 这是基于这个答案的,但这实际上会损失微秒的精度,并导致转换之间的有损转换,例如,与该答案一起变。Date-String.065005.065004

更严重的是,具有几乎一个亚秒位数的日期会使用该代码“添加”整整一秒,例如,变成 . (我很难发现这一点。xx[d].999500xx[d+1].999500

正确的版本(使用最新的 API)如下所示:String

final class PrecisionISO8601DateFormatter: DateFormatter {
    override init() {
        super.init()
        calendar = Calendar(identifier: .iso8601)
        locale = Locale(identifier: "en_US_POSIX")
        timeZone = TimeZone(identifier: "UTC")
        dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXXXX"
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func string(from date: Date) -> String {
        // Directly converting with `super` introduces errors; `.999500` adds a second.
        var string = super.string(
            from: Date(timeIntervalSince1970: floor(date.timeIntervalSince1970))
        )

        if let fractionStart = string.range(of: "."),
           let fractionEnd = string
            .index(fractionStart.lowerBound, offsetBy: 7, limitedBy: string.endIndex)
        {
            let fractionRange = fractionStart.lowerBound..<fractionEnd
            // Replace the decimal range with the six digit fraction
            let microseconds = date.timeIntervalSince1970 - floor(date.timeIntervalSince1970)
            var microsecondsString = String(format: "%.06f", microseconds)
            microsecondsString.remove(at: microsecondsString.startIndex)
            string.replaceSubrange(fractionRange, with: microsecondsString)
        }

        return string
    }

    override func date(from string: String) -> Date? {
        guard var date = super.date(from: string) else { return nil }

        date = Date(timeIntervalSinceReferenceDate: floor(date.timeIntervalSinceReferenceDate))
        if let fractionStart = string.range(of: "."),
           let fractionEnd = string
            .index(fractionStart.lowerBound, offsetBy: 7, limitedBy: string.endIndex)
        {
            // fractionString is a string containing six decimal digits.
            let fractionString = string[fractionStart.lowerBound..<fractionEnd].trimmingPrefix(".")
            // Directly converting with `Double` introduces errors; `.065005` becomes `.065004`.
            if let fraction = Int(fractionString) {
                date.addTimeInterval(Double(fraction) / 1E6)
            }
        }

        return date
    }
}

这是在 到 范围内的所有微秒内进行测试的。000000999999

评论

1赞 Eli Burke 3/24/2023
对不起,你很难走,杰伊。感谢您发布固定版本(和单元测试!
0赞 Leo Dabus 6/2/2023
请注意,Date 存储自参考日期(2001 年 1 月 1 日 00:00:00 UTC)以来的秒数,而不是自 1970 年(1970 年 1 月 1 日 00:00:00 UTC)以来的秒数。如果您担心精度,您应该使用 .如果您需要比较两个日期或将日期发送到后端而不会造成任何精度损失,请查看此帖子timeIntervalSinceReferenceDate