2015-01-19 25 views
85

ISO 8601 ve RFC 3339 için biçim standartlarını kullanarak tarih damgası nasıl oluşturulur?Swift: Tarih saat damgası oluşturma ve ISO 8601, RFC 3339, UTC saat dilimi olarak nasıl biçimlendirilir?

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

Biçim:

  • yıl, ay, gün, "XXXX-XXXX"
  • olarak mektup "T

    gol şöyle bir dizedir "bir ayırıcı olarak" XX: XX: XX.XXX "olarak saat, dakika, saniye, milisaniye olarak

  • .
  • "Z" harfi sıfır ofset, a.k.a. UTC, GMT, Zulu zamanı için bir bölge işaretçisi olarak.

En iyi durum: basit, kısa ve basittir

  • Swift kaynak kodu.
  • herhangi bir ek çerçeve, alt proje, cocoapod, C kodu kullanmak gerek yok, vb
  • ben vs StackOverflow, Google, Apple, aramalara ve buna bir Swift cevap bulamadım

.

En çok umut veren sınıflar, NSDate, NSDateFormatter, NSTimeZone.

İlgili Soru & A: How do I get ISO 8601 date in iOS?

Ben bugüne kadar ile geldim en iyisi:

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)) 
+5

Not o ** iOS10 + SADECE DAHİL ISO 8601 YERLEŞİK ** .., sırf sana otomatik olarak tamamlayacaktır. – Fattie

+1

@Fattie Ve - son zaman bu 2300Z milisaniye Zulu/UTC zaman damgasının bir parçası nasıl işleyebilir? Cevap: Matt Longs @ https://stackoverflow.com/a/42101630/3078330 – smat88dd

+1

@ smat88dd - harika ipucu, teşekkürler. "Biçimlendirici üzerinde seçenekler", garip ve vahşi olan hiçbir fikrim yoktu. – Fattie

cevap

219

Xcode 9 • Swift sonradan @keno ISO8601DateFormatter yorumlarına belirtildiği gibi

şimdi daha sonra iOS11 veya .withFractionalSecondsformatOptions destekler 11 veya 4 • iOS:

extension Formatter { 
    static let iso8601: ISO8601DateFormatter = { 
     let formatter = ISO8601DateFormatter() 
     formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] 
     return formatter 
    }() 
} 

Bahçesi testi:

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" 
    } 
} 

Kullanımı: Ben ha Benim durumumda

let stringFromDate = Date().iso8601 // "2017-03-22T13:22:13.933Z" 

if let dateFromString = stringFromDate.dateFromISO8601 { 
    print(dateFromString.iso8601)  // "2017-03-22T13:22:13.933Z" 
} 

enter image description here

+3

Ters dönüşüm uzantısını eklemek faydalı olabilir: 'extension String {var dateFormattedISO8601: NSDate? {return NSDate.Date.formatterISO8601.dateFromString (self)}} ' – Vive

+1

Bunun bir miktar hassaslık kaybettiğine dikkat edin, bu nedenle tarihlerin eşitliğinin, oluşturulan dize ile değil timeInterval ile karşılaştırılmasını sağlamak önemlidir. 'let now = NSDate() let stringFromDate = now.iso8601 tarih bırakmaFromString = stringFromDate.dateFromISO8601! XCTAssertEqual (now.timeIntervalSince1970, dateFromString.timeIntervalSince1970) ' – pixelrevision

+5

Milisaniye gereksiniminiz yoksa, yeni iOS 10' ISO8601DateFormatter' işlemi basitleştirir. Apple'a bir hata raporu (27242248) yayınladım ve bu yeni biçimlendiriciyi de milisaniye belirtme yeteneği sunmak için genişletmelerini istedim (bu yeni biçimlendirici, milisaniye olmadan çoğumuz için kullanılmadığından). – Rob

18

Technical Q&A1480 anlatıldığı gibi en_US_POSIX için yerel ayarlamayı unutmayın. Swift 3'te:

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)) 

sorunu olmayan bir Gregoryen takvimi kullanan bir cihazda iseniz timeZone yanı sıra locale belirtmedikçe, yıl RFC3339/ISO8601 uygun olmayan olmasıdır ve dateFormat dizesi.

Yoksa locale ve timeZone kendini ayarlama yabani ot kurtarmamız ISO8601DateFormatter kullanabilirsiniz: Swift 2 yorumuyla için

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)) 

, previous revision of this answer bakın.

+0

neden yerel ayarları en_US_POSIX olarak ayarlamalıyız? ABD'de olmasak bile mi? – mnemonic23

+0

Eh, _some_ sabit yerel ayarlamaya ihtiyacınız var ve ISO 8601/RFC 3999 standartlarının sözleşmesi "en_US_POSIX" tarafından sunulan biçimdir. Web’deki tarihleri ​​değiştirmek için _lingua franca_. Ayrıca, bir tarih dizesini kaydederken cihazda bir takvim ve dizge daha sonra tekrar okunduğunda başka bir takvim kullanıldığında tarihleri ​​yanlış yorumlayamazsınız. Ayrıca, hiçbir zaman değiştirilmeyecek bir biçime ihtiyacınız vardır (bu nedenle 'tr_US_POSIX'i kullanırsınız ve' tr_US' kullanmazsınız). Daha fazla bilgi için [Teknik Soru & Cevap 1480] (https://developer.apple.com/library/ios/qa/qa1480/_index.html) veya RFC/ISO standartlarına bakın. – Rob

1

ve DynamoDB - LastUpdated sütunu (Unix Zaman Damgası) Normal Zamana çevirmek için. 1460650607601 - ile 2016-04-14 16:16:47 +0000 aşağı dönüştürülür:

LastUpdated ilk değer Leo Dabus sürümünü tamamlamak için

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) 

      } 
1

, projelerde için destek eklendi

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 
    } 
} 
Swift 3

Xcode 8 ve: yazılı Swift ve Objective-C, ayrıca muhtemelen en iyi değil ama noktası alacağı, isteğe bağlı milisaniye için destek eklendi

3

Gelecekte, küçük bir baş ağrısına sahip olabilen biçimin değiştirilmesi gerekebilir.dateFromISO8601 bir uygulamada her yeri çağırır. Uygulamayı sarmak için bir sınıf ve protokol kullanın, tarih saat biçimi çağrısını tek bir yerde değiştirmek daha kolay olacaktır. Mümkünse RFC3339'u kullanın, daha eksiksiz bir temsili. DateFormatProtocol ve DateFormat bağımlılık enjeksiyonları için mükemmeldir.

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 
     } 
    } 

} 
10

sen doğru çalışması için Formatlayıcıdaki birkaç seçeneklerini ayarlamak gerekir Rails 4+ JSON feed'inden bir tarihle ISO8601DateFormatter() kullanımı (ve tabii Geçen Mili gerekmez) istiyorsanız aksi halde date(from: string) işlevi sıfırlanır. İşte kullanıyorum budur: İşte

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 
    }() 
} 

bir oyun ekran görüntüsünde değil ayetler seçenekleri kullanarak sonucudur:

enter image description here

+0

Ayrıca '.withFractionalSeconds' seçeneklerine de eklemeniz gerekirdi, ancak bunu zaten denedim ve 'libC++ abi.dylib' türünde bir hata atmaya devam ediyor: NSException türünün yakalanmamış istisnasıyla sonlanıyor. –

+0

Swift 4'te benim için çalışmıyor. – MEnnabah

+0

@MEnnabah Swift 4'te benim için iyi çalışıyor. Bir hata mı alıyorsunuz? –

3

ayrıca Andrés Torres Marroquin ve Leo Dabus iltifat için, ben var bir kesirli saniye koruyan sürüm. Herhangi bir yerde belgelenmiş bir dosyayı bulamıyorum, ancak Apple hem kesikli hem de çıktıdaki kesikli saniyeleri 3 basamaklı olarak kesiyor (SSSSSSS kullanılarak belirtilmiş olsa da, Unicode tr35-31'un aksine).

Bunun çoğu kullanım durumu için muhtemelen gerekli olmadığını vurgulamalıyım. Ayrıca, dizeyi yeniden oluşturmak için tamsayı modülü kullandığını unutmayın. Double.truncatingRemainder() değerleri yukarı ve aşağı doğru yuvarlar.

Xcode 9/8 ve Swift sadece tek satır ile bir dize oluşturmak bakalım yeni ISO8601DateFormatter sınıf yoktur 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 { 
     var data = Formatter.iso8601.string(from: self) 
     if let fractionStart = data.range(of: "."), 
      let fractionEnd = data.index(fractionStart.lowerBound, offsetBy: 7, limitedBy: data.endIndex) { 
      let fractionRange = fractionStart.lowerBound..<fractionEnd 
      let intVal = Int64(1000000 * self.timeIntervalSince1970) 
      let newFraction = String(format: ".%06d", intVal % 1000000) 
      data.replaceSubrange(fractionRange, with: newFraction) 
     } 
     return data 
    } 
} 

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 
    } 
} 
+0

Bu, tüm diğer çözümlerin milisaniye cinsinden kesildiği bir mikro saniye hassasiyet düzeyine ulaşılmasını sağlaması açısından bence en iyi cevaptır. –

+0

Tarihin tüm kesirli saniye ile korunmasını istiyorsanız, tarihinizi sunucuya kaydederken/alırken yalnızca bir çift (referans tarihinden itibaren geçen zaman aralığı) kullanmalısınız. –

+0

@LeoDabus evet, tüm sistemi kontrol ediyorsanız ve birlikte çalışmanız gerekmiyorsa. Cevapta söylediğim gibi, bu çoğu kullanıcı için gerekli değildir. Ancak, her zaman web API'larındaki veri biçimlendirmesi üzerinde kontrolümüz yoktur ve Android ve Python (en azından) 6 basamaklı kesirli kesinlik korumasını koruyarak bazen uygun bir şekilde uyulması gerekir. –

2

. Geriye dönük uyumluluk için eski bir C-kütüphanesi kullandım. Umarım bu birileri için yararlıdır.

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) 
     } 
    } 
} 
0

iOS10 veya daha yeni ISO8601DateFormatter kullanır.

iOS9 veya daha eski sürümlerde DateFormatter'u kullanır.

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 
     } 
    }() 
} 
İlgili konular