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

How to create a date timestamp using format standards for ISO 8601 and RFC 3339 ?

The target is a line that looks like this:

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

Format:

  • year, month, day, like "XXXX-XX-XX"
  • letter "T" as a delimiter
  • hour, minute, seconds, milliseconds, like "XX: XX: XX.XXX".
  • the letter "Z" as a pointer to the zone for zero offset, aka UTC, GMT, time Zulu.

Best case:

  • Fast source code, simple, short and simple.
  • No need to use any additional frameworks, subproject, cocoapod, C code, etc.

I searched StackOverflow, Google, Apple, etc., and could not find the answer to this question.

The classes that seem most promising are NSDate , NSDateFormatter , NSTimeZone .

Related Q & A: How do I get ISO 8601 date in iOS?

Here is the best I've come up with so far:

 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)) 
+143
date time swift iso8601 rfc3339
Jan 19 '15 at 0:58
source share
10 answers

Xcode 9 • Swift 4 • iOS 11 or later

 extension ISO8601DateFormatter { convenience init(_ formatOptions: Options, timeZone: TimeZone = TimeZone(secondsFromGMT: 0)!) { self.init() self.formatOptions = formatOptions self.timeZone = timeZone } } 



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



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



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



Using:

 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\n" } 



iOS 9 • Swift 3 or later

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



enter image description here

+307
Jan 19 '15 at 1:18
source share

Remember to set the locale to en_US_POSIX as described in Technical Q & A1480 . In Swift 3:

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

The problem is that if you use a device that uses a non-Gregorian calendar, the year will not correspond to RFC3339 / ISO8601 unless you specify locale as well as timeZone and dateFormat .

Or you can use the ISO8601DateFormatter to get you out of the weeds by setting the locale and 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)) 

For Swift 2, see the previous revision of this answer .

+24
Jan 19 '15 at 1:04
source share

If you want to use ISO8601DateFormatter() with a date from the Rails 4+ JSON feed (and you certainly don’t need Millions), you need to set several parameters in the formatting so that it works correctly, otherwise the date(from: string) function will return zero . Here is what I use:

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

Here is the result of using the verses not in the screenshot on the playground:

enter image description here

+18
Feb 07 '17 at 23:00
source share

To congratulate Andres Torres Marrokin and Leo Dabus even more, I have a version that saves fractional seconds. I can’t find it documented anywhere, but Apple reduces fractional seconds to microseconds (3 digits of accuracy) both at the input and at the output (even if it is indicated using SSSSSSS, unlike Unicode tr35-31 ).

I must emphasize that this is probably not necessary for most use cases . Online dates usually do not require millisecond accuracy, and when they do, it is often better to use a different data format. But sometimes you need to interact with an existing system in a certain way.

Xcode 8/9 and 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 } } 
+4
Apr 14 '17 at 13:30
source share

In the future, you may need to change the format, which can be a little headache when date.dateFromISO8601 is found throughout the application. Use the class and protocol to transfer the implementation; changing the time format in one place will be easier. Use RFC3339, if possible, a more complete view. DateFormatProtocol and DateFormat are great for dependency injection.

 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 } } } 
+3
Dec 05 '16 at 3:13
source share

There is a new ISO8601DateFormatter class that allows you to create a single line row. For backward compatibility, I used the old C library. Hope this is useful to someone.

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) } } } 
+3
Jun 28 '17 at 15:35
source share

In my case, I need to convert DynamoDB - lastUpdated column (Unix Timestamp) to normal time.

Initial value lastUpdated: 1460650607601 - converted before 2016-04-14 16:16:47 +0000 via:

  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) } 
+2
Apr 15 '16 at 11:09
source share

Uses ISO8601DateFormatter on iOS10 or later.

Uses DateFormatter on iOS9 or later.

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 } }() } 
+2
Mar 08 '18 at 13:14
source share

In addition to the Leo Dabus version, I added support for projects written by Swift and Objective-C, also added support for the extra milliseconds, maybe not the best, but you would get the point:

Xcode 8 and 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 } } 
0
Nov 03 '16 at 16:22
source share

Without any manual string masks or 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
04 Dec '18 at 5:33
source share



All Articles