目标是一个看起来像这样的字符串:
"2015-01-01T00:00:00.000Z"
格式:
年,月,日,如“ XXXX-XX-XX”
字母“ T”作为分隔符
小时,分钟,秒,毫秒,如“ XX:XX:XX.XXX”。
字母“ Z”为零偏移量(也称为UTC,GMT和Zulu时间)的区域标记。
最佳情况:
快速,简单,简短的直接源代码。
无需使用任何其他框架,子项目,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))
#1 楼
Swift 4•iOS 11.2.1或更高版本extension ISO8601DateFormatter {
convenience init(_ formatOptions: Options, timeZone: TimeZone = TimeZone(secondsFromGMT: 0)!) {
self.init()
self.formatOptions = formatOptions
self.timeZone = timeZone
}
}
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"
}
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 extension JSONEncoder.DateEncodingStrategy {
static let iso8601withFractionalSeconds = custom {
var container = .singleValueContainer()
try container.encode(Formatter.iso8601withFractionalSeconds.string(from: let dates = [Date()] // ["Feb 8, 2019 at 9:48 PM"]
))
}
}
.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
}
}
和编码策略
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"]
编码
q4312078q
解码
/>
q4312078q
评论
添加相反的转换扩展名很有用:扩展字符串{var dateFormattedISO8601:NSDate? {返回NSDate.Date.formatterISO8601.dateFromString(self)}}
– Vive
16年5月30日在10:50
请注意,这会降低精度,因此确保通过生成的字符串而不是timeInterval比较日期的相等性很重要。让now = NSDate()让stringFromDate = now.iso8601让dateFromString = stringFromDate.dateFromISO8601! XCTAssertEqual(now.timeIntervalSince1970,dateFromString.timeIntervalSince1970)
–pixelrevision
16年6月18日在16:59
不用说,如果您不需要毫秒,那么新的iOS 10 ISO8601DateFormatter可以简化此过程。我已向Apple发布了一个错误报告(27242248),要求他们扩展此新的格式化程序,以提供指定毫秒的功能(因为如果没有毫秒,此新格式化程序对我们中的许多人来说就不会使用)。
–抢夺
16年7月15日在22:28
在RFC3339中,我们可以找到注释“注意:ISO 8601定义了以“ T”分隔的日期和时间。出于可读性考虑,使用此语法的应用程序可以选择指定以(例如)分隔的完整日期和完整时间空格字符。”是否可以覆盖没有T的日期格式,例如:2016-09-21 21:05:10 + 00:00?
–manRo
16-9-29 9:00
@LeoDabus是的,但这是“ Swift iso8601”的第一个结果。我的评论旨在警告将来遇到此问题的其他开发人员,而不是针对OP。
–thislooksfun
17年7月5日在1:10
#2 楼
请记住,按照技术Q&A1480中的说明将语言环境设置为en_US_POSIX
。在Swift 3: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除非您指定
locale
以及timeZone
和dateFormat
字符串。否则,您也可以使用ISO8601DateFormatter
摆脱设置locale
和timeZone
的杂草: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再现,请参见此答案的先前版本。
评论
为什么我们应该将语言环境设置为en_US_POSIX?即使我们不在美国?
–肺炎
17年12月13日在4:38
嗯,您需要一些一致的语言环境,并且ISO 8601 / RFC 3999标准的约定是en_US_POSIX提供的格式。这是用于在网络上交换日期的通用语言。而且,如果保存日期字符串时在设备上使用了一个日历,而稍后又读回该字符串时,则在设备上使用了一个日历,就不会误解日期。另外,您需要保证永远不会更改的格式(这就是为什么使用en_US_POSIX而不是en_US的原因)。有关更多信息,请参见技术问答1480或那些RFC / ISO标准。
–抢夺
17年12月13日在10:23
#3 楼
如果要使用带有Rails 4+ JSON提要中的日期的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
}()
}
这是在操场上屏幕截图中不使用选项诗句的结果:
评论
您还需要在选项中包括.withFractionalSeconds,但是我已经尝试过了,它一直抛出错误libc ++ abi.dylib:以NSException类型的未捕获异常终止。
– Leo Dabus
17年10月12日在1:36
@MEnnabah在Swift 4中对我来说效果很好。您遇到错误了吗?
–马特·朗
17年11月6日在16:22
@LeoDabus,遇到与您相同的错误,您解决了吗?
–自由人
17/12/13在3:36
自定义JSONDecoder DateDecodingStrategy stackoverflow.com/a/46458771/2303865
– Leo Dabus
17/12/13在3:40
@freeman如果您想保留所有小数秒的日期,我建议在将日期保存/接收到服务器时使用双精度(自参考日期以来的时间间隔)。并在使用可编码协议时使用默认的日期解码策略.deferredToDate
– Leo Dabus
17/12/13在3:47
#4 楼
Swift 5如果您的目标是iOS 11.0+ / macOS 10.13+,则只需将
ISO8601DateFormatter
与withInternetDateTime
和withFractionalSeconds
选项结合使用,就像这样: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"
#5 楼
在iOS10或更高版本上使用ISO8601DateFormatter
。在iOS9或更高版本上使用
DateFormatter
。Swift 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
}
}()
}
#6 楼
为了进一步赞扬AndrésTorresMarroquí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
}
}
评论
在我看来,这是最好的答案,因为它可以使所有其他解决方案的截断时间都达到毫秒级,从而达到毫秒级的精度。
–迈克尔·A·麦克洛斯基(Michael A. McCloskey)
17年9月29日在19:31
如果要保留日期的所有小数秒,则在将日期保存/接收到服务器时,应仅使用双精度(自参考日期以来的时间间隔)。
– Leo Dabus
17年12月13日在3:45
@LeoDabus是的,如果您控制整个系统并且不需要进行互操作。就像我在答案中说的那样,对于大多数用户而言,这不是必需的。但是我们并不总是可以控制Web API中的数据格式,而且由于Android和Python(至少)保留了6位小数精度,因此有时有必要效仿。
– Eli Burke
17年12月14日在14:57
#7 楼
就我而言,我必须将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)
}
#8 楼
将来可能需要更改格式,这可能会导致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
}
}
}
#9 楼
有一个新的ISO8601DateFormatter
类,让您创建仅一行的字符串。为了向后兼容,我使用了一个旧的C库。我希望这对某人有用。Swift 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)
}
}
}
#10 楼
为了补充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
}
}
#11 楼
没有一些手动的字符串掩码或TimeFormattersimport 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
评论
这是一个很好的答案,但是使用.iso8601不会包括毫秒。
– Stefan Arentz
5月24日21:00
#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())")
评论
请注意,iOS10 +简单包含ISO 8601内置..它将自动为您完成。@Fattie然后-它如何处理时间戳的最后一个.234Z毫秒Zulu / UTC部分?答案:马特·朗斯@ stackoverflow.com/a/42101630/3078330
@ smat88dd-很棒的提示,谢谢。我不知道那里有“格式化程序的选项”,很奇怪又很疯狂!
我正在寻找适用于linux的解决方案。
@neoneye只需使用旧版本(普通的DateFormatter)并将日历iso8601更改为gregorian stackoverflow.com/a/28016692/2303865