Comment obtenir une date ISO 8601 sur iOS?

115

Il est assez facile d'obtenir la chaîne de date ISO 8601 (par exemple, 2004-02-12T15:19:21+00:00) en PHP via date('c'), mais comment l'obtenir en Objective-C (iPhone)? Existe-t-il un moyen aussi court de le faire?

Voici le long chemin que j'ai trouvé pour le faire:

NSDateFormatter* dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";

NSDate *now = [NSDate date];
NSString *formattedDateString = [dateFormatter stringFromDate:now];
NSLog(@"ISO-8601 date: %@", formattedDateString);

// Output: ISO-8601 date: 2013-04-27T13:27:50-0700

Cela semble énormément de rigmarole pour quelque chose d'aussi central.

JohnK
la source
1
Je suppose que le court est dans l'œil du spectateur. Trois lignes de code, c'est assez court, non? Il y a une sous-classe C NSDate que vous pouvez ajouter qui le fera avec une seule ligne, je viens de trébucher dessus: github.com/soffes/SAMCategories/tree/master/SAMCategories/… . Cela dit, vraiment, vous avez de très bonnes réponses (à mon humble avis) et vous devriez attribuer la réponse à Etienne ou à rmaddy. C'est juste une bonne pratique et récompense les personnes qui fournissent de vraies informations utiles (cela m'a aidé, j'avais besoin de ces informations aujourd'hui). Etienne pourrait utiliser les points plus que rmaddy. En signe de bonne mesure, je voterai même votre question!
David H

Réponses:

212

Utilisez NSDateFormatter:

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];   
NSLocale *enUSPOSIXLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
[dateFormatter setLocale:enUSPOSIXLocale];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZZZ"];
[dateFormatter setCalendar:[NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian]];

NSDate *now = [NSDate date];
NSString *iso8601String = [dateFormatter stringFromDate:now];

Et dans Swift:

let dateFormatter = DateFormatter()
let enUSPosixLocale = Locale(identifier: "en_US_POSIX")
dateFormatter.locale = enUSPosixLocale
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
dateFormatter.calendar = Calendar(identifier: .gregorian)

let iso8601String = dateFormatter.string(from: Date())
rmaddy
la source
1
Merci, Maddy. Je viens de voir votre réponse maintenant, après avoir posté mon code. Est-ce que c'est setLocaleimportant?
JohnK
2
@rmaddy Vous pouvez simplement modifier votre exemple pour utiliser ZZZZZ afin que les personnes qui copient-collent ne soient pas brûlées.
Jesse Rusak
4
@jlmendezbonini Non. Il est très rare que vous vouliez YYYY. Vous voulez généralement yyyy.
rmaddy
7
Source pour savoir pourquoi les paramètres régionaux doivent être définis sur en_US_POSIX: developer.apple.com/library/mac/qa/qa1480/_index.html
yood
11
@maddy - Je voulais que les autres le découvrent facilement via Google et je voulais vous remercier d'avoir trouvé le bon format, les paramètres régionaux, etc. Mais heureux de l'afficher comme une réponse séparée si vous le souhaitez
Robin
60

iOS 10 introduit une nouvelle NSISO8601DateFormatterclasse pour gérer exactement cela. Si vous utilisez Swift 3, votre code ressemblerait à ceci:

let formatter = ISO8601DateFormatter()
let date = formatter.date(from: "2016-08-26T12:39:00Z")
let string = formatter.string(from: Date())
Deux pailles
la source
et quel format donne-t-il? Aussi, il serait bon de savoir s'il peut gérer du: 2016-08-26T12: 39: 00-0000 et 2016-08-26T12: 39: 00-00: 00 et 2016-08-26T12: 39: 00.374-0000
malhal le
1
Après l'exécution des trois lignes de ma réponse, stringcontient "2016-09-03T21: 47: 27Z".
TwoStraws
3
vous NE POUVEZ PAS gérer les millisecondes avec ce format.
Enrique le
3
@Enrique: vous devez ajouter NSISO8601DateFormatWithFractionalSeconds aux options du formateur pour pouvoir travailler avec des millisecondes, consultez la documentation.
PakitoV
1
NSISO8601DateFormatWithFractionalSeconds ne fonctionne que sur 11.2+. Sinon, vous avez un crash!
hash3r
27

En complément de la réponse de maddy, le format du fuseau horaire devrait être "ZZZZZ" (5 fois Z) pour ISO 8601 au lieu d'un seul "Z" (qui est pour le format RFC 822). Au moins sur iOS 6.

(voir http://www.unicode.org/reports/tr35/tr35-25.html#Date_Format_Patterns )

Etienne
la source
Superbe capture! Toute personne intéressée par cela, allez sur le lien, recherchez 8601, vous le verrez.
David H
Lorsque le fuseau horaire de la date est UTC, y a-t-il un moyen de le faire afficher 00:00 au lieu du Z (qui sont équivalents)?
shim
Merci beaucoup;) me cognait la tête contre le mur avec un dateformatter renvoyant une date nulle !!! :)
polo987
19

Un problème souvent négligé est que les chaînes au format ISO 8601 peuvent avoir des millisecondes et non.

En d'autres termes, "2016-12-31T23: 59: 59.9999999" et "2016-12-01T00: 00: 00" sont légitimes, mais si vous utilisez un formateur de date de type statique, l'un d'entre eux ne sera pas analysé .

À partir d' iOS 10, vous devez utiliser ISO8601DateFormatterqui gère toutes les variations des chaînes de date ISO 8601. Voir l'exemple ci-dessous:

let date = Date()
var string: String

let formatter = ISO8601DateFormatter()
string = formatter.string(from: date)

let GMT = TimeZone(abbreviation: "GMT")
let options: ISO8601DateFormatOptions = [.withInternetDateTime, .withDashSeparatorInDate, .withColonSeparatorInTime, .withTimeZone]
string = ISO8601DateFormatter.string(from: date, timeZone: GMT, formatOptions: options)

Pour iOS 9 et les versions antérieures, utilisez l'approche suivante avec plusieurs formateurs de données.

Je n'ai pas trouvé de réponse qui couvre les deux cas et résume cette différence subtile. Voici la solution qui y répond:

extension DateFormatter {

    static let iso8601DateFormatter: DateFormatter = {
        let enUSPOSIXLocale = Locale(identifier: "en_US_POSIX")
        let iso8601DateFormatter = DateFormatter()
        iso8601DateFormatter.locale = enUSPOSIXLocale
        iso8601DateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
        iso8601DateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
        return iso8601DateFormatter
    }()

    static let iso8601WithoutMillisecondsDateFormatter: DateFormatter = {
        let enUSPOSIXLocale = Locale(identifier: "en_US_POSIX")
        let iso8601DateFormatter = DateFormatter()
        iso8601DateFormatter.locale = enUSPOSIXLocale
        iso8601DateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
        iso8601DateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
        return iso8601DateFormatter
    }()

    static func date(fromISO8601String string: String) -> Date? {
        if let dateWithMilliseconds = iso8601DateFormatter.date(from: string) {
            return dateWithMilliseconds
        }

        if let dateWithoutMilliseconds = iso8601WithoutMillisecondsDateFormatter.date(from: string) {
            return dateWithoutMilliseconds
        }

        return nil
    }
}

Usage:

let dateToString = "2016-12-31T23:59:59.9999999"
let dateTo = DateFormatter.date(fromISO8601String: dateToString)
// dateTo: 2016-12-31 23:59:59 +0000

let dateFromString = "2016-12-01T00:00:00"
let dateFrom = DateFormatter.date(fromISO8601String: dateFromString)
// dateFrom: 2016-12-01 00:00:00 +0000

Je recommande également de consulter l' article Apple sur les formateurs de date.

Vadim Bulavin
la source
Pourriez-vous s'il vous plaît indiquer également comment obtenir NSISO8601DateFormtter dans Obj-C? (sur iOS 10 et au-dessus, bien sûr)
Motti Shneor
8

Utilisez donc la catégorie de Sam Soffee sur NSDate trouvée ici . Avec ce code ajouté à votre projet, vous pouvez désormais utiliser une seule méthode sur NSDate:

- (NSString *)sam_ISO8601String

Non seulement il s'agit d'une ligne, mais c'est beaucoup plus rapide que l'approche NSDateFormatter, car il est écrit en C.

David H
la source
1
L'emplacement actuel de la catégorie est ici
Perry
1
Je le déconseille, il a un problème d'exception de mémoire qui se produit de manière très aléatoire
M_Waly
1
@M_Waly lui avez-vous signalé cela? J'ai construit son code sans avertissement et il a très bien passé les contrôles d'analyse.
David H
6

A partir d' IS8601 , les problèmes sont la représentation et le fuseau horaire

  • ISO 8601 = year-month-day time timezone
  • Pour la date et l'heure, il existe le format de base (AAAAMMJJ, hhmmss, ...) et le format étendu (AAAA-MM-JJ, hh: mm: ss, ...)
  • Le fuseau horaire peut être zoulou, décalage ou GMT
  • Le séparateur de la date et de l'heure peut être un espace, ou T
  • Il existe un format de semaine pour la date, mais il est rarement utilisé
  • Le fuseau horaire peut comporter de nombreux espaces après
  • Le deuxième est facultatif

Voici quelques chaînes valides

2016-04-08T10:25:30Z
2016-04-08 11:25:30+0100
2016-04-08 202530GMT+1000
20160408 08:25:30-02:00
2016-04-08 11:25:30     +0100

Solutions

NSDateFormatter

Voici donc le format que j'utilise dans onmyway133 ISO8601

let formatter = NSDateFormatter()
formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
formatter.dateFormat = "yyyyMMdd HHmmssZ"

À propos de la table des symboles des champs de date d'Z identification

Z: The ISO8601 basic format with hours, minutes and optional seconds fields. The format is equivalent to RFC 822 zone format (when optional seconds field is absent)

A propos du locale formatage des données à l'aide des paramètres régionaux

Les paramètres régionaux représentent les choix de mise en forme pour un utilisateur particulier, et non la langue préférée de l'utilisateur. Ce sont souvent les mêmes mais peuvent être différents. Par exemple, un anglophone qui vit en Allemagne peut sélectionner l'anglais comme langue et l'Allemagne comme région

À propos en_US_POSIX des questions et réponses techniques QA1480 NSDateFormatter et Internet Dates

D'un autre côté, si vous travaillez avec des dates au format fixe, vous devez d'abord définir les paramètres régionaux du formateur de date sur quelque chose qui convient à votre format fixe. Dans la plupart des cas, la meilleure langue à choisir est "en_US_POSIX", une langue spécialement conçue pour produire des résultats en anglais américain, quelles que soient les préférences de l'utilisateur et du système. "en_US_POSIX" est également invariant dans le temps (si les États-Unis, à un moment donné dans le futur, modifient la façon dont ils formate les dates, "en_US" changera pour refléter le nouveau comportement, mais "en_US_POSIX" ne le fera pas), et entre les machines ( "en_US_POSIX" fonctionne de la même manière sur iOS que sur OS X, et comme il le fait sur d'autres plates-formes).

Questions connexes intéressantes

onmyway133
la source
Je viens d'essayer avec 2016-07-23T07: 24: 23Z, cela ne fonctionne pas
Kamen Dobrev
@KamenDobrev qu'entendez-vous par «ne fonctionne pas»? Je viens d'ajouter votre exemple aux tests github.com/onmyway133/ISO8601/blob/master/ISO8601Tests/… et cela fonctionne
onmyway133
3

Sur la base de cet essentiel: https://github.com/justinmakaila/NSDate-ISO-8601/blob/master/NSDateISO8601.swift , la méthode suivante peut être utilisée pour convertir NSDate en chaîne de date ISO 8601 au format aaaa-MM -dd'T'HH: mm: ss.SSSZ

-(NSString *)getISO8601String
    {
        static NSDateFormatter *formatter = nil;
        if (!formatter)
        {
            formatter = [[NSDateFormatter alloc] init];
            [formatter setLocale: [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
            formatter.timeZone = [NSTimeZone timeZoneWithAbbreviation: @"UTC"];
            [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS"];

        }
        NSString *iso8601String = [formatter stringFromDate: self];
        return [iso8601String stringByAppendingString: @"Z"];
    }
Zack Zhu
la source
La limite Sde la sous-seconde était la clé ici
Mark Meyer
Notez que si vous avez besoin de plus de 3 S, vous ne pouvez pas utiliser le formateur de date pour l'analyser manuellement.
malhal
-1

C'est un peu plus simple et met la date en UTC.

extension NSDate
{
    func iso8601() -> String
    {
        let dateFormatter = NSDateFormatter()
        dateFormatter.timeZone = NSTimeZone(name: "UTC")
        let iso8601String = dateFormatter.stringFromDate(NSDate())
        return iso8601String
    }
}

let date = NSDate().iso8601()
iphaaw
la source
-1

Utilisation de Swift 3.0 et iOS 9.0

extension Date {
    private static let jsonDateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.timeZone = TimeZone(identifier: "UTC")!
        return formatter
    }()

    var IOS8601String: String {
        get {
            return Date.jsonDateFormatter.string(from: self)
        }
    }

    init?(fromIOS8601 dateString: String) {
        if let d = Date.jsonDateFormatter.date(from: dateString) {
            self.init(timeInterval: 0, since:d)
        } else {
            return nil
        }
    }
}
Denis Krivitski
la source