Swift: как создать штамп даты и времени в формате ISO 8601, RFC 3339, часовой пояс UTC?
как создать штамп даты и времени, используя стандарты формата для ISO 8601 и RFC 3339?
цель-строка, которая выглядит так:
"2015-01-01T00:00:00.000Z"
:
- год, месяц, день, как "XXXX-XX-XX"
- буква "т" в качестве разделителя
- час, минута, секунды, миллисекунды, как " XX: XX: XX.XXX".
- буква " Z " как обозначение зоны для смещения нуля, a.к. a. UTC, GMT, Зулусское время.
лучший вариант:
- Swift исходный код, который является простым, коротким и простым.
- не нужно использовать никаких дополнительных рамок, подпроект, cocoapod, с кодом и т. д.
Я искал 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 цифры точности) как на входе, так и на выходе (хотя указано с помощью 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
}
}
в моем случае я должен преобразовать столбец 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)
}
есть новый 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
}
}