Làm cách nào để lấy ngày ISO 8601 trên iOS?


115

Thật dễ dàng để lấy chuỗi ngày ISO 8601 (ví dụ 2004-02-12T15:19:21+00:00:) trong PHP date('c'), nhưng làm cách nào để lấy chuỗi ngày đó trong Objective-C (iPhone)? Có một cách ngắn gọn tương tự để làm điều đó?

Đây là chặng đường dài mà tôi tìm thấy để làm điều đó:

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

Có vẻ như rất nhiều khắt khe đối với một thứ quá trung tâm.


1
Tôi đoán ngắn là trong mắt của người xử lý. Ba dòng mã khá ngắn phải không? Có một lớp con C NSDate mà bạn có thể thêm sẽ làm điều đó với một dòng, tôi vừa vấp phải nó: github.com/soffes/SAMCategories/tree/master/SAMCategories/… . Điều đó nói rằng, thực sự, bạn đã có câu trả lời khá tốt (IMHO) và bạn nên trao giải cho Etienne hoặc rmaddy câu trả lời. Nó chỉ là thực tiễn tốt và thưởng cho những người cung cấp thông tin hữu ích thực sự (nó đã giúp tôi, tôi cần thông tin này hôm nay). Etienne có thể sử dụng số điểm nhiều hơn là rmaddy. Như một dấu hiệu tốt, tôi thậm chí sẽ bỏ phiếu cho câu hỏi của bạn!
David H

Câu trả lời:


212

Sử dụng 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];

Và trong 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
Cảm ơn, Maddy. Tôi vừa thấy câu trả lời của bạn bây giờ, sau khi tôi đăng mã của mình. Là setLocalequan trọng?
JohnK

2
@rmaddy Bạn có thể chỉ cần chỉnh sửa ví dụ của mình để sử dụng ZZZZZ để mọi người sao chép dán không bị cháy.
Jesse Rusak

4
@jlmendezbonini Không. Rất hiếm khi bạn muốn YYYY. Bạn thường muốn yyyy.
rmaddy

7
Nguồn để biết lý do ngôn ngữ nên được đặt thành en_US_POSIX: developer.apple.com/library/mac/qa/qa1480/_index.html
tốt

11
@maddy - Muốn giúp những người khác dễ dàng khám phá điều này thông qua Google và muốn ghi công cho bạn vì đã thực sự tìm ra đúng định dạng, ngôn ngữ, v.v. Nhưng rất vui khi đăng nó như một câu trả lời riêng nếu bạn muốn
Robin

60

iOS 10 giới thiệu một NSISO8601DateFormatterlớp mới để xử lý vấn đề này. Nếu bạn đang sử dụng Swift 3, mã của bạn sẽ giống như sau:

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

và nó cung cấp định dạng gì? Ngoài ra, sẽ rất tốt nếu biết liệu nó có thể xử lý từ: 2016-08-26T12: 39: 00-0000 và 2016-08-26T12: 39: 00-00: 00 và 2016-08-26T12: 39: 00.374-0000
malhal

1
Sau khi ba dòng trong câu trả lời của tôi được chạy, stringcó chứa "2016-09-03T21: 47: 27Z".
TwoStraws

3
bạn KHÔNG THỂ xử lý mili giây với định dạng này.
Enrique

3
@Enrique: bạn nên thêm NSISO8601DateFormatWithFractionalSeconds vào các tùy chọn định dạng để có thể làm việc với mili giây, hãy kiểm tra tài liệu.
PakitoV

1
NSISO8601DateFormatWithFractionalSeconds chỉ hoạt động trên 11.2+. Nếu không, bạn có một vụ tai nạn!
hash3r

27

Để bổ sung cho câu trả lời của điên, định dạng múi giờ phải là "ZZZZZ" (5 lần Z) cho ISO 8601 thay vì một "Z" (dành cho định dạng RFC 822). Ít nhất là trên iOS 6.

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


Cú bắt tuyệt vời! Bất cứ ai khác quan tâm đến điều này, hãy vào liên kết, tìm kiếm 8601 bạn sẽ thấy nó.
David H

Khi múi giờ trong ngày là UTC, có cách nào để bạn có thể hiển thị múi giờ 00:00 thay vì Z (tương đương) không?
shim

Cảm ơn rất nhiều;) đã đập đầu tôi vào tường với một dateformatter trả về ngày tháng không !!! :)
polo987

19

Một vấn đề thường bị bỏ qua là các chuỗi ở định dạng ISO 8601 có thể có mili giây và có thể không.

Nói cách khác, cả "2016-12-31T23: 59: 59.9999999" và "2016-12-01T00: 00: 00" đều hợp pháp, nhưng nếu bạn đang sử dụng bộ định dạng ngày được nhập tĩnh, một trong số chúng sẽ không được phân tích cú pháp .

Bắt đầu từ iOS 10, bạn nên sử dụng ISO8601DateFormatternó xử lý tất cả các biến thể của chuỗi ngày ISO 8601. Xem ví dụ bên dưới:

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)

Đối với iOS 9 trở xuống, hãy sử dụng phương pháp sau với nhiều định dạng dữ liệu.

Tôi đã không tìm thấy câu trả lời bao hàm cả hai trường hợp và tóm tắt sự khác biệt nhỏ này. Đây là giải pháp giải quyết nó:

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

Sử dụng:

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

Tôi cũng khuyên bạn nên kiểm tra bài viết của Apple về bộ định dạng ngày.


Bạn cũng có thể vui lòng cho biết cách nắm giữ NSISO8601DateFormtter trong Obj-C không? (tất nhiên là trên iOS 10 trở lên)
Motti Shneor

8

Vì vậy, hãy sử dụng danh mục của Sam Soffee trên NSDate được tìm thấy ở đây . Với mã đó được thêm vào dự án của bạn, từ đó bạn có thể sử dụng một phương pháp duy nhất trên NSDate:

- (NSString *)sam_ISO8601String

Nó không chỉ là một dòng, nó nhanh hơn nhiều so với cách tiếp cận NSDateFormatter, vì nó được viết bằng C.


1
Vị trí hiện tại của danh mục là ở đây
Perry

1
Tôi khuyên bạn nên chống lại nó, nó có vấn đề ngoại lệ bộ nhớ xảy ra rất ngẫu nhiên
M_Waly

1
@M_Waly bạn đã báo cáo điều này với anh ấy chưa? Tôi đã tạo mã của anh ấy mà không có cảnh báo và nó đã vượt qua kiểm tra phân tích rất tốt.
David H

6

Từ IS8601 , các vấn đề là biểu diễn và múi giờ

  • ISO 8601 = year-month-day time timezone
  • Đối với ngày và giờ, có định dạng cơ bản (YYYYMMDD, hhmmss, ...) và định dạng mở rộng (YYYY-MM-DD, hh: mm: ss, ...)
  • Múi giờ có thể là Zulu, offset hoặc GMT
  • Dấu phân cách cho ngày và giờ có thể là dấu cách, hoặc T
  • Có định dạng tuần cho ngày, nhưng nó hiếm khi được sử dụng
  • Múi giờ có thể là nhiều khoảng trắng sau
  • Thứ hai là tùy chọn

Đây là một số chuỗi hợp lệ

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

Các giải pháp

NSDateFormatter

Vì vậy, đây là định dạng mà tôi đang sử dụng trong onmyway133 ISO8601

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

Giới thiệu về Bảng ký hiệu trường ngày của mãZ định danh

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)

Giới thiệu về locale Định dạng Dữ liệu Sử dụng Cài đặt Ngôn ngữ

Miền địa phương đại diện cho các lựa chọn định dạng cho một người dùng cụ thể, không phải ngôn ngữ ưa thích của người dùng. Chúng thường giống nhau nhưng có thể khác nhau. Ví dụ: một người nói tiếng Anh bản ngữ sống ở Đức có thể chọn tiếng Anh làm ngôn ngữ và Đức là khu vực

Giới thiệu về en_US_POSIX Kỹ thuật Hỏi & Đáp QA1480 NSDateFormatter và Internet Ngày

Mặt khác, nếu bạn đang làm việc với các ngày có định dạng cố định, trước tiên bạn nên đặt ngôn ngữ của bộ định dạng ngày thành thứ gì đó thích hợp với định dạng cố định của bạn. Trong hầu hết các trường hợp, ngôn ngữ tốt nhất để chọn là "en_US_POSIX", một ngôn ngữ được thiết kế đặc biệt để mang lại kết quả tiếng Anh Mỹ bất kể tùy chọn của cả người dùng và hệ thống. "en_US_POSIX" cũng bất biến theo thời gian (nếu Hoa Kỳ, tại một thời điểm nào đó trong tương lai, thay đổi cách định dạng ngày tháng, "en_US" sẽ thay đổi để phản ánh hành vi mới, nhưng "en_US_POSIX" thì không) và giữa các máy ( "en_US_POSIX" hoạt động tương tự trên iOS cũng như trên OS X và tương tự như trên các nền tảng khác).

Các câu hỏi liên quan thú vị


Tôi chỉ cố gắng với 2016-07-23T07: 24: 23Z, nó không hoạt động
Kamen Dobrev

@KamenDobrev Ý bạn là gì khi nói "không hoạt động"? Tôi chỉ cần thêm ví dụ của bạn để kiểm tra github.com/onmyway133/ISO8601/blob/master/ISO8601Tests/... và nó hoạt động
onmyway133


3

Dựa trên ý chính này: https://github.com/justinmakaila/NSDate-ISO-8601/blob/master/NSDateISO8601.swift , phương pháp sau có thể được sử dụng để chuyển đổi chuỗi ngày NSDate thành ISO 8601 ở định dạng 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"];
    }

Nắp Scho phụ thứ hai là chìa khóa ở đây
Mark Meyer

Lưu ý nếu bạn cần nhiều hơn 3 chữ S thì bạn không thể sử dụng bộ định dạng ngày phải phân tích cú pháp thủ công.
malhal

-1

Điều này đơn giản hơn một chút và đặt ngày vào 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

Sử dụng Swift 3.0 và 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
        }
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.