Bagaimana cara mendapatkan tanggal ISO 8601 di iOS?

115

Cukup mudah untuk mendapatkan string tanggal ISO 8601 (misalnya, 2004-02-12T15:19:21+00:00) dalam PHP melalui date('c'), tetapi bagaimana cara mendapatkannya di Objective-C (iPhone)? Apakah ada cara singkat yang serupa untuk melakukannya?

Inilah cara lama saya melakukannya:

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

Tampaknya terlalu banyak omong kosong untuk sesuatu yang begitu sentral.

JohnK
sumber
1
Saya kira pendek ada di mata yang melihatnya. Tiga baris kode cukup pendek, bukan? Ada subkelas C NSDate yang dapat Anda tambahkan yang akan melakukannya dengan satu baris, saya baru saja tersandung di atasnya: github.com/soffes/SAMCategories/tree/master/SAMCategories/… . Bisa dikatakan, sungguh, Anda mendapat jawaban yang cukup bagus (IMHO) dan Anda harus memberikan jawaban kepada Etienne atau rmaddy. Ini hanya latihan yang baik dan memberi penghargaan kepada orang-orang yang memberikan info yang sangat membantu (ini membantu saya, saya membutuhkan info ini hari ini). Etienne bisa memanfaatkan poin lebih banyak dari rmaddy. Sebagai tanda tindakan yang baik, saya bahkan akan memberikan suara untuk pertanyaan Anda!
David H

Jawaban:

212

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

Dan di 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
sumber
1
Terima kasih, Maddy. Saya baru saja melihat jawaban Anda sekarang, setelah saya memposting kode saya. Apakah setLocalepenting?
JohnK
2
@rmaddy Anda dapat mengedit contoh Anda untuk menggunakan ZZZZZ sehingga orang yang menyalin dan menempel tidak terbakar.
Jesse Rusak
4
@jlmendezbonini Tidak. Sangat jarang Anda menginginkan YYYY. Anda biasanya ingin yyyy.
rmaddy
7
Sumber mengapa lokal harus disetel ke en_US_POSIX: developer.apple.com/library/mac/qa/qa1480/_index.html
yood
11
@maddy - Ingin memudahkan pencarian orang lain yang menemukan ini melalui Google dan ingin memberi penghargaan kepada Anda karena benar-benar menggali format yang benar, lokal, dll. Tetapi dengan senang hati mempostingnya sebagai jawaban terpisah jika Anda mau
Robin
60

iOS 10 memperkenalkan NSISO8601DateFormatterkelas baru untuk menangani hal ini. Jika Anda menggunakan Swift 3, kode Anda akan menjadi seperti ini:

let formatter = ISO8601DateFormatter()
let date = formatter.date(from: "2016-08-26T12:39:00Z")
let string = formatter.string(from: Date())
TwoStraws
sumber
dan format apa yang diberikannya? Juga akan baik untuk mengetahui apakah itu dapat menangani dari: 2016-08-26T12: 39: 00-0000 dan 2016-08-26T12: 39: 00-00: 00 dan 2016-08-26T12: 39: 00.374-0000
malhal
1
Setelah tiga baris di jawaban saya dijalankan, stringberisi "2016-09-03T21: 47: 27Z".
TwoStraws
3
Anda TIDAK BISA menangani milidetik dengan format ini.
Enrique
3
@Enrique: Anda harus menambahkan NSISO8601DateFormatWithFractionalSeconds ke opsi pemformat agar dapat bekerja dengan milidetik, periksa dokumennya.
PakitoV
1
NSISO8601DateFormatWithFractionalSeconds hanya berfungsi pada 11.2+. Jika tidak, Anda akan mengalami crash!
hash3r
27

Sebagai pelengkap jawaban maddy, format zona waktu harus "ZZZZZ" (5 kali Z) untuk ISO 8601, bukan "Z" tunggal (yang untuk format RFC 822). Setidaknya di iOS 6.

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

Etienne
sumber
Tangkapan bagus! Siapa pun yang tertarik dengan ini, buka tautannya, cari 8601, Anda akan melihatnya.
David H
Jika zona waktu pada tanggal tersebut adalah UTC, apakah ada cara agar Anda dapat menampilkan 00:00 alih-alih Z (yang setara)?
shim
Terima kasih banyak;) membenturkan kepalaku ke dinding dengan dateformatter mengembalikan tanggal nihil !!! :)
polo987
19

Masalah yang sering diabaikan adalah string dalam format ISO 8601 mungkin memiliki milidetik dan mungkin juga tidak.

Dengan kata lain, "2016-12-31T23: 59: 59.9999999" dan "2016-12-01T00: 00: 00" adalah sah, tetapi jika Anda menggunakan formatter tanggal yang diketik statis, salah satunya tidak akan diurai .

Mulai dari iOS 10, Anda harus menggunakan ISO8601DateFormatteryang menangani semua variasi string tanggal ISO 8601. Lihat contoh di bawah ini:

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)

Untuk iOS 9 dan yang lebih lama, gunakan pendekatan berikut dengan beberapa pemformat data.

Saya belum menemukan jawaban yang mencakup kedua kasus dan mengaburkan perbedaan halus ini. Inilah solusi yang mengatasinya:

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

Pemakaian:

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

Saya juga merekomendasikan untuk memeriksa artikel Apple tentang format tanggal.

Vadim Bulavin
sumber
Bisakah Anda juga menyatakan cara mendapatkan NSISO8601DateFormtter di Obj-C? (di iOS 10 dan yang lebih baru, tentu saja)
Motti Shneor
8

Jadi gunakan kategori Sam Soffee di NSDate yang ditemukan di sini . Dengan kode itu ditambahkan ke proyek Anda, Anda dapat sejak saat itu menggunakan satu metode di NSDate:

- (NSString *)sam_ISO8601String

Tidak hanya satu baris, ini jauh lebih cepat daripada pendekatan NSDateFormatter, karena ditulis dalam C murni.

David H.
sumber
1
Lokasi kategori saat ini ada di sini
Perry
1
saya akan menyarankan untuk tidak melakukannya, ini memiliki masalah pengecualian memori yang terjadi sangat acak
M_Waly
1
@M_Waly apakah Anda melaporkan ini padanya? Saya membuat kodenya tanpa peringatan dan lulus pemeriksaan analisis dengan baik.
David H
6

Dari IS8601 , masalahnya adalah representasi dan zona waktu

  • ISO 8601 = year-month-day time timezone
  • Untuk tanggal dan waktu, ada format dasar (YYYYMMDD, hhmmss, ...) dan format diperpanjang (YYYY-MM-DD, hh: mm: dd, ...)
  • Zona waktu bisa Zulu, offset atau GMT
  • Pemisah untuk tanggal dan waktu dapat berupa spasi, atau T
  • Ada format minggu untuk tanggal, tetapi jarang digunakan
  • Zona waktu bisa menjadi banyak spasi setelahnya
  • Kedua adalah opsional

Berikut beberapa string yang valid

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

Solusi

NSDateFormatter

Jadi, inilah format yang saya gunakan di onmyway133 ISO8601

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

Tentang Zpengidentifikasi Tabel Simbol Bidang Tanggal

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)

Tentang locale Memformat Data Menggunakan Pengaturan Lokal

Lokal mewakili pilihan format untuk pengguna tertentu, bukan bahasa pilihan pengguna. Ini seringkali sama tetapi bisa berbeda. Misalnya, penutur asli bahasa Inggris yang tinggal di Jerman mungkin memilih bahasa Inggris sebagai bahasa dan Jerman sebagai wilayah

Tentang en_US_POSIX Tanya Jawab Teknis QA1480 NSDateFormatter dan Tanggal Internet

Di sisi lain, jika Anda bekerja dengan tanggal format tetap, Anda harus terlebih dahulu mengatur lokal formatter tanggal ke sesuatu yang sesuai untuk format tetap Anda. Dalam kebanyakan kasus, lokal terbaik untuk dipilih adalah "en_US_POSIX", sebuah lokal yang secara khusus dirancang untuk menghasilkan hasil bahasa Inggris AS terlepas dari preferensi pengguna dan sistem. "en_US_POSIX" juga tidak berubah dalam waktu (jika AS, pada suatu saat di masa depan, mengubah cara format tanggal, "en_US" akan berubah untuk mencerminkan perilaku baru, tetapi "en_US_POSIX" tidak akan), dan di antara mesin ( "en_US_POSIX" bekerja dengan cara yang sama di iOS seperti di OS X, dan seperti di platform lain).

Quetions terkait yang menarik

onmyway133
sumber
Saya baru saja mencoba dengan 2016-07-23T07: 24: 23Z, tidak berhasil
Kamen Dobrev
@KamenDobv apa yang Anda maksud dengan "tidak bekerja"? Saya hanya menambahkan contoh Anda ke tes github.com/onmyway133/ISO8601/blob/master/ISO8601Tests/… dan berfungsi
onmyway133
3

Berdasarkan inti ini: https://github.com/justinmakaila/NSDate-ISO-8601/blob/master/NSDateISO8601.swift , metode berikut dapat digunakan untuk mengonversi NSDate ke string tanggal ISO 8601 dalam format 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"];
    }
Zack Zhu
sumber
Batas Suntuk sub-detik adalah kuncinya di sini
Mark Meyer
Catatan jika Anda membutuhkan lebih dari 3 S maka Anda tidak dapat menggunakan formatter tanggal harus menguraikannya secara manual.
malhal
-1

Ini sedikit lebih sederhana dan memasukkan tanggal ke dalam 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.dll
sumber
-1

Menggunakan Swift 3.0 dan 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
sumber