この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
こんにちは。モバイルアプリサービス部の加藤 潤です。
今回はiOS 10で追加されたNSDateInterval
クラスを使って何ができるかを確認してみます。
本記事は Apple からベータ版として公開されているドキュメントを情報源としています。 そのため、正式版と異なる情報になる可能性があります。ご留意の上、お読みください。
NSDateIntervalとは
NSDateInterval
はiOS 10からFoundationフレームワークに追加されたクラスで、2つの日時の間隔を表現することが出来るクラスです。
以下のようなプロパティを持ちます。
- startDate (開始日時)
- endDate (終了日時)
- duration (上記の日時の間隔(秒))
- ちなみにSwiftにおいては
NSDateInterval
のブリッジとなるDateInterval
というstructureが用意されているのでそちらを使用します。
これらはちょうどNSString
とString
のような関係にあたります。
以下の実装では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で追加された機能の中でも比較的地味ですが、こういう地味だけどどんなアプリでも使う足支えとなるような機能も重要だと思い記事にしてみました。