Swift: comment créer un horodatage et un format comme ISO 8601, RFC 3339, UTC time zone?

comment générer un horodatage de date, en utilisant les normes de format pour ISO 8601 et RFC 3339 ?

le but est une chaîne qui ressemble à ceci:

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

Format:

  • Année, Mois, Jour, comme "XXXX-XX-XX"
  • la lettre "T" comme séparateur
  • heures, minutes, secondes, millisecondes, comme "XX:XX:XX.XXX".
  • la lettre" Z " comme indicateur de zone pour l'offset zéro, A. K. A. UTC, GMT, heure Zoulou.

dans le Meilleur des cas:

  • code source Swift qui est simple, court et direct.
  • pas besoin d'utiliser un cadre supplémentaire, un sous-projet, un cocoapod, un code C, etc.

j'ai cherché StackOverflow, Google, Apple, etc. et n'ont pas trouvé de réponse rapide à ce.

les classes qui semblent les plus prometteuses sont NSDate , NSDateFormatter , NSTimeZone .

questions et réponses connexes: Comment puis-je obtenir la date ISO 8601 dans iOS?

Ici est la meilleure que j'ai trouvé jusqu'à présent:

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))
124
demandé sur Community 2015-01-19 03:58:31

9 réponses

Xcode 9 • Swift 4 • iOS 11 ou plus tard

comme mentionné dans les commentaires de @keno ISO8601DateFormatter supporte maintenant .withFractionalSeconds formatOptions dans iOS11 ou plus tard:

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

aire de Jeux de tests:

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

Utilisation:

let stringFromDate = Date().iso8601    // "2017-03-22T13:22:13.933Z"

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

enter image description here

279
répondu Leo Dabus 2018-02-13 13:17:58

N'oubliez pas de régler la locale à en_US_POSIX comme décrit dans questions et réponses techniques . Dans 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))

le problème est que si vous êtes sur un appareil qui utilise un calendrier Non grégorien, l'année ne sera pas conforme à RFC3339/ISO8601 à moins que vous spécifiez la locale ainsi que la timeZone et dateFormat chaîne.

Ou vous pouvez utiliser ISO8601DateFormatter pour vous sortir de mauvaises herbes de réglage locale et timeZone vous-même:

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

pour les rendus Swift 2, voir révision précédente de cette réponse .

22
répondu Rob 2017-12-26 20:45:16

si vous voulez utiliser le ISO8601DateFormatter() avec une date à partir D'un rail 4+ JSON feed (et n'avez pas besoin de millis bien sûr), vous devez définir quelques options sur le formatteur pour qu'il fonctionne correctement sinon la fonction date(from: string) retournera zéro. Voici ce que j'utilise:

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

voici le résultat de l'utilisation des options versets pas dans une capture d'écran de terrain de jeu:

enter image description here

17
répondu Matt Long 2017-02-07 23:00:34

dans le futur le format pourrait devoir être changé qui pourrait être une petite tête ache ayant date.dateFromISO8601 appelle partout dans une application. Utilisez une classe et un protocole pour terminer la mise en œuvre, changer la date heure appel de format dans un endroit sera plus simple. Utilisez RFC3339 si possible, c'est une représentation plus complète. DateFormatProtocol et DateFormat sont parfaits pour l'injection de dépendances.

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
répondu Gary Davies 2016-12-05 03:57:43

pour compléter Andrés Torres Marroquín et Leo Dabus, j'ai une version qui préserve la fraction de seconde. Je ne le trouve documenté nulle part, mais Apple tronque fraction de secondes à la microseconde (3 chiffres de précision) sur l'entrée et la sortie (même si spécifié en utilisant sssss, contrairement à Unicode tr35-31 ).

je dois souligner que ce n'est probablement pas nécessaire pour la plupart des cas d'utilisation . Date en ligne n'ont généralement pas besoin de précision milliseconde, et quand ils le font, il est souvent préférable d'utiliser un format de données différent. Mais il faut parfois interopérer d'une manière particulière avec un système préexistant.

Xcode 8/9 et 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
    }
}
3
répondu Eli Burke 2018-07-12 15:18:17

dans mon cas, je dois convertir la colonne DynamoDB - lastUpdated (horodatage Unix) en temps Normal.

la valeur initiale de lastUpdated était: 1460650607601-converti en 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
répondu ioopl 2016-04-15 11:09:44

il y a une nouvelle classe ISO8601DateFormatter qui vous permet de créer une chaîne avec une seule ligne. Pour la compatibilité ascendante, j'ai utilisé une ancienne bibliothèque C. J'espère que cela est utile pour quelqu'un.

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)
        }
    }
}
2
répondu Thomas Szabo 2017-06-28 15:35:24

utilise ISO8601DateFormatter sur iOS10 ou plus récent.

utilise DateFormatter sur iOS9 ou plus.

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
répondu neoneye 2018-03-08 13:14:22

pour compléter la version de Leo Dabus, j'ai ajouté le support pour les projets écrits Swift et Objective-C, a également ajouté le support pour les millisecondes optionnelles, n'est probablement pas la meilleure mais vous obtiendriez le point:

Xcode 8 et 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
répondu Andrés Torres Marroquín 2016-11-03 16:22:16