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的答案。
看起来最有希望的类是
相关问答:如何在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"] |
请记住将技术语言设置为
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)) |
问题是,如果您使用的是使用非公历的设备,则除非您指定
或者您可以使用
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提要的日期的
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 }() } |
以下是使用操场截图中不包含选项的结果:
为了进一步称赞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或更新版本上使用
在iOS9或更早版本上使用
斯威夫特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 } }() } |
有一个新的
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 |