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

How to create a date time stamp and format as ISO 8601, RFC 3339, UTC time zone?

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

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

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

格式:

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

最佳案例:

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

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

看起来最有希望的类是NSDateNSDateFormatterNSTimeZone

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

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

1
2
3
4
5
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))


斯威夫特4? iOS 11.2.1或更高版本

1
2
3
4
5
6
7
extension ISO8601DateFormatter {
    convenience init(_ formatOptions: Options, timeZone: TimeZone = TimeZone(secondsFromGMT: 0)!) {
        self.init()
        self.formatOptions = formatOptions
        self.timeZone = timeZone
    }
}
1
2
3
extension Formatter {
    static let iso8601 = ISO8601DateFormatter([.withInternetDateTime, .withFractionalSeconds])
}
1
2
3
4
5
extension Date {
    var iso8601: String {
        return Formatter.iso8601.string(from: self)
    }
}
1
2
3
4
5
extension String {
    var iso8601: Date? {
        return Formatter.iso8601.date(from: self)
    }
}

用法:

1
2
3
4
5
6
7
8
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
"
}

iOS 9? Swift 3或更高版本

1
2
3
4
5
6
7
8
9
10
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
    }()
}

Codable Protocol

If you need to encode and decode this format when working with Codable
protocol you can create your own custom date encoding/decoding strategies:

1
2
3
4
5
6
7
8
9
10
11
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
    }
}

和编码策略

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

游乐场测试

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

编码

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

解码

1
2
3
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


请记住将技术语言设置为en_US_POSIX,如技术问答A1480中所述。在Swift 3中:

1
2
3
4
5
6
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))

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

或者您可以使用ISO8601DateFormatter让您摆脱设置localetimeZone的杂草:

1
2
3
4
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的演绎,请参阅此答案的上一版本。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
    }()
}

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

enter image description here


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

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

Xcode 8/9和Swift 3.0-3.2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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
    }
}


在iOS10或更新版本上使用ISO8601DateFormatter

在iOS9或更早版本上使用DateFormatter

斯威夫特4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
        }
    }()
}

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

Swift 3.0

1
2
3
4
5
6
7
8
9
10
11
12
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)
        }
    }
}


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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
        }
    }

}

在我的情况下,我必须将DynamoDB - lastUpdated列(Unix时间戳)转换为正常时间。

lastUpdated的初始值为:1460650607601 - 转换为2016-04-14 16:16:47 +0000 via:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
   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)

            }

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

Xcode 8和Swift 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
    }
}

没有一些手动字符串掩码或TimeFormatters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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