Swift: como criar um carimbo de data e hora e formatar como ISO 8601, RFC 3339, fuso horário UTC?

Como gerar um registro de data e hora, usando os padrões de formato para ISO 8601 e RFC 3339 ?

O objective é uma string que se parece com isso:

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

Formato:

  • ano, mês, dia, como “XXXX-XX-XX”
  • a letra “T” como separador
  • horas, minutos, segundos, milissegundos, como “XX: XX: XX.XXX”.
  • a letra “Z” como um designador de zona para deslocamento zero, também conhecido como UTC, GMT e Zulu time.

Melhor caso:

  • Código fonte rápido, simples, curto e simples.
  • Não é necessário usar nenhuma estrutura adicional, subprojeto, cocoapod, código C etc.

Eu pesquisei StackOverflow, Google, Apple, etc. e não encontrei uma resposta Swift para isso.

As classs que parecem mais promissoras são NSDate , NSDateFormatter , NSTimeZone .

Perguntas e respostas relacionadas: Como faço para obter a data ISO 8601 no iOS?

Aqui está o melhor que eu já vi até agora:

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

Xcode 9 • Swift 4 • iOS 11 ou posterior

como mencionado nos comentários do @keno ISO8601DateFormatter agora suporta .withFractionalSeconds .withFractionalSeconds no iOS11 ou posterior:

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

Testes de playground:

 let dateString = Formatter.iso8601.string(from: Date()) // "2018-01-23T03:06:46.232Z" if let date = Formatter.iso8601.date(from: dateString) { print(date) // "2018-01-23 03:06:46 +0000\n" } 

Xcode 8.2 • Swift 3.0.2

 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 }() } extension Date { var iso8601: String { return Formatter.iso8601.string(from: self) } } extension String { var dateFromISO8601: Date? { return Formatter.iso8601.date(from: self) // "Mar 22, 2017, 10:22 AM" } } 

Uso:

 let stringFromDate = Date().iso8601 // "2017-03-22T13:22:13.933Z" if let dateFromString = stringFromDate.dateFromISO8601 { print(dateFromString.iso8601) // "2017-03-22T13:22:13.933Z" } 

insira a descrição da imagem aqui

Lembre-se de definir a localidade como en_US_POSIX conforme descrito em Technical Q & A1480 . Em 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)) 

A questão é que, se você estiver em um dispositivo que está usando um calendar não gregoriano, o ano não estará em conformidade com o RFC3339 / ISO8601, a menos que você especifique o código do locale , bem como a string dateFormat e dateFormat .

Ou você pode usar o ISO8601DateFormatter para tirar você das ervas daninhas da configuração do locale e do 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)) 

Para a renderização do Swift 2, consulte a revisão anterior desta resposta .

Se você quiser usar o ISO8601DateFormatter() com uma data de um feed Rails 4+ JSON (e não precisa de millis, é claro), você precisa definir algumas opções no formatador para que ele funcione corretamente, caso contrário, a date(from: string) function retornará nil. Aqui está o que estou usando:

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

Aqui está o resultado de usar as opções verses não em uma captura de canvas do playground:

insira a descrição da imagem aqui

No futuro, o formato pode precisar ser alterado, o que pode ser uma pequena dor de cabeça com data.dateFromISO8601 chamadas em todos os lugares em um aplicativo. Use uma class e protocolo para envolver a implementação, alterando a chamada de formato de data e hora em um lugar será mais simples. Use RFC3339, se possível, é uma representação mais completa. DateFormatProtocol e DateFormat são ótimos para injeção de dependência.

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

Para complementar ainda mais Andrés Torres Marroquín e Leo Dabus, tenho uma versão que preserva os segundos fracionários. Não consigo encontrá-lo documentado em lugar algum, mas a Apple trunca segundos fracionários para o microssegundo (3 dígitos de precisão) na input e saída (mesmo que especificado usando o SSSSSSS, ao contrário do Unicode tr35-31 ).

Devo salientar que isso provavelmente não é necessário para a maioria dos casos de uso . As datas on-line normalmente não precisam de precisão de milissegundos e, quando o fazem, geralmente é melhor usar um formato de dados diferente. Mas às vezes é preciso interoperar com um sistema pré-existente de uma maneira particular.

Xcode 8/9 e 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.. 

No meu caso eu tenho que converter a coluna DynamoDB – lastUpdated (Unix Timestamp) para Normal Time.

O valor inicial de lastUpdated foi: 1460650607601 – convertido em 2016-04-14 16:16:47 +0000 através de:

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

Há uma nova class ISO8601DateFormatter que permite criar uma string com apenas uma linha. Para compatibilidade com versões anteriores, usei uma biblioteca C antiga. Espero que isso seja útil para alguém.

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

Para complementar a versão do Leo Dabus, eu adicionei suporte para projetos escritos como Swift e Objective-C, também adicionei suporte para os milissegundos opcionais, provavelmente não é o melhor, mas você entenderia:

Xcode 8 e 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 } } 

Usa ISO8601DateFormatter no iOS10 ou mais recente.

Usa o DateFormatter no iOS9 ou anterior.

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