はじめに
日付操作とか比較とかしようとか思ってググると、NSDateComponents
を使ったやつがよく出てきます。一旦NSDateComponents
を作ってから一つの要素ごとに足し算したりとか色々めんどいなーと思ってリファレンス見てたら、日付操作とか日付比較で便利なメソッドがNSCalendr
に追加されていることに気づきました!
introduced=8.0
って書いてあるからiOS8からなのかなと思ったんですが、どうなんでしょう。
とりあえずiOS8からっぽいやつを試してみました。
iOS8からっぽいNSCalendarのAPI
日付生成
全体を通してですが、NSCalendar
は西暦を使用します。currentCalendar()
使っちゃうと和暦が設定されてると困るみたいです。
let calendar = NSCalendar(identifier: NSCalendarIdentifierGregorian)!
時刻を指定してNSDateの生成
例えば2014年12月10日20時26分を生成するには、dateWithEra
を使います。
let date2014_12_10_2026 = calendar.dateWithEra(1, year: 2014, month: 12, day: 10, hour: 20, minute: 26, second: 0, nanosecond: 0)!
// => 2014/12/10 20:26:00
dateWithEra
のeraってなんだろうと思ったんですが、紀元前(BC)、紀元後(AD)のことだそうです。紀元前は、0
、紀元後は1
なので、1
を指定しています。
ちなみに、和暦の場合はここが、なんか色んな値になるっぽいコメントがありました。
+1 this is important because (for example) the Japanese calendar uses the Era to denote the reign of a single emperor. So while the Gregorian calendar only had had 2 eras do far, the Japanese calendar has had dozens.
時刻だけを変更したものを生成
NSDate
オブジェクトの時刻だけを変えたいってときは、dateBySettingHour
を使います。結構使う頻度高そう。例えばさっきのやつの時刻だけを変えるにはこうする。
let date2014_12_10_0830 = calendar.dateBySettingHour(8, minute: 30, second: 0, ofDate: date2014_12_10_2026, options: nil)!
// => 2014/12/10 08:30:00
日付を加減算したものを生成
dateByAddingUnit
で年、月、日、時、分、秒を指定して加減算できます。
例えばさっき生成した日付の10日前はこんな感じ。
let date2014_11_30_0830 = calendar.dateByAddingUnit(.DayCalendarUnit, value: -10, toDate: date2014_12_10_0830, options: nil)!
//=> 2014/11/30 08:30:00
分だけを足し算するにはこう。.MinuteCalendarUnit
を指定する。
let date2014_11_30_0845 = calendar.dateByAddingUnit(.MinuteCalendarUnit, value: 15, ofDate: date2014_11_30_0830, options: nil)!
// => 2014/11/30 08:45:00
わざわざNSDateComponents
を作って足し算引き算しなくてすむようになったので便利ですね。
日付要素の取得
component
ってメソッドがあるので好きな要素だけをとれます。
例えば、時
だけが欲しいときは、こうやります。
let hour = calendar.component(.HourCalendarUnit, fromDate: date2014_11_30_0845)
// => 8
各要素をまとめてとりたい時用のAPIもあるんですが、参照渡しで値を取得するってやつで、なんか使い方が直感的じゃない気がする。(とりあえずタプルでとってみたけどタプルじゃなくてもできます。)
// 年月日の各要素をとる場合
var comps = (0, 0, 0, 0)
calendar.getEra(&comps.0, year: &comps.1, month: &comps.2, day: &comps.3, fromDate: date2014_11_30_0845)
// comps => (1, 2014, 11, 30)
// 時分秒の各要素をとる場合
var comps = (0, 0, 0, 0)
calendar.getHour(&comps.0, minute: &comps.1, second: &comps.2, nanosecond: &comps.3, fromDate: date2014_11_30_0845)
// comps => (8, 45, 0, 0)
日付の比較
同じ日付かどうか
isDate
が比較用のAPIなんですが、日付が同じかどうかと一口で言っても、同じ年か、同じ月かという個別に判断したい場合もありますよね。そういうときにも、細かく指定できるようになってます。
例えば、2014/12/10 8:30
と、2014/11/30 8:30
が年が一緒か、月が一緒かチェックしたい場合はこう書きます。これは結構使いやすいんじゃないかなー。
// 年までを比較する場合は、.YearCalendarUnitを指定する
calendar.isDate(date2014_12_10_0830, equalToDate: date2014_11_30_0830, toUnitGranularity: .YearCalendarUnit)
// => true
// 年月までを比較する場合は、.MonthCalendarUnitを指定する
calendar.isDate(date2014_12_10_0830, equalToDate: date2014_11_30_0830, toUnitGranularity: .MonthCalendarUnit)
// => false
そもそも、日にちだけならもっと簡単なAPIisDate(date1: NSDate, inSameDayAsDate date2: NSDate)
があります。
calendar.isDate(date2014_12_10_0830, inSameDayAsDate: date2014_12_10_2026)
// => 時刻は違うけどどちらも2014/12/10なのでtrue
calendar.isDate(date2014_12_10_2026, inSameDayAsDate: date2014_11_30_0830)
// => 2014/12/10と2014/11/30は違う日なのでfalse
調べたい日付が、今日なのか、明日なのか、週末なのかも用意されてます。この辺はカレンダーとか作る場合に使うかもしれないですね。
// 例えば今日が12月10日ならtrue
calendar.isDateInToday(date2014_12_10_0830)
// 例えば今日が12月9日ならtrue
calendar.isDateInTomorrow(date2014_12_10_0830)
// 11月30日は日曜日なのでtrue
calendar.isDateInWeekend(date2014_11_30_0845)
日付の前後/大小の比較
これは、beforeとかafterとかって名前がついたAPIがあったらいいなというところでしたが、残念ながらいつものNSComparisonResult
が返ってくるやつしかないっぽいです。
でもどこまで比較するかってのを指定できるようになってて、例えばこんなことができます。
2014/12/10 8:30
と2014/12/10 20:26
を日にちまでの部分だけで比較するためには、.DayCalendarUnit
を指定します。日にちまでは同じなので比較する順番を入れ替えても.OrderedSame
が返ってきます。
calendar.compareDate(date2014_12_10_0830, toDate: date2014_12_10_2026, toUnitGranularity: .DayCalendarUnit)
// => `2014/12/10 8:30`と`2014/12/10 20:26` を比較しても .OrderedSame
calendar.compareDate(date2014_12_10_2026, toDate: date2014_12_10_0830, toUnitGranularity: .DayCalendarUnit)
// => `2014/12/10 20:26` と`2014/12/10 8:30`で比較しても .OrderedSame
そして、MinuteCalendarUnit
を指定すれば分のところまでで比較されるので、どっちが先か後かが返ってきます。
calendar.compareDate(date2014_12_10_0830, toDate: date2014_12_10_2026, toUnitGranularity: .MinuteCalendarUnit)
// => `2014/12/10 8:30` < `2014/12/10 20:26` なので .OrderedAscending
calendar.compareDate(date2014_12_10_2026, toDate: date2014_12_10_0830, toUnitGranularity: .MinuteCalendarUnit)
// => `2014/12/10 20:26` > `2014/12/10 8:30` なので .OrderedDescending
おしゃれなメソッドはないけど、比較したい要素を指定できるので使い勝手はよくなってると思います。
謎のAPI
APIのドキュメントも読んだんですがなんとなく使い道がイメージできなかったやつたちです。
使い方も理解するのに時間がかかった。
その1: nextDateAfterDate
次のマッチする日付を探索します。
例えば、次の13日はいつ?というのを調べます。
calendar.nextDateAfterDate(NSDate(), matchingUnit: .DayCalendarUnit, value: 13, options: NSCalendarOptions.MatchNextTime)
// 今日が12/11だとしたら、12/13がかえってきます。
// 今日が12/14だとしたら、1/13がかえってきます。
うーん何かに使えるのかな。あと、NSCalendarOptions
の詳しい説明がリファレンスに書いてなくて各値が何を表すのかが分からないです。。カレンダーを探索するときに使えそうな雰囲気ではあるんだが。
その2: rangeOfWeekendStartDate
そして、一番謎だったAPIがこれ。
=> rangeOfWeekendStartDate(datep: AutoreleasingUnsafeMutablePointer<NSDate?>, interval tip: UnsafeMutablePointer<NSTimeInterval>, containingDate date: NSDate)
使い方と使い道が分からないんだけど、なんとか使い方は理解できました。この参照渡し系のやつってなんとかならんのか。
要は、containingDate
が週末かどうかをチェックしてくれるんだけど、参照渡しで、datep
とinterval
を渡しておくと週末の始まりの日付と週末の期間の長さをセットしてくれるというAPI。
// 参照渡し用に変数を宣言しておく
var startDate : NSDate? = nil
var interval = NSTimeInterval()
// 週末かどうかを判定したい日付
let containingDate = calendar.dateWithEra(1, year: 2014, month: 12, day: 21, hour: 1, minute: 1, second: 3, nanosecond: 1)!
calendar.rangeOfWeekendStartDate(&startDate, interval: &interval, containingDate: containingDate)
// => 2014/12/21は日曜なのでtrue
startDate
// => この週の週末は土曜からだから2014/12/20がセットされている
interval
// => この週の週末は2日間だからintervalには、`60*60*24*2=172800`がセットされている
うーん、何に使うんだろうか・・・きっとスケジュールアプリとか作ってる人は使い道がぱっと思いつくんだろうな。
まとめ
NSDateの操作は他の言語に比べて直感的な操作ができるものが長らく用意されてなかったけど、iOS8ぐらいから少しずつ手を入れていくつもりなんでしょうか。
Javaだってずっと辛い感じだったけど8になってようやく使いやすいAPIが出てきたぐらいだし、なんかそんなもんなんでしょうか。
あと、日付生成系はほとんどNSDate?
を返すので今回は説明用に!
でアンラップしてますが、本当はチェックしてから使用する必要があるあたりはちょっと面倒かもしれません。