Як отримати дату 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/… . Це сказало, що, дійсно, ви отримали досить хороші відповіді (ІМХО), і ви повинні нагородити Етьєнн або Рмадді. Його просто хороша практика і винагороджує людей, що надають справжню корисну інформацію (мені це допомогло, мені ця інформація потрібна сьогодні). Етьєн міг використовувати очки більше, ніж rmaddy. На знак доброї міри я навіть проголосую ваше запитання!
Девід Н

Відповіді:


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

І в Свіфті:

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
Спасибі, Медді. Щойно я побачив вашу відповідь, після того як я опублікував свій код. Це setLocaleважливо?
JohnK

2
@rmaddy Ви можете просто відредагувати свій приклад, щоб використовувати ZZZZZ, щоб люди не вставляли копію.
Джессі Русак

4
@jlmendezbonini Ні. Дуже рідко хочеться РРРР. Ви зазвичай хочете, щоб.
rmaddy

7
Джерело, чому потрібно встановити локаль на en_US_POSIX: developer.apple.com/library/mac/qa/qa1480/_index.html
1515

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
мальхал

1
Після запуску трьох рядків у моїй відповіді stringміститься "2016-09-03T21: 47: 27Z".
TwoStraws

3
Ви НЕ МОЖЕТЕ звертатися мілісекунди з цим форматом.
Енріке

3
@Enrique: вам слід додати NSISO8601DateFormatWithFractionalSeconds до параметрів форматора, щоб мати можливість працювати з мілісекундами, перевірте документи.
PakitoV

1
NSISO8601DateFormatWithFractionalSeconds працює лише на 11.2+. Інакше у вас трапився збій!
hash3r

27

Як доповнення до відповіді мадді, формат часового поясу повинен бути "ZZZZZ" (5 разів Z) для ISO 8601 замість одного "Z" (що стосується формату RFC 822). Принаймні, на iOS 6.

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


Чудовий улов! Хтось ще зацікавлений у цьому, перейдіть за посиланням, шукайте 8601, ви його побачите.
Девід Н

Коли часовий пояс у даті - 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

Тому використовуйте категорію Сем Софіса на NSDate, знайденому тут . Додавши цей код до вашого проекту, ви зможете використовувати єдиний метод на NSDate:

- (NSString *)sam_ISO8601String

Мало того, що це один рядок, його набагато швидше, ніж підхід NSDateFormatter, оскільки його написано чистою С.


1
Поточне місце розташування категорії тут
Perry

1
Я б радив проти цього, у нього є проблема виключення з пам'яті, яка трапляється дуже випадковим чином
M_Waly

1
@M_Waly ти це повідомив йому? Я створив його код без попереджень, і він пройшов перевірку аналізу просто чудово.
Девід Н

6

Від IS8601 проблемами є представлення та часовий пояс

  • ISO 8601 = year-month-day time timezone
  • Для дати та часу існують базовий (РРРРММДД, гмммсс, ...) та розширений формат (РРРР-ММ-ДД, год: мм: сс, ...)
  • Часовий пояс може бути зулу, зміщення або 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

Рішення

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 технічні запитання та запитання QA1480 NSDateFormatter та Інтернет-дати

З іншого боку, якщо ви працюєте з датами фіксованого формату, спочатку слід встановити локаль форматера дат на щось відповідне вашому фіксованому формату. У більшості випадків найкращим місцем для вибору є "en_US_POSIX", що спеціально розроблено для отримання результатів англійської мови в США, незалежно від налаштувань користувача та системи. "en_US_POSIX" також інваріантний за часом (якщо США в якийсь момент змінить спосіб форматування дат, "en_US" зміниться, щоб відобразити нову поведінку, але "en_US_POSIX" не буде), а також між машинами ( "en_US_POSIX" працює так само, як на OS X, як і на інших платформах).

Цікаві відповідні черги


Я щойно спробував з 2016-07-23T07: 24: 23Z, це не працює
Камен Добрев

@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: мм: 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була ключова максимальна
Марк Мейєр

Зверніть увагу, якщо вам потрібно більше 3 S, тоді ви не можете використовувати форматник дати, щоб розібрати його вручну.
мальхал

-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
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.