Как мне получить дату 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

Это кажется ужасной чепухой для чего-то столь центрального.

JohnK
источник
1
Я думаю, что короткое в глазах смотрящего. Три строки кода - это довольно мало, не так ли? Есть подкласс C NSDate, который вы можете добавить, который будет делать это с помощью одной строки, я только что наткнулся на него: github.com/soffes/SAMCategories/tree/master/SAMCategories/… . Тем не менее, на самом деле у вас есть довольно хорошие ответы (ИМХО), и вам следует наградить ответом Этьена или Мадди. Это просто хорошая практика и вознаграждает людей, предоставляющих реальную полезную информацию (мне это помогло, мне эта информация понадобилась сегодня). Этьен мог использовать очки больше, чем мэдди. В знак хорошей меры я даже проголосую за ваш вопрос!
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())
rmaddy
источник
1
Спасибо, Мэдди. Я только что увидел ваш ответ, после того как выложил свой код. Это setLocaleважно?
JohnK
2
@rmaddy Вы можете просто отредактировать свой пример, чтобы использовать ZZZZZ, чтобы люди, копирующие вставку, не сгорели.
Джесси Русак
4
@jlmendezbonini Нет. ГГГГ бывает очень редко. Обычно вы хотите гггг.
rmaddy
7
Источник, почему для локали следует установить en_US_POSIX: developer.apple.com/library/mac/qa/qa1480/_index.html
yood
11
@maddy - Хотел сделать так, чтобы другие находили это через Google, и хотел поблагодарить вас за то, что вы действительно нашли правильный формат, локаль и т. д. Но с удовольствием опубликую это как отдельный ответ, если хотите
Робин
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())
TwoStraws
источник
а какой формат дает? Также было бы неплохо узнать, может ли он справиться с: 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 03
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 )

Etienne
источник
Отличный улов! Всем, кто заинтересован в этом, перейдите по ссылке, найдите 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 и
новее
8

Так что используйте категорию Сэма Соффи на NSDate, найденную здесь . С этим кодом, добавленным в ваш проект, вы можете с этого момента использовать единственный метод в NSDate:

- (NSString *)sam_ISO8601String

Это не только одна строка, но и намного быстрее, чем подход NSDateFormatter, поскольку он написан на чистом C.

Дэвид Х
источник
1
Текущее местоположение категории здесь
Перри
1
Я бы посоветовал этого не
делать
1
@M_Waly ты ему об этом сообщил? Я построил его код без предупреждений, и он отлично прошел аналитические проверки.
David H
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 и Internet Dates

С другой стороны, если вы работаете с датами фиксированного формата, вам следует сначала установить языковой стандарт средства форматирования даты на что-то подходящее для вашего фиксированного формата. В большинстве случаев лучше всего выбрать «en_US_POSIX», язык, специально разработанный для получения результатов на английском языке (США) независимо от пользовательских и системных предпочтений. "en_US_POSIX" также инвариантен во времени (если США в какой-то момент в будущем изменят способ форматирования дат, "en_US" изменится, чтобы отразить новое поведение, а "en_US_POSIX" - нет), и между компьютерами ( «en_US_POSIX» работает на iOS так же, как и на OS X, и на других платформах).

Интересные похожие вопросы

onmyway133
источник
Я только что пробовал с 2016-07-23T07: 24: 23Z, не работает
Камен Добрев
@KamenDobrev, что вы имеете в виду, говоря "не работает"? Я просто добавляю ваш пример в тесты github.com/onmyway133/ISO8601/blob/master/ISO8601Tests/… и он работает
onmyway133 01
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, вы не можете использовать средство форматирования даты, и вам придется анализировать его вручную.
Malhal 03
-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()
iphaaw
источник
-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
        }
    }
}
Денис Кривицкий
источник