Swift: как создать отметку времени даты и формат как ISO 8601, RFC 3339, часовой пояс UTC?
как создать отметку времени даты, используя стандарты формата для ISO 8601 и RFC 3339?
цель-это строка, которая выглядит так:
"2015-01-01T00:00:00.000Z"
:
- год, месяц, день, как "XXXX-XX-XX"
- буква "т" в качестве разделителя
- часы, минуты, секунды, миллисекунды, как "хХ:Хх:ХХ.ХХХ".
- буква "Z" как обозначение зоны для смещения нуля, также известная как UTC, GMT, Зулусское время.
в лучшем случае:
- Swift исходный код, который является простым, коротким и простым.
- нет необходимости использовать какие-либо дополнительные рамки, подпроекты, cocoapod, C-код и т. д.
Я искал StackOverflow, Google, Apple и т. д. и не нашел быстрого ответа.
классы, которые кажутся наиболее перспективными являются NSDate
,NSDateFormatter
, NSTimeZone
.
В Ответ: как получить дату ISO 8601 в iOS?
9 ответов:
Xcode 9 * Swift 4 * iOS 11 или более поздней версии
как уже упоминалось в комментариях @keno
ISO8601DateFormatter
теперь поддерживает.withFractionalSeconds
formatOptions в iOS11 или более поздней версии:extension Formatter { static let iso8601: ISO8601DateFormatter = { let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] return formatter }() }
площадка для испытаний:
let dateString = Formatter.iso8601.string(from: Date()) // "2018-01-23T03:06:46.232Z" if let date = Formatter.iso8601.date(from: dateString) { print(date) // "2018-01-23 03:06:46 +0000\n" }
Xcode 8.2 * Swift 3.0.2
extension 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 }() } extension Date { var iso8601: String { return Formatter.iso8601.string(from: self) } } extension String { var dateFromISO8601: Date? { return Formatter.iso8601.date(from: self) // "Mar 22, 2017, 10:22 AM" } }
использование:
let stringFromDate = Date().iso8601 // "2017-03-22T13:22:13.933Z" if let dateFromString = stringFromDate.dateFromISO8601 { print(dateFromString.iso8601) // "2017-03-22T13:22:13.933Z" }
не забудьте установить язык, на
en_US_POSIX
как описано в Технический Q & A1480. В Swift 3:let date = Date() let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" formatter.timeZone = TimeZone(secondsFromGMT: 0) formatter.locale = Locale(identifier: "en_US_POSIX") 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 исполнение, см. предыдущая редакция этого ответа.
Если вы хотите использовать
ISO8601DateFormatter()
С датой из Rails 4 + JSON feed (и, конечно, не нужно millis), вам нужно установить несколько параметров на форматере, чтобы он работал правильно, иначеdate(from: string)
функция вернет nil. Вот что я использую: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 }() }
вот результат использования вариантов стихов не в скриншоте игровой площадки:
в будущем формат может потребоваться изменить, что может быть небольшая головная боль, имеющая дату.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 } } }
чтобы еще больше похвалить Андрес Торрес Маррокин и Лео Дабус, у меня есть версия, которая сохраняет дробные секунды. Я не могу найти его документированным нигде, но Apple усекает дробные секунды до микросекунды (3 цифры точности) как на входе, так и на выходе (хотя указано с помощью SSSSSS, в отличие от 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 } }
в моем случае мне нужно преобразовать столбец DynamoDB-lastUpdated (unix Timestamp) в обычное время.
начальное значение 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) }
есть новый
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) } } }
использует
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 } }() }
чтобы дополнить версию 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 } }