提问人:joelparkerhenderson 提问时间:1/19/2015 最后编辑:Communityjoelparkerhenderson 更新时间:11/20/2023 访问量:177126
如何在 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?
问:
如何使用 ISO 8601 和 RFC 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、NSDateFormatter
和 NSTimeZone
。
这是我迄今为止想出的最好的:
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))
答:
请记住按照技术 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,除非您指定 以及 和 字符串。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
en_US_POSIX
en_US
.withFractionalSeconds
- 这是我支持处理毫秒所需的。谢谢!
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"]
评论
extension String { var dateFormattedISO8601: NSDate? {return NSDate.Date.formatterISO8601.dateFromString(self)} }
let now = NSDate() let stringFromDate = now.iso8601 let dateFromString = stringFromDate.dateFromISO8601! XCTAssertEqual(now.timeIntervalSince1970, dateFromString.timeIntervalSince1970)
T
2016-09-21 21:05:10+00:00
Calendar(identifier: .iso8601)
就我而言,我必须将 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)
}
为了补充 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
}
}
将来可能需要更改格式,这可能是一个小小的头疼,因为 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
}
}
}
如果你想使用 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 屏幕截图中的结果:
评论
.withFractionalSeconds
libc++abi.dylib: terminating with uncaught exception of type NSException
.deferredToDate
为了进一步赞美安德烈斯·托雷斯·马罗金和里奥·达布斯,我有一个保留了小数秒的版本。我在任何地方都找不到它的记录,但 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
}
}
评论
有一个新类,可让您创建一个只有一行的字符串。为了向后兼容,我使用了旧的 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)
}
}
}
在 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
}
}()
}
无需一些手动字符串掩码或 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
评论
.iso8601
斯威夫特 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"
基于对象范式中可接受的答案
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())")
现在是 2022 年,但我正在寻找这个问题的答案(即如何将日期转换为包含秒数的ISO8601)。事实证明,现在的答案是一句话:
var somedate: Date = Date.now
var isodate = somedate.ISO8601Format(Date.ISO8601FormatStyle(includingFractionalSeconds: true))
所以这将打印类似的东西2022-08-16T17:45:08.548Z
评论
ISOFormatStyle(includingFractionalSeconds:)
下面的代码保留了微秒精度。
这是基于这个答案的,但这实际上会损失微秒的精度,并导致转换之间的有损转换,例如,与该答案一起变。Date-String
.065005
.065004
更严重的是,具有几乎一个亚秒位数的日期会使用该代码“添加”整整一秒,例如,变成 .
(我很难发现这一点。xx[d].999500
xx[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
}
}
这是在 到 范围内的所有微秒内进行测试的。000000
999999
评论
timeIntervalSinceReferenceDate
上一个:Swift - 编码 URL
评论