ฉันจะรับวันที่ ISO 8601 บน iOS ได้อย่างไร


115

ง่ายพอที่จะรับสตริงวันที่ ISO 8601 (เช่น2004-02-12T15:19:21+00:00) ใน PHP ผ่านทางdate('c')แต่จะรับมันใน Objective-C (iPhone) ได้อย่างไร มีวิธีสั้น ๆ ในทำนองเดียวกันนี้หรือไม่?

นี่เป็นวิธีที่ยาวนานที่ฉันพบว่าต้องทำ:

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

ดูเหมือนว่าเป็นเครื่องมือที่น่ากลัวสำหรับบางสิ่งที่เป็นศูนย์กลาง


1
ฉันเดาว่าสั้นอยู่ในสายตาของคนดู โค้ดสามบรรทัดค่อนข้างสั้นไม่ใช่เหรอ? มีคลาสย่อย C NSDate ที่คุณสามารถเพิ่มได้ซึ่งจะทำได้ด้วยบรรทัดเดียวฉันเพิ่งสะดุดมัน: github.com/soffes/SAMCategories/tree/master/SAMCategories/… . นั่นหมายความว่าคุณได้คำตอบที่ดี (IMHO) และคุณควรให้รางวัลแก่ Etienne หรือ rmaddy เป็นการปฏิบัติที่ดีและให้รางวัลแก่ผู้ที่ให้ข้อมูลที่เป็นประโยชน์อย่างแท้จริง (ช่วยฉันได้ฉันต้องการข้อมูลนี้ในวันนี้) Etienne สามารถใช้คะแนนได้มากกว่า rmaddy เพื่อเป็นสัญญาณของมาตรการที่ดีฉันจะโหวตคำถามของคุณ!
David H

คำตอบ:


212

ใช้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];

และใน 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
ขอบคุณ Maddy ฉันเพิ่งเห็นคำตอบของคุณตอนนี้หลังจากที่ฉันโพสต์รหัสของฉัน เป็นsetLocaleความสำคัญ?
JohnK

2
@rmaddy คุณอาจแก้ไขตัวอย่างของคุณเพื่อใช้ ZZZZZ เพื่อไม่ให้คนคัดลอกวางถูกเบิร์น
Jesse Rusak

4
@jlmendezbonini ไม่ค่ะหายากมากอยากไปปปปป คุณมักจะต้องการ yyyy
rmaddy

7
แหล่งที่มาของสาเหตุที่ควรตั้งค่าภาษาเป็น en_US_POSIX: developer.apple.com/library/mac/qa/qa1480/_index.html
yood

11
@maddy - ต้องการให้คนอื่นค้นพบสิ่งนี้ผ่าน Google ได้ง่ายและต้องการให้เครดิตคุณสำหรับการขุดรูปแบบสถานที่และอื่น ๆ ที่ถูกต้อง แต่ยินดีที่จะโพสต์เป็นคำตอบแยกต่างหากหากคุณต้องการ
Robin

60

iOS 10 ขอแนะนำNSISO8601DateFormatterคลาสใหม่เพื่อจัดการกับสิ่งนี้ หากคุณใช้ Swift 3 รหัสของคุณจะเป็นดังนี้:

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

และรูปแบบใดที่ให้? นอกจากนี้ควรทราบด้วยว่าสามารถจัดการได้หรือไม่ตั้งแต่: 2016-08-26T12: 39: 00-0000 และ 2016-08-26T12: 39: 00-00: 00 และ 2016-08-26T12: 39: 00.374-0000
malhal

1
หลังจากเรียกใช้สามบรรทัดในคำตอบของฉันstringมี "2016-09-03T21: 47: 27Z"
TwoStraws

3
คุณไม่สามารถจัดการมิลลิวินาทีด้วยรูปแบบนี้ได้
Enrique

3
@Enrique: คุณควรเพิ่ม NSISO8601DateFormatWithFractionalSeconds ลงในตัวเลือกฟอร์แมตเตอร์เพื่อให้สามารถทำงานกับมิลลิวินาทีตรวจสอบเอกสาร
PakitoV

1
NSISO8601DateFormatWithFractionalSeconds ทำงานบน 11.2+ เท่านั้น ไม่งั้นคุณพังแน่!
hash3r

27

เพื่อเป็นการเสริมคำตอบของแมดดี้รูปแบบเขตเวลาควรเป็น "ZZZZZ" (Z 5 เท่า) สำหรับ ISO 8601 แทนที่จะเป็น "Z" เดียว (ซึ่งเป็นรูปแบบ RFC 822) อย่างน้อยบน iOS 6

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


จับได้เยี่ยม! ใครสนใจเรื่องนี้ไปที่ลิงค์ค้นหา 8601 คุณจะเห็นมัน
David H

เมื่อเขตเวลาในวันที่เป็น UTC มีวิธีที่คุณจะทำให้แสดง 00:00 แทน Z (ซึ่งเทียบเท่า) ได้หรือไม่?
ชิม

ขอบคุณมาก;) กำลังทุบหัวของฉันกับผนังโดยมีรูปแบบวันที่ที่กลับมาไม่มีวันที่ !!! :)
polo987

19

ปัญหาที่มักถูกมองข้ามคือสตริงในรูปแบบ ISO 8601 อาจมีหน่วยมิลลิวินาทีและอาจไม่มี

กล่าวอีกนัยหนึ่งทั้ง "2016-12-31T23: 59: 59.9999999" และ "2016-12-01T00: 00: 00" เป็นสิ่งที่ถูกต้อง แต่ถ้าคุณใช้ตัวจัดรูปแบบวันที่แบบคงที่ระบบจะไม่แยกวิเคราะห์รายการใดรายการหนึ่ง .

ตั้งแต่iOS 10คุณควรใช้ISO8601DateFormatterที่จัดการสตริงวันที่ ISO 8601 รูปแบบต่างๆ ดูตัวอย่างด้านล่าง:

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)

สำหรับiOS 9 และต่ำกว่าให้ใช้แนวทางต่อไปนี้กับรูปแบบข้อมูลหลายรูปแบบ

ฉันยังไม่พบคำตอบที่ครอบคลุมทั้งกรณีและนามธรรมที่แตกต่างกันเล็กน้อยนี้ นี่คือวิธีแก้ปัญหาที่กล่าวถึง:

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

การใช้งาน:

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

ฉันขอแนะนำให้ตรวจสอบบทความของ Appleเกี่ยวกับรูปแบบวันที่


คุณช่วยระบุวิธีรับ NSISO8601DateFormtter ใน Obj-C ได้ไหม (ใน iOS 10 ขึ้นไปแน่นอน)
Motti Shneor

8

ดังนั้นประเภทการใช้แซม Soffee บน NSDate พบที่นี่ เมื่อเพิ่มรหัสดังกล่าวลงในโครงการของคุณคุณสามารถใช้วิธีการเดียวใน NSDate:

- (NSString *)sam_ISO8601String

ไม่เพียงแค่บรรทัดเดียวเท่านั้น แต่ยังเร็วกว่าแนวทาง NSDateFormatter มากเนื่องจากเขียนด้วยภาษาซีบริสุทธิ์


1
ตำแหน่งปัจจุบันของหมวดหมู่อยู่ที่นี่
Perry

1
ฉันจะแนะนำให้ต่อต้านมันมีปัญหาข้อยกเว้นหน่วยความจำซึ่งเกิดขึ้นแบบสุ่มมาก
M_Waly

1
@M_Waly คุณรายงานเรื่องนี้กับเขาหรือไม่? ฉันสร้างรหัสของเขาโดยไม่มีคำเตือนและผ่านการตรวจสอบการวิเคราะห์แล้ว
David H

6

จากIS8601ปัญหาคือการแสดงและเขตเวลา

  • ISO 8601 = year-month-day time timezone
  • สำหรับวันที่และเวลามีรูปแบบพื้นฐาน (YYYYMMDD, hhmmss, ... ) และรูปแบบขยาย (YYYY-MM-DD, hh: mm: ss, ... )
  • เขตเวลาอาจเป็นซูลูออฟเซ็ตหรือ GMT
  • ตัวคั่นสำหรับวันที่และเวลาสามารถเว้นวรรคหรือ T
  • มีรูปแบบสัปดาห์สำหรับวันที่ แต่ไม่ค่อยได้ใช้
  • เขตเวลาสามารถเว้นวรรคหลังได้มาก
  • ประการที่สองเป็นทางเลือก

นี่คือสตริงที่ถูกต้อง

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

โซลูชั่น

  • แยกวิเคราะห์ทีละขั้นตอนเช่นsoffes ISO8601
  • แปลงเป็นรูปแบบพื้นฐานเช่นonmyway133 ISO8601

NSDateFormatter

นี่คือรูปแบบที่ฉันใช้ในonmyway133 ISO8601

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

เกี่ยวกับตารางสัญลักษณ์ฟิลด์วันที่Zตัวระบุ

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)

เกี่ยวกับlocale การจัดรูปแบบข้อมูลโดยใช้การตั้งค่าสถานที่

โลแคลแสดงถึงตัวเลือกการจัดรูปแบบสำหรับผู้ใช้เฉพาะไม่ใช่ภาษาที่ผู้ใช้ต้องการ สิ่งเหล่านี้มักจะเหมือนกัน แต่อาจแตกต่างกันได้ ตัวอย่างเช่นเจ้าของภาษาอังกฤษที่อาศัยอยู่ในเยอรมนีอาจเลือกภาษาอังกฤษเป็นภาษาและเยอรมนีเป็นภูมิภาค

เกี่ยวกับen_US_POSIX Q&A ทางเทคนิค QA1480 NSDateFormatter และวันที่อินเทอร์เน็ต

ในทางกลับกันหากคุณกำลังทำงานกับวันที่ที่มีรูปแบบตายตัวก่อนอื่นคุณควรตั้งค่าภาษาของตัวจัดรูปแบบวันที่เป็นสิ่งที่เหมาะสมกับรูปแบบคงที่ของคุณก่อน ในกรณีส่วนใหญ่ภาษาที่ดีที่สุดในการเลือกคือ "en_US_POSIX" ซึ่งเป็นภาษาที่ออกแบบมาโดยเฉพาะเพื่อให้ได้ผลลัพธ์เป็นภาษาอังกฤษแบบสหรัฐอเมริกาโดยไม่คำนึงถึงความชอบของผู้ใช้และระบบ "en_US_POSIX" ยังไม่แปรผันตามเวลา (หากในบางช่วงของสหรัฐอเมริกาเปลี่ยนรูปแบบวันที่ "en_US" จะเปลี่ยนไปเพื่อแสดงพฤติกรรมใหม่ แต่ "en_US_POSIX" จะไม่) และระหว่างเครื่อง ( "en_US_POSIX" ทำงานเหมือนกันบน iOS เช่นเดียวกับบน OS X และเหมือนกับที่ทำงานบนแพลตฟอร์มอื่น ๆ )

คำถามที่เกี่ยวข้องที่น่าสนใจ


ฉันเพิ่งลองใช้ 2016-07-23T07: 24: 23Z มันไม่ได้ผล
Kamen Dobrev

@KamenDobrev คำว่า "ไม่ทำงาน" หมายความว่าอย่างไร? ฉันเพิ่งเพิ่มตัวอย่างของคุณในการทดสอบgithub.com/onmyway133/ISO8601/blob/master/ISO8601Tests/…และได้ผล
onmyway133


3

จากข้อมูลสำคัญนี้: https://github.com/justinmakaila/NSDate-ISO-8601/blob/master/NSDateISO8601.swiftสามารถใช้วิธีการต่อไปนี้เพื่อแปลง NSDate เป็นสตริงวันที่ ISO 8601 ในรูปแบบของ 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"];
    }

ขีด จำกัดSของวินาทีย่อยเป็นกุญแจสำคัญที่นี่
Mark Meyer

หมายเหตุหากคุณต้องการมากกว่า 3 S คุณไม่สามารถใช้ตัวจัดรูปแบบวันที่ได้ต้องแยกวิเคราะห์ด้วยตนเอง
malhal

-1

วิธีนี้ง่ายกว่าเล็กน้อยและทำให้วันที่เป็น 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

ใช้ Swift 3.0 และ 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
        }
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.