date swift time iso8601 rfc3339

date - ¿Cómo crear un sello de fecha y hora y formato como ISO 8601, RFC 3339, zona horaria UTC?



swift time (10)

¿Cómo generar una marca de fecha y hora, utilizando los estándares de formato para ISO 8601 y RFC 3339 ?

El objetivo es una cadena que se ve así:

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

Formato:

  • año, mes, día, como "XXXX-XX-XX"
  • la letra "T" como separador
  • hora, minuto, segundos, milisegundos, como "XX: XX: XX.XXX".
  • la letra "Z" como un designador de zona para desplazamiento cero, también conocido como UTC, GMT, hora zulú.

Mejor caso:

  • Código fuente rápido que es simple, corto y directo.
  • No es necesario utilizar ningún marco adicional, subproyecto, cocoapod, código C, etc.

He buscado en StackOverflow, Google, Apple, etc. y no he encontrado una respuesta rápida a esto.

Las clases que parecen más prometedoras son NSDate , NSDateFormatter , NSTimeZone .

Preguntas y respuestas relacionadas: ¿Cómo obtengo la fecha ISO 8601 en iOS?

Aquí está lo mejor que he encontrado hasta ahora:

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


En el futuro, es posible que sea necesario cambiar el formato, lo que podría ser un pequeño dolor de cabeza con date.dateFromISO8601 en todas partes de una aplicación. Use una clase y un protocolo para ajustar la implementación, cambiar el formato de fecha y hora en un solo lugar será más simple. Use RFC3339 si es posible, es una representación más completa. DateFormatProtocol y DateFormat es ideal para la inyección de dependencias.

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


En mi caso, tengo que convertir la columna DynamoDB - lastUpdated (Unix Timestamp) a Hora normal.

El valor inicial de lastUpdated fue: 1460650607601 - convertido a 2016-04-14 16:16:47 +0000 a travé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) }


Hay una nueva clase ISO8601DateFormatter que le permite crear una cadena con solo una línea. Por compatibilidad con versiones anteriores, utilicé una antigua biblioteca C. Espero que esto sea útil para alguien.

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 la versión de Leo Dabus, agregué soporte para proyectos escritos Swift y Objective-C, también agregué soporte para los milisegundos opcionales, probablemente no sea lo mejor, pero entendería el punto:

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


Para felicitar aún más a Andrés Torres Marroquín y Leo Dabus, tengo una versión que conserva segundos fraccionarios. No puedo encontrarlo documentado en ninguna parte, pero Apple trunca segundos fraccionarios al microsegundo (3 dígitos de precisión) tanto en la entrada como en la salida (aunque se especifique usando SSSSSSS, al contrario de Unicode tr35-31 ).

Debo enfatizar que esto probablemente no sea necesario para la mayoría de los casos de uso . Las fechas en línea generalmente no necesitan una precisión de milisegundos, y cuando lo hacen, a menudo es mejor usar un formato de datos diferente. Pero a veces uno debe interoperar con un sistema preexistente de una manera particular.

Xcode 8/9 y 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 } }


Recuerde configurar la configuración regional en en_US_POSIX como se describe en las Preguntas y respuestas técnicas 1480 . En 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))

El problema es que si está en un dispositivo que utiliza un calendario no gregoriano, el año no se ajustará a RFC3339 / ISO8601 a menos que especifique la locale , así como la cadena timeZone y dateFormat .

O puede usar ISO8601DateFormatter para sacarlo de las malas hierbas de la configuración locale y la zona timeZone usted mismo:

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 la versión de Swift 2, vea la revisión anterior de esta respuesta .


Si desea utilizar el ISO8601DateFormatter() con una fecha de un feed JSON de Rails 4+ (y no necesita millis, por supuesto), debe establecer algunas opciones en el formateador para que funcione correctamente; de ​​lo contrario, la date(from: string) función devolverá nil. Esto es lo que estoy 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 }() }

Aquí está el resultado de usar los versos de opciones que no están en una captura de pantalla del patio de recreo:


Utiliza ISO8601DateFormatter en iOS10 o posterior.

Utiliza DateFormatter en iOS9 o 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 } }() }


Sin algunas máscaras de cadena manuales o 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


Swift 4 • iOS 11.2.1 o posterior

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

Uso:

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 o posterior

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

Protocolo codificable

Si necesita codificar y decodificar este formato cuando trabaje con el protocolo Codificable, puede crear sus propias estrategias de codificación / decodificación de fecha personalizadas:

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

y la estrategia de codificación

extension JSONEncoder.DateEncodingStrategy { static let iso8601withFractionalSeconds = custom { var container = $1.singleValueContainer() try container.encode(Formatter.iso8601.string(from: $0)) } }

Prueba de patio de recreo

let dates = [Date()] // ["Feb 8, 2019 at 9:48 PM"]

codificación

let encoder = JSONEncoder() encoder.dateEncodingStrategy = .iso8601withFractionalSeconds let data = try! encoder.encode(dates) print(String(data: data, encoding: .utf8)!)

descodificación

let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601withFractionalSeconds let decodedDates = try! decoder.decode([Date].self, from: data) // ["Feb 8, 2019 at 9:48 PM"]