Come posso ottenere una data ISO 8601 su iOS?


115

È abbastanza facile ottenere la stringa della data ISO 8601 (ad esempio 2004-02-12T15:19:21+00:00) in PHP tramite date('c'), ma come si ottiene in Objective-C (iPhone)? C'è un modo altrettanto breve per farlo?

Ecco la lunga strada che ho trovato per farlo:

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

Sembra un'enorme tiritera per qualcosa di così centrale.


1
Immagino che sia breve negli occhi di chi guarda. Tre righe di codice sono piuttosto brevi, no? C'è una sottoclasse C NSDate che puoi aggiungere che lo farà con una riga, ci sono appena scattato: github.com/soffes/SAMCategories/tree/master/SAMCategories/… . Detto questo, davvero, hai ottenuto risposte abbastanza buone (IMHO) e dovresti assegnare la risposta a Etienne o rmaddy. È solo una buona pratica e premia le persone che forniscono informazioni utili reali (mi ha aiutato, avevo bisogno di queste informazioni oggi). Etienne potrebbe usare i punti più che rmaddy. Come segno di buona misura voterò anche la tua domanda!
David H

Risposte:


212

Usa 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];

E in 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())

1
Grazie, Maddy. Ho appena visto la tua risposta ora, dopo aver pubblicato il mio codice. È setLocaleimportante?
JohnK

2
@rmaddy Potresti semplicemente modificare il tuo esempio per usare ZZZZZ in modo che le persone che fanno copia-incolla non vengano bruciate.
Jesse Rusak

4
@jlmendezbonini No. È molto raro che tu voglia YYYY. Di solito vuoi yyyy.
rmaddy

7
Fonte del motivo per cui la locale dovrebbe essere impostata su en_US_POSIX: developer.apple.com/library/mac/qa/qa1480/_index.html
yood

11
@maddy - Volevo che fosse facile scoprirlo per gli altri che lo trovavano tramite Google e volevo darti credito per aver effettivamente scavato il formato corretto, la località ecc. Ma felice di pubblicarlo come risposta separata se vuoi
Robin

60

iOS 10 introduce una nuova NSISO8601DateFormatterclasse per gestire proprio questo. Se stai usando Swift 3, il tuo codice sarebbe qualcosa del genere:

let formatter = ISO8601DateFormatter()
let date = formatter.date(from: "2016-08-26T12:39:00Z")
let string = formatter.string(from: Date())

e che formato offre? Inoltre sarebbe bene sapere se può gestire da: 2016-08-26T12: 39: 00-0000 e 2016-08-26T12: 39: 00-00: 00 e 2016-08-26T12: 39: 00.374-0000
malhal

1
Dopo che le tre righe nella mia risposta sono state eseguite, stringcontiene "2016-09-03T21: 47: 27Z".
TwoStraws

3
si NON POTETE gestire millisecondi con questo formato.
Enrique

3
@Enrique: dovresti aggiungere NSISO8601DateFormatWithFractionalSeconds alle opzioni del formattatore per poter lavorare con i millisecondi, controlla i documenti.
PakitoV

1
NSISO8601DateFormatWithFractionalSeconds funziona solo su 11.2+. Altrimenti hai un incidente!
hash3r

27

Come complemento alla risposta di Maddy, il formato del fuso orario dovrebbe essere "ZZZZZ" (5 volte Z) per ISO 8601 invece di una singola "Z" (che è per il formato RFC 822). Almeno su iOS 6.

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


Grande cattura! Chiunque sia interessato a questo, vai al link, cerca 8601 lo vedrai.
David H

Quando il fuso orario della data è UTC, c'è un modo per far sì che mostri 00:00 invece di Z (che sono equivalenti)?
spessore

Grazie mille;) stavo sbattendo la testa contro il muro con un datario che restituiva zero data !!! :)
polo987

19

Un problema spesso trascurato è che le stringhe in formato ISO 8601 potrebbero avere millisecondi e potrebbero non esserlo.

In altre parole, sia "2016-12-31T23: 59: 59.9999999" e "2016-12-01T00: 00: 00" sono legittimi, ma se utilizzi un formattatore di data di tipo statico, uno di essi non verrà analizzato .

A partire da iOS 10 dovresti usare ISO8601DateFormatterche gestisce tutte le variazioni delle stringhe di data ISO 8601. Vedi esempio sotto:

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)

Per iOS 9 e versioni precedenti, utilizzare il seguente approccio con più formattatori di dati.

Non ho trovato una risposta che copra entrambi i casi e astragga questa sottile differenza. Ecco la soluzione che lo affronta:

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

Uso:

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

Consiglio anche di controllare l' articolo Apple sui formattatori di data.


Potresti anche indicare come ottenere NSISO8601DateFormtter in Obj-C? (su iOS 10 e versioni successive, ovviamente)
Motti Shneor

8

Quindi usa la categoria di Sam Soffee su NSDate trovata qui . Con quel codice aggiunto al tuo progetto, da quel momento in poi potrai utilizzare un unico metodo su NSDate:

- (NSString *)sam_ISO8601String

Non solo è una riga, è molto più veloce dell'approccio NSDateFormatter, poiché è scritto in C. puro.


1
La posizione attuale della categoria è qui
Perry

1
Lo sconsiglio, ha un problema di eccezione di memoria che si verifica in modo molto casuale
M_Waly

1
@M_Waly gli hai segnalato questo? Ho costruito il suo codice senza avvisi e ha superato i controlli di analisi senza problemi.
David H

6

Da IS8601 , i problemi sono la rappresentazione e il fuso orario

  • ISO 8601 = year-month-day time timezone
  • Per la data e l'ora, sono disponibili il formato di base (AAAAMMGG, hhmmss, ...) ed esteso (AAAA-MM-GG, hh: mm: ss, ...)
  • Il fuso orario può essere Zulu, offset o GMT
  • Il separatore per data e ora può essere spazio o T
  • Esistono formati settimanali per la data, ma viene utilizzato raramente
  • Il fuso orario può contenere molti spazi dopo
  • Il secondo è facoltativo

Ecco alcune stringhe valide

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

soluzioni

NSDateFormatter

Quindi ecco il formato che sto usando in onmyway133 ISO8601

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

Informazioni Zsull'identificatore Data Field Symbol Table

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)

Informazioni sulla locale formattazione dei dati utilizzando le impostazioni internazionali

Le impostazioni locali rappresentano le scelte di formattazione per un particolare utente, non la lingua preferita dell'utente. Questi sono spesso gli stessi ma possono essere diversi. Ad esempio, un madrelingua inglese che vive in Germania potrebbe selezionare l'inglese come lingua e la Germania come regione

Informazioni su en_US_POSIX domande e risposte tecniche QA1480 NSDateFormatter e date Internet

D'altra parte, se stai lavorando con date a formato fisso, dovresti prima impostare le impostazioni internazionali del formattatore di date su qualcosa di appropriato per il tuo formato fisso. Nella maggior parte dei casi, la lingua migliore da scegliere è "en_US_POSIX", una lingua progettata specificamente per fornire risultati in inglese americano indipendentemente dalle preferenze dell'utente e di sistema. Anche "en_US_POSIX" è invariante nel tempo (se gli Stati Uniti, in futuro, cambieranno il modo in cui formatta le date, "en_US" cambierà per riflettere il nuovo comportamento, ma "en_US_POSIX" no), e tra macchine ( "en_US_POSIX" funziona allo stesso modo su iOS come su OS X e come su altre piattaforme).

Interessanti domande correlate


Ho appena provato con 2016-07-23T07: 24: 23Z, non funziona
Kamen Dobrev

@KamenDobrev cosa intendi con "non funziona"? Aggiungo solo il tuo esempio ai test github.com/onmyway133/ISO8601/blob/master/ISO8601Tests/… e funziona
onmyway133


3

Sulla base di questa sintesi: https://github.com/justinmakaila/NSDate-ISO-8601/blob/master/NSDateISO8601.swift , il seguente metodo può essere utilizzato per convertire NSDate nella stringa della data ISO 8601 nel formato yyyy-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"];
    }

Il limite Sper il sotto-secondo era la chiave qui
Mark Meyer

Nota se hai bisogno di più di 3 S, non puoi usare il formattatore della data, devi analizzarlo manualmente.
malhal

-1

Questo è un po 'più semplice e mette la data in 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()

-1

Utilizzando Swift 3.0 e 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
        }
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.