[iOS 10] NSDateIntervalを使ってみる #wwdc

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

こんにちは。モバイルアプリサービス部の加藤 潤です。
今回はiOS 10で追加されたNSDateIntervalクラスを使って何ができるかを確認してみます。

本記事は Apple からベータ版として公開されているドキュメントを情報源としています。 そのため、正式版と異なる情報になる可能性があります。ご留意の上、お読みください。

NSDateIntervalとは

NSDateIntervalはiOS 10からFoundationフレームワークに追加されたクラスで、2つの日時の間隔を表現することが出来るクラスです。 以下のようなプロパティを持ちます。

  • startDate (開始日時)
  • endDate (終了日時)
  • duration (上記の日時の間隔(秒))

  • ちなみにSwiftにおいてはNSDateIntervalのブリッジとなるDateIntervalというstructureが用意されているのでそちらを使用します。
    これらはちょうどNSStringStringのような関係にあたります。
    以下の実装ではSwiftなのでDateIntervalを使用します。

実装

それでは実装を確認していきましょう。

NSDateIntervalを生成する

NSDateIntervalを生成する方法は以下の3種類が用意されています。

  • 引数を何も指定しないで生成する方法
  • 開始日時と日時間隔を指定して生成する方法
  • 開始日時と終了日時を指定して生成する方法
// 引数指定なし
let dateInterval_1 = DateInterval()
print(dateInterval_1)

// 開始日時と日時間隔を指定
let dateInterval_2 = DateInterval(start: Date(), duration: 60)
print(dateInterval_2)

// 開始日時と終了日時を指定
let startDate = Date()
let endDate = startDate.addingTimeInterval(3600)
let dateInterval_3 = DateInterval(start: startDate, end: endDate)
print(dateInterval_3)

実行結果は下記のようになります。

(Start Date) 2016-06-19 13:47:33 +0000 + (Duration) 0.0 seconds = (End Date) 2016-06-19 13:47:33 +0000
(Start Date) 2016-06-19 13:47:33 +0000 + (Duration) 60.0 seconds = (End Date) 2016-06-19 13:48:33 +0000
(Start Date) 2016-06-19 13:47:33 +0000 + (Duration) 3600.0 seconds = (End Date) 2016-06-19 14:47:33 +0000

引数を何も指定しないで生成した場合は開始日時と終了日時に同じ現在日時がセットされます。 この場合もちろんdurationは0となります。

ある日時がNSDateIntervalの日時の範囲に含まれているかどうかをチェックする

NSDateIntervalの関数、func contains(_ date: Date) -> Boolを使用するとある日時がNSDateIntervalが持つ日時の範囲に含まれているかどうかをチェックすることができます。

let calendar = Calendar(calendarIdentifier: .gregorian)

var startDateComponents = DateComponents()
startDateComponents.year = 2016
startDateComponents.month = 6
startDateComponents.day = 1

var endDateComponents = DateComponents()
endDateComponents.year = 2016
endDateComponents.month = 7
endDateComponents.day = 0

let start = calendar?.date(from: startDateComponents)
let end = calendar?.date(from: endDateComponents)
let dateInterval = DateInterval(start: start!, end: end!)

// 2016年5月31日は範囲に含まれない
var targetDateComponents_1 = DateComponents()
targetDateComponents_1.year = 2016
targetDateComponents_1.month = 6
targetDateComponents_1.day = 0
let target1 = calendar?.date(from: targetDateComponents_1)
let contains1 = dateInterval.contains(target1!)
print("contains1: \(contains1)")

// 2016年6月10日は範囲に含まれる
var targetDateComponents_2 = DateComponents()
targetDateComponents_2.year = 2016
targetDateComponents_2.month = 6
targetDateComponents_2.day = 10
let target2 = calendar?.date(from: targetDateComponents_2)
let contains2 = dateInterval.contains(target2!)
print("contains2: \(contains2)")

実行結果は下記のようになります。

contains1: false
contains2: true

範囲に含まれる場合はtrue、含まれない場合はfalseが返却されます。

2つのNSDateIntervalについて重複部分が存在するかどうかをチェックする

関数func intersects(_ dateInterval: DateInterval) -> Boolを使用すれば、2つのNSDateIntervalについて重複部分が存在するかどうかをチェックすることができます。

// 2016年6月1日から6月30日までの範囲のDateIntervalを生成する
let calendar = Calendar(calendarIdentifier: .gregorian)

var firstDateStartDateComponents = DateComponents()
firstDateStartDateComponents.year = 2016
firstDateStartDateComponents.month = 6
firstDateStartDateComponents.day = 1

var firstDateEndDateComponents = DateComponents()
firstDateEndDateComponents.year = 2016
firstDateEndDateComponents.month = 7
firstDateEndDateComponents.day = 0

let firstDateStart = calendar?.date(from: firstDateStartDateComponents)
let firstDateEnd = calendar?.date(from: firstDateEndDateComponents)
let firstDateInterval = DateInterval(start: firstDateStart!, end: firstDateEnd!)

// 2016年6月20日から7月20日までの範囲のDateIntervalを生成する
var secondDateStartDateComponents = DateComponents()
secondDateStartDateComponents.year = 2016
secondDateStartDateComponents.month = 6
secondDateStartDateComponents.day = 20

var secondDateEndDateComponents = DateComponents()
secondDateEndDateComponents.year = 2016
secondDateEndDateComponents.month = 7
secondDateEndDateComponents.day = 20

let secondDateStart = calendar?.date(from: secondDateStartDateComponents)
let secondDateEnd = calendar?.date(from: secondDateEndDateComponents)
let secondDateInterval = DateInterval(start: secondDateStart!, end: secondDateEnd!)

let intersects1 = firstDateInterval.intersects(secondDateInterval)
print("intersects1: \(intersects1)")

// 2016年7月1日から7月20日までの範囲のDateIntervalを生成する
var thirdDateStartDateComponents = DateComponents()
thirdDateStartDateComponents.year = 2016
thirdDateStartDateComponents.month = 7
thirdDateStartDateComponents.day = 1

var thirdDateEndDateComponents = DateComponents()
thirdDateEndDateComponents.year = 2016
thirdDateEndDateComponents.month = 7
thirdDateEndDateComponents.day = 20

let thirdDateStart = calendar?.date(from: thirdDateStartDateComponents)
let thirdDateEnd = calendar?.date(from: thirdDateEndDateComponents)
let thirdDateInterval = DateInterval(start: thirdDateStart!, end: thirdDateEnd!)

let intersects2 = firstDateInterval.intersects(thirdDateInterval)
print("intersects2: \(intersects2)")

実行結果は下記のようになります。

intersects1: true
intersects2: false

2016年6月1日から6月30日までの範囲のDateIntervalに対して、
2016年6月20日から7月20日までの範囲のDateIntervalと、
2016年7月1日から7月20日までの範囲のDateIntervalそれぞれに重複部分が存在するかどうかをチェックしています。

2016年6月1日から6月30日までと2016年6月20日から7月20日までの範囲では6月20日から6月30日までの重複部分が存在するのでtrueが返却されます。 対して、2016年6月1日から6月30日までと2016年7月1日から7月20日までの範囲では重複部分が存在しないのでfalseが返却されます。

2つのNSDateIntervalについて重複部分のNSDateIntervalを取得する

上述の通り、関数func intersects(_ dateInterval: DateInterval) -> Boolを使用すれば、2つのNSDateIntervalについて重複部分が存在するかどうかをチェックすることができます。 さらにその重複部分をNSDateIntervalで取得する関数func intersection(with dateInterval: DateInterval) -> DateInterval?も用意されています。

let intersection1 = firstDateInterval.intersection(with: secondDateInterval)
print("intersection1: \(intersection1)")

let intersection2 = firstDateInterval.intersection(with: thirdDateInterval)
print("intersection2: \(intersection2)")

実行結果は下記のようになります。

intersection1: Optional((Start Date) 2016-06-19 15:00:00 +0000 + (Duration) 864000.0 seconds = (End Date) 2016-06-29 15:00:00 +0000)
intersection2: nil

ちゃんと想定した重複部分がDateIntervalで返ってきました。 重複部分が存在しない場合はnilが返却されます。

2つのNSDateIntervalを比較する

2つのNSDateIntervalを比較するには関数func compare(_ dateInterval: DateInterval) -> ComparisonResultを使用します。

let comparisonResult = firstDateInterval.compare(secondDateInterval)
switch comparisonResult {
case .orderedAscending:
    print("orderedAscending")
case .orderedDescending:
    print("orderedDescending")
case .orderedSame:
    print("orderedSame")
}

実行結果は下記のようになります。

orderedAscending

比較はまず最初に開始日時で行われ、仮に開始日時が同じ場合には日時間隔で比較が行われるようです。 比較結果に応じて以下のように列挙型ComparisonResultが返却されます。

  • orderedAscending
    • レシーバーの開始日時が引数のDateIntervalよりも前の場合、 もしくは開始日時が同じでレシーバーの日時間隔が引数のDateIntervalの日時間隔よりも短い場合
  • orderedDescending
    • レシーバーの開始日時が引数のDateIntervalよりも後の場合、 もしくは開始日時が同じでレシーバーの日時間隔が引数のDateIntervalの日時間隔よりも長い場合
  • orderedSame
    • 開始日時と日時間隔がレシーバーと引数のDateIntervalで一致する場合

まとめ

NSDateIntervalについてまとめてみました。
日時の範囲に特定の日付が含まれているかを確認するには、今まで2つの日時と対象の日時をそれぞれ比較することで行っていました。 また、NSDateIntervalのようなクラスを自作していた方もいるかと思います。
iOS 10で追加された機能の中でも比較的地味ですが、こういう地味だけどどんなアプリでも使う足支えとなるような機能も重要だと思い記事にしてみました。

参考資料

API Reference