Wie erhalte ich ein ISO 8601-Datum unter iOS?

115

Es ist leicht genug, die ISO 8601-Datumszeichenfolge (zum Beispiel 2004-02-12T15:19:21+00:00) in PHP über abzurufen date('c'), aber wie erhält man sie in Objective-C (iPhone)? Gibt es einen ähnlich kurzen Weg, dies zu tun?

Hier ist der lange Weg, den ich gefunden habe:

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

Es scheint eine Menge Rigmarole für etwas so Zentrales zu sein.

JohnK
quelle
1
Ich denke, kurz ist im Auge des Betrachters. Drei Codezeilen sind ziemlich kurz, oder? Es gibt eine C NSDate-Unterklasse, die Sie hinzufügen können und die dies mit einer Zeile erledigt. Ich bin gerade darauf gestoßen: github.com/soffes/SAMCategories/tree/master/SAMCategories/… . Das heißt, wirklich, Sie haben ziemlich gute Antworten (IMHO) und Sie sollten entweder Etienne oder rmaddy die Antwort geben. Es ist nur eine gute Übung und belohnt Leute, die wirklich hilfreiche Informationen liefern (es hat mir geholfen, ich brauchte diese Informationen heute). Etienne könnte die Punkte mehr als rmaddy gebrauchen. Als Zeichen einer guten Maßnahme werde ich sogar Ihre Frage abstimmen!
David H

Antworten:

212

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

Und 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())
rmaddy
quelle
1
Danke, Maddy. Ich habe gerade Ihre Antwort gesehen, nachdem ich meinen Code gepostet hatte. Ist das setLocalewichtig
JohnK
2
@rmaddy Sie können Ihr Beispiel einfach bearbeiten, um ZZZZZ zu verwenden, damit das Einfügen von Personen nicht verbrannt wird.
Jesse Rusak
4
@jlmendezbonini Nein. Es ist sehr selten, dass Sie JJJJ wollen. Normalerweise möchten Sie JJJJ.
rmaddy
7
Quelle, warum das Gebietsschema auf en_US_POSIX gesetzt werden sollte: developer.apple.com/library/mac/qa/qa1480/_index.html
yood
11
@maddy - Wollte es anderen leicht machen, dies über Google zu finden, und wollte Ihnen die Ehre erweisen, das richtige Format, Gebietsschema usw. gefunden zu haben. Wenn Sie möchten, können Sie es gerne als separate Antwort veröffentlichen
Robin
60

iOS 10 führt eine neue NSISO8601DateFormatterKlasse ein, um genau dies zu handhaben. Wenn Sie Swift 3 verwenden, sieht Ihr Code ungefähr so ​​aus:

let formatter = ISO8601DateFormatter()
let date = formatter.date(from: "2016-08-26T12:39:00Z")
let string = formatter.string(from: Date())
TwoStraws
quelle
und welches Format gibt es? Es wäre auch gut zu wissen, ob es von: 2016-08-26T12: 39: 00-0000 und 2016-08-26T12: 39: 00-00: 00 und 2016-08-26T12: 39: 00.374-0000 verarbeitet werden kann
Malhal
1
Nachdem die drei Zeilen in meiner Antwort ausgeführt wurden, stringenthält "2016-09-03T21: 47: 27Z".
TwoStraws
3
Sie NICHT behandeln Millisekunden mit diesem Format.
Enrique
3
@Enrique: Sie sollten den Formatierungsoptionen NSISO8601DateFormatWithFractionalSeconds hinzufügen, um mit Millisekunden arbeiten zu können. Überprüfen Sie die Dokumente.
PakitoV
1
NSISO8601DateFormatWithFractionalSeconds funktioniert nur unter 11.2+. Sonst hast du einen Absturz!
Hash3r
27

Als Ergänzung zur Antwort von Maddy sollte das Zeitzonenformat für ISO 8601 "ZZZZZ" (5 mal Z) anstelle eines einzelnen "Z" (für das RFC 822-Format) sein. Zumindest unter iOS 6.

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

Etienne
quelle
Großer Fang! Alle anderen, die daran interessiert sind, gehen Sie zum Link, suchen Sie nach 8601, Sie werden es sehen.
David H
Wenn die Zeitzone am Datum UTC ist, gibt es eine Möglichkeit, die 00:00 anstelle der Z anzuzeigen (die gleichwertig sind)?
Shim
Vielen Dank;) schlug meinen Kopf gegen die Wand mit einem Datumsformatierer, der kein Datum zurückgab !!! :)
polo987
19

Ein häufig übersehenes Problem ist, dass Zeichenfolgen im ISO 8601-Format möglicherweise Millisekunden haben und nicht.

Mit anderen Worten, sowohl "2016-12-31T23: 59: 59.9999999" als auch "2016-12-01T00: 00: 00" sind legitim. Wenn Sie jedoch einen statisch datierten Datumsformatierer verwenden, wird einer von ihnen nicht analysiert .

Ab iOS 10 sollten Sie ISO8601DateFormatteralle Variationen von ISO 8601-Datumszeichenfolgen verwenden. Siehe Beispiel unten:

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)

Verwenden Sie für iOS 9 und niedriger den folgenden Ansatz mit mehreren Datenformatierern.

Ich habe keine Antwort gefunden, die beide Fälle abdeckt und diesen subtilen Unterschied abstrahiert. Hier ist die Lösung, die es anspricht:

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

Verwendung:

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

Ich empfehle außerdem, den Apple-Artikel über Datumsformatierer zu lesen.

Vadim Bulavin
quelle
Könnten Sie bitte auch angeben, wie Sie NSISO8601DateFormtter in Obj-C erhalten können? (unter iOS 10 und höher natürlich)
Motti Shneor
8

Verwenden Sie also die Kategorie von Sam Soffee für NSDate, die Sie hier finden . Mit diesem Code, der Ihrem Projekt hinzugefügt wurde, können Sie fortan eine einzige Methode für NSDate verwenden:

- (NSString *)sam_ISO8601String

Es ist nicht nur eine Zeile, es ist auch viel schneller als der NSDateFormatter-Ansatz, da es in reinem C geschrieben ist.

David H.
quelle
1
Der aktuelle Standort der Kategorie ist hier
Perry
1
Ich würde davon abraten, es hat ein Speicherausnahmeproblem, das sehr zufällig
auftritt
1
@M_Waly hast du ihm das gemeldet? Ich habe seinen Code ohne Warnungen erstellt und er hat die Analyseprüfungen problemlos bestanden.
David H
6

Ab IS8601 sind die Probleme die Darstellung und die Zeitzone

  • ISO 8601 = year-month-day time timezone
  • Für Datum und Uhrzeit gibt es grundlegende (JJJJMMTT, hhmmss, ...) und erweiterte Formate (JJJJ-MM-TT, hh: mm: ss, ...).
  • Die Zeitzone kann Zulu, Offset oder GMT sein
  • Trennzeichen für Datum und Uhrzeit können Leerzeichen oder sein T
  • Es gibt Wochenformate für Datum, aber es wird selten verwendet
  • Zeitzone kann viele Leerzeichen nach sein
  • Zweitens ist optional

Hier sind einige gültige Zeichenfolgen

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

Lösungen

NSDateFormatter

Hier ist das Format, das ich in onmyway133 ISO8601 verwende

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

Informationen zur ZKennung Datumsfeld Symboltabelle

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)

Informationen zum locale Formatieren von Daten mithilfe der Gebietsschemaeinstellungen

Gebietsschemas stellen die Formatierungsoptionen für einen bestimmten Benutzer dar, nicht die bevorzugte Sprache des Benutzers. Diese sind oft gleich, können aber unterschiedlich sein. Beispielsweise könnte ein englischer Muttersprachler, der in Deutschland lebt, Englisch als Sprache und Deutschland als Region auswählen

Über en_US_POSIX Technische Q & A QA1480 NSDateFormatter und Internet Termine

Wenn Sie jedoch mit Datumsangaben mit festem Format arbeiten, sollten Sie zuerst das Gebietsschema des Datumsformatierers auf einen Wert festlegen, der für Ihr festes Format geeignet ist. In den meisten Fällen ist "en_US_POSIX" das beste Gebietsschema zur Auswahl. Dieses Gebietsschema wurde speziell entwickelt, um unabhängig von Benutzer- und Systemeinstellungen Ergebnisse in US-Englisch zu erzielen. "en_US_POSIX" ist auch zeitlich unveränderlich (wenn die USA zu einem späteren Zeitpunkt die Formatierung von Datumsangaben ändern, ändert sich "en_US" entsprechend dem neuen Verhalten, "en_US_POSIX" jedoch nicht) und zwischen Maschinen ( "en_US_POSIX" funktioniert unter iOS genauso wie unter OS X und auf anderen Plattformen.

Interessante verwandte Fragen

onmyway133
quelle
Ich habe es gerade mit 2016-07-23T07: 24: 23Z versucht, es funktioniert nicht
Kamen Dobrev
@KamenDobrev was meinst du mit "nicht arbeiten"? Ich füge Ihr Beispiel einfach zu den Tests hinzu github.com/onmyway133/ISO8601/blob/master/ISO8601Tests/… und es funktioniert
onmyway133
3

Basierend auf diesem Kern: https://github.com/justinmakaila/NSDate-ISO-8601/blob/master/NSDateISO8601.swift kann die folgende Methode verwendet werden, um NSDate in eine ISO 8601-Datumszeichenfolge im Format JJJJ-MM zu konvertieren -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
quelle
Die Obergrenze Sfür die Sekunde war hier entscheidend
Mark Meyer
Beachten Sie, wenn Sie mehr als 3 S benötigen, können Sie den Datumsformatierer nicht verwenden, um ihn manuell zu analysieren.
Malhal
-1

Dies ist etwas einfacher und gibt das Datum in UTC ein.

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
quelle
-1

Verwenden von Swift 3.0 und 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
quelle