Как я могу проанализировать / создать метку даты и времени, отформатированную с использованием дробных секунд часового пояса UTC (ISO 8601, RFC 3339) в Swift?

Как создать отметку даты и времени, используя стандарты формата для ISO 8601 и RFC 3339?

Цель - это строка, которая выглядит так:

"2015-01-01T00:00:00.000Z"

Формат:

  • год, месяц, день, как «XXXX-XX-XX»
  • буква "Т" как разделитель
  • час, минута, секунды, миллисекунды в формате «XX: XX: XX.XXX».
  • буква «Z» в качестве обозначения зоны для нулевого смещения, также известного как UTC, GMT, зулусское время.

Лучший случай:

  • Исходный код Swift - простой, короткий и понятный.
  • Нет необходимости использовать какие-либо дополнительные фреймворки, подпроекты, cocoapod, код C и т. Д.

Я искал StackOverflow, Google, Apple и т. Д. И не нашел на это ответа Swift.

Наиболее перспективными кажутся классы NSDate < / a>, NSDateFormatter , NSTimeZone.

Связанные вопросы и ответы: Как получить дату ISO 8601 в iOS? < / а>

Вот лучшее, что я придумал:

var now = NSDate()
var formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)
println(formatter.stringFromDate(now))

person joelparkerhenderson    schedule 19.01.2015    source источник
comment
Обратите внимание, что iOS10 + ПРОСТО ВКЛЮЧАЕТ ВСТРОЕННЫЙ ISO 8601 ... он просто заполнится автоматически.   -  person Fattie    schedule 27.04.2017
comment
@Fattie И - как он может обработать эту последнюю часть временной метки в 0,234Z миллисекунды в формате Zulu / UTC? Ответ: Мэтт Лонгс @ stackoverflow.com/a/42101630/3078330   -  person smat88dd    schedule 09.06.2017
comment
@ smat88dd - фантастический совет, спасибо. Я понятия не имел, что есть варианты форматтера, странные и дикие!   -  person Fattie    schedule 09.06.2017
comment
Ищу решение, которое работает на linux.   -  person neoneye    schedule 20.09.2018
comment
@neoneye Просто используйте старую версию (простой DateFormatter) и измените календарь iso8601 на грегорианский stackoverflow.com/a/28016692/2303865   -  person Leo Dabus    schedule 31.10.2018


Ответы (12)


Swift 4 • iOS 11.2.1 или новее

extension ISO8601DateFormatter {
    convenience init(_ formatOptions: Options) {
        self.init()
        self.formatOptions = formatOptions
    }
}

extension Formatter {
    static let iso8601withFractionalSeconds = ISO8601DateFormatter([.withInternetDateTime, .withFractionalSeconds])
}

extension Date {
    var iso8601withFractionalSeconds: String { return Formatter.iso8601withFractionalSeconds.string(from: self) }
}

extension String {
    var iso8601withFractionalSeconds: Date? { return Formatter.iso8601withFractionalSeconds.date(from: self) }
}

Использование:

Date().description(with: .current)  //  Tuesday, February 5, 2019 at 10:35:01 PM Brasilia Summer Time"
let dateString = Date().iso8601withFractionalSeconds   //  "2019-02-06T00:35:01.746Z"

if let date = dateString.iso8601withFractionalSeconds {
    date.description(with: .current) // "Tuesday, February 5, 2019 at 10:35:01 PM Brasilia Summer Time"
    print(date.iso8601withFractionalSeconds)           //  "2019-02-06T00:35:01.746Z\n"
}

iOS 9 • Swift 3 или новее

extension Formatter {
    static let iso8601withFractionalSeconds: DateFormatter = {
        let formatter = DateFormatter()
        formatter.calendar = Calendar(identifier: .iso8601)
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
        return formatter
    }()
}

Кодируемый протокол

Если вам нужно кодировать и декодировать этот формат при работе с протоколом Codable, вы можете создать свои собственные стратегии кодирования / декодирования даты:

extension JSONDecoder.DateDecodingStrategy {
    static let iso8601withFractionalSeconds = custom {
        let container = try $0.singleValueContainer()
        let string = try container.decode(String.self)
        guard let date = Formatter.iso8601withFractionalSeconds.date(from: string) else {
            throw DecodingError.dataCorruptedError(in: container,
                  debugDescription: "Invalid date: " + string)
        }
        return date
    }
}

и стратегия кодирования

extension JSONEncoder.DateEncodingStrategy {
    static let iso8601withFractionalSeconds = custom {
        var container = $1.singleValueContainer()
        try container.encode(Formatter.iso8601withFractionalSeconds.string(from: $0))
    }
}

Тестирование детской площадки

let dates = [Date()]   // ["Feb 8, 2019 at 9:48 PM"]

кодирование

let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601withFractionalSeconds
let data = try! encoder.encode(dates)
print(String(data: data, encoding: .utf8)!)

расшифровка

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601withFractionalSeconds
let decodedDates = try! decoder.decode([Date].self, from: data)  // ["Feb 8, 2019 at 9:48 PM"]

введите описание изображения здесь

person Leo Dabus    schedule 19.01.2015
comment
Было бы полезно добавить противоположное расширение конверсии: extension String { var dateFormattedISO8601: NSDate? {return NSDate.Date.formatterISO8601.dateFromString(self)} } - person Nat; 30.05.2016
comment
Просто обратите внимание, что это немного теряет точность, поэтому важно убедиться, что равенство дат сравнивается через сгенерированную строку, а не timeInterval. let now = NSDate() let stringFromDate = now.iso8601 let dateFromString = stringFromDate.dateFromISO8601! XCTAssertEqual(now.timeIntervalSince1970, dateFromString.timeIntervalSince1970) - person pixelrevision; 18.06.2016
comment
В RFC3339 мы можем найти примечание ПРИМЕЧАНИЕ: ISO 8601 определяет дату и время разделены символом T. Приложения, использующие этот синтаксис, могут выбрать, для удобства чтения, указать полную дату и полный рабочий день, разделенные (скажем) пробелом. Охватывает ли он также формат даты без T, например : 2016-09-21 21:05:10+00:00? - person manRo; 29.09.2016
comment
@LeoDabus - еще раз спасибо. Послушай, чувак, вот тебе загадка: stackoverflow.com/questions/43808693/ - person Fattie; 05.05.2017
comment
@LeoDabus Не могли бы вы объяснить причину, по которой вы расширяете Formatter вместо DateFormatter? - person Leon; 21.06.2017
comment
Нет никакой разницы. Вместо этого вы можете расширить DateFormatter, если хотите. - person Leo Dabus; 21.06.2017
comment
ЭТО НЕ РАБОТАЕТ В LINUX. Если вы также ориентируетесь на Linux, вам необходимо удалить строку Calendar(identifier: .iso8601), иначе произойдет ошибка и произойдет сбой. - person thislooksfun; 04.07.2017
comment
@LeoDabus да, но это первый результат для Swift iso8601. Мой комментарий был призван предупредить других разработчиков, которые столкнутся с этим в будущем, и не был направлен на OP. - person thislooksfun; 05.07.2017
comment
@pixelrevision, если вам нужно убедиться, что дата, сохраненная на сервере, и возвращенная дата совпадают, вам необходимо сохранить дату как Double (timeIntervalSinceReferenceDate). Проверьте это stackoverflow.com/a/47502712/2303865 - person Leo Dabus; 13.12.2017
comment
@keno спасибо Раньше он вылетал при установке ISO8601DateFormatter formatOptions на [.withInternetDateTime, .withFractionalSeconds] Я обновлю ответ соответственно - person Leo Dabus; 23.01.2018
comment
iOS 11 теперь поддерживает дробные секунды с опцией NSISO8601DateFormatWithFractionalSeconds developer.apple.com/documentation / Foundation / - person keno; 23.01.2018
comment
По крайней мере, с Swift 4 это даже не будет компилироваться (первый пример кода): static let iso8601: DateFormatter = { return ISO8601DateFormatter() } ISO8601DateFormatter подклассы Formatter, а не DateFormatter. Я предполагаю, что возвращаемое значение должно быть ISO8601DateFormatter. - person NeverwinterMoon; 13.02.2018
comment
static let iso8601 = ISO8601DateFormatter([.withInternetDateTime, .withFractionalSeconds]) у меня вылетает на iOS11. .withFractionalSeconds вызывает проблему. - person Kirill Kudaev; 31.07.2019
comment
используйте простую версию DateFormatter для старых ОС. Я читал, что вам понадобится минимум ›11.2 - person Leo Dabus; 31.07.2019
comment
Тест не пройден с XCTAssertEqual - person Soheil Novinfard; 09.02.2020
comment
@SoheilNovinfard единственный способ сохранить дату как есть - это отправить timeIntervalSinceReferenceDate на ваш сервер. - person Leo Dabus; 09.02.2020
comment
Пожалуйста, ознакомьтесь с этим вопросом: stackoverflow.com/questions/60136982/ - person Soheil Novinfard; 09.02.2020
comment
@SoheilNovinfard Проверьте ссылки, которые я разместил выше. DateFormatter отбрасывает большую часть дробных секунд в дате. Попробуйте с этим stackoverflow.com/a/47502712/2303865 - person Leo Dabus; 09.02.2020
comment
Другой вариант - использовать dateDecodingStrategy со значением .deferredToDate - person Leo Dabus; 09.02.2020
comment
Я не могу изменить его на . deferredToDate, он исходит из удаленного JSON, и я не решаюсь с форматом даты. Ни в одном из ответов не говорится о декодируемости, пожалуйста, повторно откройте мой вопрос - person Soheil Novinfard; 09.02.2020
comment
Я уже сказал, что там происходит. Невозможно утверждать, что он равен, когда вы отбрасываете наносекунды FloatingPoint - person Leo Dabus; 09.02.2020
comment
Нет ничего лучше мести. Ваш ответ бесполезен в описанной мной ситуации, хотя он похож, но это не то же самое и не может решить эту проблему. Вы можете позволить другим пользователям проверять и думать над ответами, не закрывая вопрос, это противоречит открытости души сообщества. - person Soheil Novinfard; 09.02.2020
comment
@LeoDabus: Да, я согласен, Лео, теперь, переосмыслив историю, я думаю, мне не следовало отрицать их, потому что они связаны с разными вопросами (это настоящая причина, по которой я попросил вас снова открыть ее, и я считаю, что ее следует переосмыслить). -открыт). Я отменю его по истечении установленного времени. Спасибо, в любом случае - person Soheil Novinfard; 09.02.2020
comment
stackoverflow.com/questions/60136982/ - person Leo Dabus; 09.02.2020
comment
Я думаю, кто-то сказал, что при декодировании теряется точность. Но просто соответствующе. У меня есть дата как часть большего json-объекта. Я установил стратегию декодирования как iso8601withFractionalSeconds и try decoder.decode(UserInfo.self, from: data). В базе данных хранится значение "2021-02-19T07:23:09.799Z", но logger.debug("First online: \(userInfo.activity.firstSeenDate)") печатает First online: 2021-02-19 07:23:09 +0000. Это место, где теряется точность? +0000 должно быть миллисекундами? - person Parth Tamane; 19.02.2021

Не забудьте установить для языкового стандарта en_US_POSIX, как описано в Технические вопросы и ответы A1480. В Swift 3:

let date = Date()
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
print(formatter.string(from: date))

Проблема в том, что если вы используете устройство, использующее не григорианский календарь, год не будет соответствовать RFC3339 / ISO8601, если вы не укажете locale, а также строку timeZone и dateFormat.

Или вы можете использовать ISO8601DateFormatter, чтобы избавиться от сорняков настройки locale и timeZone самостоятельно:

let date = Date()
let formatter = ISO8601DateFormatter()
formatter.formatOptions.insert(.withFractionalSeconds)  // this is only available effective iOS 11 and macOS 10.13
print(formatter.string(from: date))

Для представления Swift 2 см. предыдущую версию этого ответа.

person Rob    schedule 19.01.2015
comment
почему мы должны установить локаль на en_US_POSIX? даже если мы не в США? - person axumnemonic; 13.12.2017
comment
Что ж, вам нужна некоторая согласованная локаль, и соглашение стандартов ISO 8601 / RFC 3999 - это формат, предлагаемый en_US_POSIX. Это lingua franca для обмена свиданиями в Интернете. И у вас не может быть неправильной интерпретации дат, если один календарь использовался на устройстве при сохранении строки даты, а другой - когда строка считывается позже. Кроме того, вам нужен формат, который гарантированно никогда не изменится (поэтому вы используете en_US_POSIX, а не en_US). См. Технические вопросы и ответы 1480 или стандарты RFC / ISO для получения дополнительной информации. Информация. - person Rob; 13.12.2017

Если вы хотите использовать ISO8601DateFormatter() с датой из канала JSON Rails 4+ (и, конечно, вам не нужны миллисекунды), вам нужно установить несколько параметров в форматировщике, чтобы он работал правильно, иначе функция date(from: string) вернет ноль. Вот что я использую:

extension Date {
    init(dateString:String) {
        self = Date.iso8601Formatter.date(from: dateString)!
    }

    static let iso8601Formatter: ISO8601DateFormatter = {
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withFullDate,
                                          .withTime,
                                          .withDashSeparatorInDate,
                                          .withColonSeparatorInTime]
        return formatter
    }()
}

Вот результат использования опций по сравнению с скриншотом игровой площадки:

введите описание изображения здесь

person Matt Long    schedule 07.02.2017
comment
Вам нужно будет включить в параметры также .withFractionalSeconds, но я уже пробовал это, и он продолжает выдавать ошибку libc++abi.dylib: terminating with uncaught exception of type NSException. - person Leo Dabus; 12.10.2017
comment
@MEnnabah У меня все отлично работает в Swift 4. Вы получаете сообщение об ошибке? - person Matt Long; 06.11.2017
comment
@LeoDabus, возникла та же ошибка, что и у вас, вы ее решили? - person freeman; 13.12.2017
comment
пользовательский JSONDecoder DateDecodingStrategy stackoverflow.com/a/46458771/2303865 - person Leo Dabus; 13.12.2017
comment
@freeman Если вы хотите сохранить дату со всеми ее дробными секундами, я предлагаю использовать двойной (временной интервал с контрольной даты) при сохранении / получении вашей даты на сервер. И используйте стратегию декодирования даты по умолчанию .deferredToDate при использовании протокола Codable - person Leo Dabus; 13.12.2017
comment
@LeoDabus, спасибо за ваши ответы, наконец, я решил позволить строке даты и времени возврата api без дробного раздела, и причина, почему не использовать двойное значение, заключается в удобочитаемости запроса / ответа API, я узнал об этом из этого сообщение: apiux.com/2013/03/20 / 5-rules-api-date-and-times А доли секунды не так важны для пользователя приложения, так что без него ничего не помешает. - person freeman; 14.12.2017

Swift 5

Если вы ориентируетесь на iOS 11.0+ / macOS 10.13+, вы просто используете ISO8601DateFormatter с параметрами withInternetDateTime и withFractionalSeconds, например:

let date = Date()

let iso8601DateFormatter = ISO8601DateFormatter()
iso8601DateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let string = iso8601DateFormatter.string(from: date)

// string looks like "2020-03-04T21:39:02.112Z"
person jrc    schedule 04.03.2020

Использует ISO8601DateFormatter на iOS10 или новее.

Использует DateFormatter на iOS9 или старше.

Swift 4

protocol DateFormatterProtocol {
    func string(from date: Date) -> String
    func date(from string: String) -> Date?
}

extension DateFormatter: DateFormatterProtocol {}

@available(iOS 10.0, *)
extension ISO8601DateFormatter: DateFormatterProtocol {}

struct DateFormatterShared {
    static let iso8601: DateFormatterProtocol = {
        if #available(iOS 10, *) {
            return ISO8601DateFormatter()
        } else {
            // iOS 9
            let formatter = DateFormatter()
            formatter.calendar = Calendar(identifier: .iso8601)
            formatter.locale = Locale(identifier: "en_US_POSIX")
            formatter.timeZone = TimeZone(secondsFromGMT: 0)
            formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
            return formatter
        }
    }()
}
person neoneye    schedule 08.03.2018

Чтобы еще больше похвалить Андреса Торреса Маррокина и Лео Дабуса, у меня есть версия, в которой сохраняются доли секунды. Я нигде не могу найти это задокументировано, но Apple сокращает доли секунды до микросекунды (3 цифры точности) как на входе, так и на выходе (даже если указано с помощью SSSSSSS, в отличие от Unicode tr35-31).

Я должен подчеркнуть, что это, вероятно, не требуется для большинства случаев использования. Даты в Интернете обычно не требуют точности в миллисекундах, и когда это необходимо, часто лучше использовать другой формат данных. Но иногда нужно определенным образом взаимодействовать с уже существующей системой.

Xcode 8/9 и Swift 3.0–3.2

extension Date {
    struct Formatter {
        static let iso8601: DateFormatter = {
            let formatter = DateFormatter()
            formatter.calendar = Calendar(identifier: .iso8601)
            formatter.locale = Locale(identifier: "en_US_POSIX")
            formatter.timeZone = TimeZone(identifier: "UTC")
            formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXXXX"
            return formatter
        }()
    }

    var iso8601: String {
        // create base Date format 
         var formatted = DateFormatter.iso8601.string(from: self)

        // Apple returns millisecond precision. find the range of the decimal portion
         if let fractionStart = formatted.range(of: "."),
             let fractionEnd = formatted.index(fractionStart.lowerBound, offsetBy: 7, limitedBy: formatted.endIndex) {
             let fractionRange = fractionStart.lowerBound..<fractionEnd
            // replace the decimal range with our own 6 digit fraction output
             let microseconds = self.timeIntervalSince1970 - floor(self.timeIntervalSince1970)
             var microsecondsStr = String(format: "%.06f", microseconds)
             microsecondsStr.remove(at: microsecondsStr.startIndex)
             formatted.replaceSubrange(fractionRange, with: microsecondsStr)
        }
         return formatted
    }
}

extension String {
    var dateFromISO8601: Date? {
        guard let parsedDate = Date.Formatter.iso8601.date(from: self) else {
            return nil
        }

        var preliminaryDate = Date(timeIntervalSinceReferenceDate: floor(parsedDate.timeIntervalSinceReferenceDate))

        if let fractionStart = self.range(of: "."),
            let fractionEnd = self.index(fractionStart.lowerBound, offsetBy: 7, limitedBy: self.endIndex) {
            let fractionRange = fractionStart.lowerBound..<fractionEnd
            let fractionStr = self.substring(with: fractionRange)

            if var fraction = Double(fractionStr) {
                fraction = Double(floor(1000000*fraction)/1000000)
                preliminaryDate.addTimeInterval(fraction)
            }
        }
        return preliminaryDate
    }
}
person Eli Burke    schedule 14.04.2017
comment
На мой взгляд, это лучший ответ, поскольку он позволяет достичь микросекундного уровня точности, когда все другие решения усекаются за миллисекунды. - person Michael A. McCloskey; 29.09.2017
comment
Если вы хотите сохранить дату со всеми ее дробными секундами, вы должны использовать только двойной (временной интервал с контрольной даты) при сохранении / получении вашей даты на сервер. - person Leo Dabus; 13.12.2017
comment
@LeoDabus: да, если вы контролируете всю систему и вам не нужно взаимодействовать. Как я уже сказал в ответе, для большинства пользователей в этом нет необходимости. Но не все мы можем контролировать форматирование данных в веб-API, а поскольку Android и Python (по крайней мере) сохраняют шестизначную дробную точность, иногда необходимо последовать их примеру. - person Eli Burke; 14.12.2017

В моем случае мне нужно преобразовать столбец DynamoDB - lastUpdated (временная метка Unix) в обычное время.

Начальное значение lastUpdated было: 1460650607601 - преобразовано в 2016-04-14 16:16:47 +0000 через:

   if let lastUpdated : String = userObject.lastUpdated {

                let epocTime = NSTimeInterval(lastUpdated)! / 1000 // convert it from milliseconds dividing it by 1000

                let unixTimestamp = NSDate(timeIntervalSince1970: epocTime) //convert unix timestamp to Date
                let dateFormatter = NSDateFormatter()
                dateFormatter.timeZone = NSTimeZone()
                dateFormatter.locale = NSLocale.currentLocale() // NSLocale(localeIdentifier: "en_US_POSIX")
                dateFormatter.dateFormat =  "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
                dateFormatter.dateFromString(String(unixTimestamp))

                let updatedTimeStamp = unixTimestamp
                print(updatedTimeStamp)

            }
person ioopl    schedule 15.04.2016

В будущем, возможно, потребуется изменить формат, что может вызвать небольшую головную боль из-за вызова date.dateFromISO8601 повсюду в приложении. Используйте класс и протокол, чтобы обернуть реализацию, изменить вызов формата даты и времени в одном месте будет проще. По возможности используйте RFC3339, это более полное представление. DateFormatProtocol и DateFormat отлично подходят для внедрения зависимостей.

class AppDelegate: UIResponder, UIApplicationDelegate {

    internal static let rfc3339DateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
    internal static let localeEnUsPosix = "en_US_POSIX"
}

import Foundation

protocol DateFormatProtocol {

    func format(date: NSDate) -> String
    func parse(date: String) -> NSDate?

}


import Foundation

class DateFormat:  DateFormatProtocol {

    func format(date: NSDate) -> String {
        return date.rfc3339
    }

    func parse(date: String) -> NSDate? {
        return date.rfc3339
    }

}


extension NSDate {

    struct Formatter {
        static let rfc3339: NSDateFormatter = {
            let formatter = NSDateFormatter()
            formatter.calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierISO8601)
            formatter.locale = NSLocale(localeIdentifier: AppDelegate.localeEnUsPosix)
            formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)
            formatter.dateFormat = rfc3339DateFormat
            return formatter
        }()
    }

    var rfc3339: String { return Formatter.rfc3339.stringFromDate(self) }
}

extension String {
    var rfc3339: NSDate? {
        return NSDate.Formatter.rfc3339.dateFromString(self)
    }
}



class DependencyService: DependencyServiceProtocol {

    private var dateFormat: DateFormatProtocol?

    func setDateFormat(dateFormat: DateFormatProtocol) {
        self.dateFormat = dateFormat
    }

    func getDateFormat() -> DateFormatProtocol {
        if let dateFormatObject = dateFormat {

            return dateFormatObject
        } else {
            let dateFormatObject = DateFormat()
            dateFormat = dateFormatObject

            return dateFormatObject
        }
    }

}
person Gary Davies    schedule 05.12.2016

Есть новый класс ISO8601DateFormatter, который позволяет вам создавать строку из одной строки. Для обратной совместимости я использовал старую C-библиотеку. Надеюсь, это кому-то пригодится.

Swift 3.0

extension Date {
    var iso8601: String {
        if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
            return ISO8601DateFormatter.string(from: self, timeZone: TimeZone.current, formatOptions: .withInternetDateTime)
        } else {
            var buffer = [CChar](repeating: 0, count: 25)
            var time = time_t(self.timeIntervalSince1970)
            strftime_l(&buffer, buffer.count, "%FT%T%z", localtime(&time), nil)
            return String(cString: buffer)
        }
    }
}
person Thomas Szabo    schedule 28.06.2017

Чтобы дополнить версию Leo Dabus, я добавил поддержку проектов, написанных на Swift и Objective-C, а также добавил поддержку необязательных миллисекунд, вероятно, не самый лучший, но вы поймете суть:

Xcode 8 и Swift 3

extension Date {
    struct Formatter {
        static let iso8601: DateFormatter = {
            let formatter = DateFormatter()
            formatter.calendar = Calendar(identifier: .iso8601)
            formatter.locale = Locale(identifier: "en_US_POSIX")
            formatter.timeZone = TimeZone(secondsFromGMT: 0)
            formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
            return formatter
        }()
    }

    var iso8601: String {
        return Formatter.iso8601.string(from: self)
    }
}


extension String {
    var dateFromISO8601: Date? {
        var data = self
        if self.range(of: ".") == nil {
            // Case where the string doesn't contain the optional milliseconds
            data = data.replacingOccurrences(of: "Z", with: ".000000Z")
        }
        return Date.Formatter.iso8601.date(from: data)
    }
}


extension NSString {
    var dateFromISO8601: Date? {
        return (self as String).dateFromISO8601
    }
}
person Andrés Torres Marroquín    schedule 03.11.2016

Без ручных масок String или TimeFormatters

import Foundation

struct DateISO: Codable {
    var date: Date
}

extension Date{
    var isoString: String {
        let encoder = JSONEncoder()
        encoder.dateEncodingStrategy = .iso8601
        guard let data = try? encoder.encode(DateISO(date: self)),
        let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as?  [String: String]
            else { return "" }
        return json?.first?.value ?? ""
    }
}

let dateString = Date().isoString
person Dmitrii Z    schedule 04.12.2018
comment
Это хороший ответ, но использование .iso8601 не включает миллисекунды. - person Stefan Arentz; 25.05.2020

На основе приемлемого ответа в объектной парадигме

class ISO8601Format
{
    let format: ISO8601DateFormatter

    init() {
        let format = ISO8601DateFormatter()
        format.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        format.timeZone = TimeZone(secondsFromGMT: 0)!
        self.format = format
    }

    func date(from string: String) -> Date {
        guard let date = format.date(from: string) else { fatalError() }
        return date
    }

    func string(from date: Date) -> String { return format.string(from: date) }
}


class ISO8601Time
{
    let date: Date
    let format = ISO8601Format() //FIXME: Duplication

    required init(date: Date) { self.date = date }

    convenience init(string: String) {
        let format = ISO8601Format() //FIXME: Duplication
        let date = format.date(from: string)
        self.init(date: date)
    }

    func concise() -> String { return format.string(from: date) }

    func description() -> String { return date.description(with: .current) }
}

позывной

let now = Date()
let time1 = ISO8601Time(date: now)
print("time1.concise(): \(time1.concise())")
print("time1: \(time1.description())")


let time2 = ISO8601Time(string: "2020-03-24T23:16:17.661Z")
print("time2.concise(): \(time2.concise())")
print("time2: \(time2.description())")
person Aaronium112    schedule 24.03.2020