Jak uzyskać datę ISO 8601 w systemie iOS?

115

Łatwo jest pobrać ciąg daty ISO 8601 (na przykład 2004-02-12T15:19:21+00:00) w PHP za pośrednictwem date('c'), ale jak można go uzyskać w Objective-C (iPhone)? Czy można to zrobić w podobny sposób?

Oto długa droga, jaką udało mi się zrobić:

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

Wydaje się, że to strasznie dużo bzdury jak na coś tak ważnego.

John K.
źródło
1
Myślę, że krótki jest w oku patrzącego. Trzy linijki kodu są dość krótkie, prawda? Istnieje podklasa C NSDate, którą możesz dodać, która zrobi to za pomocą jednej linii, właśnie o nią się potknąłem : github.com/soffes/SAMCategories/tree/master/SAMCategories/… . To powiedziawszy, naprawdę, otrzymałeś całkiem dobre odpowiedzi (IMHO) i powinieneś przyznać odpowiedź Etienne lub rmaddy. To po prostu dobra praktyka i nagradza ludzi dostarczających naprawdę pomocnych informacji (pomogło mi, potrzebowałem tych informacji dzisiaj). Etienne mógłby wykorzystać punkty bardziej niż rmaddy. Na znak dobrej miary zagłosuję nawet na Twoje pytanie!
David H

Odpowiedzi:

212

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

A w 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
źródło
1
Dzięki, Maddy. Właśnie zobaczyłem twoją odpowiedź teraz, po wysłaniu mojego kodu. Czy to setLocaleważne?
JohnK
2
@rmaddy Możesz po prostu edytować swój przykład, aby używał ZZZZZ, aby ludzie kopiujący i wklejający nie zostali spaleni.
Jesse Rusak
4
@jlmendezbonini Nie. Bardzo rzadko chcesz YYYY. Zwykle chcesz yyyy.
rmaddy
7
Źródło wskazujące, dlaczego język powinien być ustawiony na en_US_POSIX: developer.apple.com/library/mac/qa/qa1480/_index.html
yood
11
@maddy - chciałem ułatwić innym znalezienie tego przez Google i chciałem dać ci zasługę za faktyczne wykopanie prawidłowego formatu, lokalizacji itp. Ale z przyjemnością opublikuję to jako osobną odpowiedź, jeśli chcesz
Robin
60

iOS 10 wprowadza nową NSISO8601DateFormatterklasę, która obsługuje właśnie to. Jeśli używasz Swift 3, Twój kod będzie wyglądał mniej więcej tak:

let formatter = ISO8601DateFormatter()
let date = formatter.date(from: "2016-08-26T12:39:00Z")
let string = formatter.string(from: Date())
TwoStraws
źródło
i jaki ma format? Dobrze byłoby również wiedzieć, czy poradzi sobie z: 2016-08-26T12: 39: 00-0000 i 2016-08-26T12: 39: 00-00: 00 i 2016-08-26T12: 39: 00.374-0000
malhal
1
Po uruchomieniu trzech wierszy w mojej odpowiedzi stringzawiera „2016-09-03T21: 47: 27Z”.
TwoStraws
3
ty NIE MOGĄ obsługiwać milisekund w tym formacie.
Enrique
3
@Enrique: należy dodać NSISO8601DateFormatWithFractionalSeconds do opcji programu formatującego, aby móc pracować z milisekundami, sprawdź dokumenty.
PakitoV
1
NSISO8601DateFormatWithFractionalSeconds działa tylko w wersji 11.2+. W przeciwnym razie masz awarię!
hash3r
27

Jako uzupełnienie odpowiedzi maddy'ego, format strefy czasowej powinien mieć format „ZZZZZ” (5 razy Z) dla ISO 8601 zamiast pojedynczego „Z” (który jest dla formatu RFC 822). Przynajmniej na iOS 6.

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

Etienne
źródło
Ładny chwyt! Każdy inny zainteresowany tym przejdź do linku, wyszukaj 8601, zobaczysz.
David H
Kiedy strefa czasowa w dniu to UTC, czy istnieje sposób, aby wyświetlić 00:00 zamiast Z (które są równoważne)?
shim
Wielkie dzięki;) waliłam głową w ścianę z datownikiem zwracającym zerową datę !!! :)
polo987
19

Często pomijanym problemem jest to, że ciągi znaków w formacie ISO 8601 mogą mieć milisekundy, a mogą nie.

Innymi słowy, zarówno „2016-12-31T23: 59: 59.9999999”, jak i „2016-12-01T00: 00: 00” są prawidłowe, ale jeśli używasz programu formatującego datę wpisywanego statycznie, jeden z nich nie zostanie przeanalizowany .

Począwszy od iOS 10 , powinieneś używać ISO8601DateFormattertego, który obsługuje wszystkie odmiany ciągów daty ISO 8601. Zobacz przykład poniżej:

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)

W przypadku systemu iOS 9 i starszych użyj następującego podejścia z wieloma elementami formatującymi dane.

Nie znalazłem odpowiedzi, która obejmowałaby oba przypadki i ukazywałaby tę subtelną różnicę. Oto rozwiązanie, które to rozwiązuje:

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

Stosowanie:

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

Polecam też sprawdzić artykułem Apple o formatach daty.

Vadim Bulavin
źródło
Czy mógłbyś również podać, jak zdobyć NSISO8601DateFormtter w Obj-C? (oczywiście na iOS 10 i nowszych
wersjach
8

Skorzystaj więc z kategorii Sama Soffee w NSDate, którą znajdziesz tutaj . Po dodaniu tego kodu do projektu możesz odtąd używać jednej metody w NSDate:

- (NSString *)sam_ISO8601String

Nie tylko jest to jedna linia, ale jest znacznie szybsza niż podejście NSDateFormatter, ponieważ jest napisane w czystym C.

David H.
źródło
1
Obecna lokalizacja kategorii jest tutaj
Perry
1
odradzałbym to, ma problem z wyjątkiem pamięci, który zdarza się bardzo losowo
M_Waly
1
@M_Waly czy zgłosiłeś mu to? Zbudowałem jego kod bez ostrzeżeń i pomyślnie przeszedł testy analizy.
David H
6

Od IS8601 problemy dotyczą reprezentacji i strefy czasowej

  • ISO 8601 = year-month-day time timezone
  • Dla daty i godziny dostępne są podstawowe (RRRRMMDD, ggmmss, ...) i rozszerzone (RRRR-MM-DD, gg: mm: ss, ...)
  • Strefa czasowa może być zulu, przesunięta lub GMT
  • Separatorem daty i godziny może być spacja lub T
  • Istnieje format tygodniowy dla daty, ale jest on rzadko używany
  • Po strefie czasowej może być dużo spacji
  • Drugi jest opcjonalny

Oto kilka prawidłowych ciągów

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

Rozwiązania

NSDateFormatter

Oto format, którego używam w onmyway133 ISO8601

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

Informacje o Zidentyfikatorze Tabela symboli pola daty

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)

Informacje o locale formatowaniu danych przy użyciu ustawień regionalnych

Lokalizacje reprezentują opcje formatowania dla konkretnego użytkownika, a nie preferowany język użytkownika. Często są takie same, ale mogą się różnić. Na przykład osoba, dla której język angielski jest językiem ojczystym, mieszkający w Niemczech, może wybrać język angielski, a region - Niemcy

Informacje en_US_POSIX techniczne Pytania i odpowiedzi QA1480 NSDateFormatter i daty internetowe

Z drugiej strony, jeśli pracujesz z datami o stałym formacie, powinieneś najpierw ustawić ustawienia regionalne programu formatującego datę na coś odpowiedniego dla twojego stałego formatu. W większości przypadków najlepszym językiem do wyboru jest „en_US_POSIX”, które zostało specjalnie zaprojektowane w celu uzyskania wyników w języku angielskim, niezależnie od preferencji użytkownika i systemu. „en_US_POSIX” jest również niezmienna w czasie (jeśli USA w pewnym momencie w przyszłości zmienią sposób formatowania dat, „en_US” zmieni się, aby odzwierciedlić nowe zachowanie, ale „en_US_POSIX” nie) i między maszynami ( „en_US_POSIX” działa tak samo na iOS, jak na OS X i na innych platformach).

Ciekawe pytania pokrewne

onmyway133
źródło
Właśnie próbowałem z 2016-07-23T07: 24: 23Z, to nie działa
Kamen Dobrev
@KamenDobrev co masz na myśli mówiąc „nie działa”? Po prostu dodaję twój przykład do testów github.com/onmyway133/ISO8601/blob/master/ISO8601Tests/ ... i działa
onmyway133
3

Na podstawie tego sedna: https://github.com/justinmakaila/NSDate-ISO-8601/blob/master/NSDateISO8601.swift można użyć następującej metody do konwersji NSDate na ciąg daty ISO 8601 w formacie rrrr-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
źródło
SKluczowy był limit na sekundę
Mark Meyer.
Zwróć uwagę, że jeśli potrzebujesz więcej niż 3 S, nie możesz użyć programu formatującego datę, aby przeanalizować go ręcznie.
malhal
-1

Jest to trochę prostsze i umieszcza datę w 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
źródło
-1

Korzystanie ze Swift 3.0 i 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
źródło