日付のタイムスタンプを作成し、ISO 8601、RFC 3339、UTCタイムゾーンとしてフォーマットする方法


186

どのようにするためのフォーマット規格使用して、日付のタイムスタンプを生成するためにISO 8601およびRFC 3339を

目標は次のような文字列です。

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

フォーマット:

  • 年、月、日、「XXXX-XX-XX」
  • 区切り文字としての文字「T」
  • 時、分、秒、ミリ秒、「XX:XX:XX.XXX」。
  • ゼロオフセット、別名UTC、GMT、ズールー時間のゾーン指定子としての文字「Z」

最良の場合:

  • シンプルで短く、簡単なSwiftソースコード。
  • 追加のフレームワーク、サブプロジェクト、cocoapod、Cコードなどを使用する必要はありません。

StackOverflow、Google、Appleなどを検索しましたが、これに対するSwiftの回答は見つかりませんでした。

最も有望に見えるのクラスがありますNSDateNSDateFormatterNSTimeZone

関連Q&A: iOSでISO 8601の日付を取得するにはどうすればよいですか?

これが私がこれまでに思いついた最高のものです:

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
iOS10 +にはISO 8601組み込みが含まれているだけであることに注意してください。自動補完されます。
Fattie

2
@Fattieそして-タイムスタンプの最後の.234Zミリ秒Zulu / UTC部分をどのように処理できますか?回答:Matt Longs @ stackoverflow.com/a/42101630/3078330
smat88dd 2017年

1
@ smat88dd-素晴らしいヒント、ありがとう。「フォーマッターのオプション」があり、奇妙でワイルドであるという手がかりはありませんでした。
Fattie

Linuxで動作するソリューションを探しています。
neoneye

@neoneyeだけでは、古いバージョン(無地たDateFormatter)を使用すると、グレゴリオ暦へのISO8601を変更stackoverflow.com/a/28016692/2303865
レオDabus

回答:


392

Swift 4•iOS 11.2.1以降

extension ISO8601DateFormatter {
    convenience init(_ formatOptions: Options, timeZone: TimeZone = TimeZone(secondsFromGMT: 0)!) {
        self.init()
        self.formatOptions = formatOptions
        self.timeZone = timeZone
    }
}

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

ここに画像の説明を入力してください


3
逆の変換拡張機能を追加するために便利になるだろう:extension String { var dateFormattedISO8601: NSDate? {return NSDate.Date.formatterISO8601.dateFromString(self)} }
Viveの

1
これは少し精度が失われることに注意してください。日付の等価性が、timeIntervalではなく生成された文字列を介して比較されることを確認することが重要です。 let now = NSDate() let stringFromDate = now.iso8601 let dateFromString = stringFromDate.dateFromISO8601! XCTAssertEqual(now.timeIntervalSince1970, dateFromString.timeIntervalSince1970)
pixelrevision 2016年

7
言うまでもなく、ミリ秒が必要ない場合、新しいiOS 10 ISO8601DateFormatterはプロセスを簡素化します。私はバグ報告(27242248)をAppleに発行し、ミリ秒を指定する機能も提供するようにこの新しいフォーマッターを拡張するように依頼しました(この新しいフォーマッターは、ミリ秒なしでは多くの人に使用されないため)。
Rob

1
ではRFC3339我々はノートを見つけることができます 「注:ISO 8601定義の日付と時間が区切ら『T』は、この構文を使用しているアプリケーションは、読みやすさのために、選択することができ、フル日付とフルタイムで区切っ指定するには、(例えば)。スペース文字。」T例: なしの日付形式もカバーしています2016-09-21 21:05:10+00:00か?
manRo 2016

3
@LeoDabusはい、しかしこれは「Swift iso8601」の最初の結果です。私のコメントは、将来これに遭遇し、OPに向けられなかった他の開発者に警告することを意図したものでした。
thislooksfun 2017

38

en_US_POSIX説明されているようにロケールを設定することを忘れないでくださいテクニカルQ&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に準拠しないことです。 localetimeZoneおよびとdateFormat文字列。

またはISO8601DateFormatter、設定の雑草から抜け出すために使用できますlocaletimeZone自分自身:

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レンディションについては、この回答の以前のリビジョンを参照してください。


ロケールをen_US_POSIXに設定する理由 米国にいなくても?
ニーモニック

2
まあ、あなたはいくつかの一貫したロケールが必要であり、ISO 8601 / RFC 3999標準の規則は、が提供する形式ですen_US_POSIX。これは、ウェブ上で日付を交換するための共通言語です。また、日付の文字列を保存するときにデバイスで1つのカレンダーが使用され、後で文字列が読み戻されるときに別のカレンダーが使用された場合、日付が誤って解釈されることはありません。また、絶対に変更されないことが保証されている形式が必要です(そのため、を使用しen_US_POSIX、使用しないのはこのためですen_US)。詳細については、テクニカルQ&A 1480またはそれらのRFC / ISO標準を参照してください。
Rob

24

ISO8601DateFormatter()Rails 4+ JSONフィードからの日付でを使用する場合(そしてもちろんミリ秒は必要ありません)、フォーマッタが正しく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
    }()
}

以下は、プレイグラウンドのスクリーンショットにないオプションの詩を使用した結果です。

ここに画像の説明を入力してください


あなたもオプションに含める必要があります.withFractionalSecondsが、私はすでにそれを試しました、そしてそれはエラーを投げ続けますlibc++abi.dylib: terminating with uncaught exception of type NSException
レオダバス2017年

@MEnnabah Swift 4でうまく動作します。エラーが発生しますか?
Matt Long

@LeoDabus、あなたと同じエラーが出ました、あなたはそれを解決しましたか?
フリーマン2017

カスタムJSONDecoder DateDecodingStrategy stackoverflow.com/a/46458771/2303865
Leo

@freemanすべての小数秒で日付を保持したい場合は、サーバーに日付を保存/受信するときに、2倍(基準日からの時間間隔)を使用することをお勧めします。.deferredToDateCodableプロトコルを使用する場合は、デフォルトの日付デコード戦略を使用します
Leo

6

スウィフト5

あなたはiOSの11.0以上/ MacOSの10.13+をターゲットにしている場合は、単に使用ISO8601DateFormatterしてwithInternetDateTimewithFractionalSeconds、オプションので、のように:

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"

5

AndrésTorresMarroquínとLeo Dabusをさらに引き立たせるために、小数秒を保持するバージョンがあります。どこにも記載されていませんが、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
    }
}

これは、他のすべてのソリューションがミリ秒で切り捨てられるマイクロ秒レベルの精度に到達できるという点で、私の意見では最良の答えです。
Michael A. McCloskey 2017

すべての小数秒を含む日付を保持する場合は、サーバーに日付を保存/受信するときに、2倍(基準日からの時間間隔)を使用する必要があります。
レオダバス2017

@LeoDabusはい、システム全体を制御し、相互運用する必要がない場合。私が答えで言ったように、これはほとんどのユーザーにとって必要ではありません。ただし、すべての人がWeb APIのデータ形式を常に制御できるわけではありません。AndroidとPythonは(少なくとも)6桁の小数精度を保持するため、時々、追跡する必要があります。
Eli Burke

4

用途 ISO8601DateFormatteriOS10以降でします。

用途 DateFormatteriOS9以前でします。

スウィフト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
        }
    }()
}

3

私の場合、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)

            }

3

将来的には、フォーマットを変更する必要が生じる可能性があります。これは、アプリ内のあらゆる場所でdate.dateFromISO8601呼び出しが発生するという小さな頭痛の種になる可能性があります。クラスとプロトコルを使用して実装をラップします。1つの場所で日時形式の呼び出しを変更する方が簡単です。可能であれば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
        }
    }

}

3

ISO8601DateFormatter1行だけの文字列を作成できる新しいクラスがあります。下位互換性のために、古い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)
        }
    }
}

1

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

0

手動の文字列マスクまたは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

これは良い答えですが、使用.iso8601にはミリ秒は含まれません。
ステファンアレンツ

0

オブジェクトパラダイムの許容可能な回答に基づく

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())")
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.