Swiftの日付操作をDateComponentsとNSCalendarで比較してみる

エンジニアの渡辺です。 プロジェクトも一段落し、昔研修で作成したスケジュールアプリをObjective-Cから…

エンジニアの渡辺です。

プロジェクトも一段落し、昔研修で作成したスケジュールアプリをObjective-CからSwiftに書き直しています。
もう2年程前に作成したアプリなのでSwiftもかなり変化しており、色々と調べながら書き直していたのですが、
日付操作がNSCalendrで簡単にできるようになった様なので、
NSDateComponentsとNSCalendarの両方で書いて、書き方を比較してみようと思います。

■はじめに


今回のデータ出力はprintで出しますが、実際にアプリでデータを出力する時はStringで表示すると思うので、
以下のメソッドでStringに変換して出力します。

func dateFormatter(date: Date) -> String {
    let formatter = DateFormatter()
    formatter.dateFormat = "YYYY/MM/dd HH:mm"
    return formatter.string(from: date)
}

このメソッドを実行せずにそのままprint出力するとNSCalenderのTimeZoneの関係で、
思った通りの出力になりません。

ちなみに、作成したDateをそのまま出力する場合は

calendar?.timeZone = TimeZone.init(secondsFromGMT: 0)!

の1行を追加すれば想定通りの表示になります。
printの結果はこんな感じです。2017/09/01 10:30を指定してDateを作成しています。

--タイムゾーン未設定
NSCalendar: 2017-09-01 01:30:00 +0000
Stringに変換: 2017/09/01 10:30
--タイムゾーン設定
NSCalendar: 2017-09-01 10:30:00 +0000
Stringに変換: 2017/09/01 19:30

タイムゾーン設定後にStringに変換すると時間がずれるので注意して下さい。

■日付を指定してDateを作成


2017/09/01 10:30のDateを作成します。

// 日付を指定してDate(2017/09/01 10:30)を作成
// NSCalendar
let calDate_2017_09_01_1030 = calendar?.date(era: 1, year: 2017, month: 9,
                                             day: 1, hour: 10, minute: 30,
                                             second: 0, nanosecond: 0)

// DateComponents
var components_2017_09_01_1030 = DateComponents()
components_2017_09_01_1030.era = 1
components_2017_09_01_1030.year = 2017
components_2017_09_01_1030.month = 9
components_2017_09_01_1030.day = 1
components_2017_09_01_1030.hour = 10
components_2017_09_01_1030.minute = 30
let compDate_2017_09_01_1030 = calendar?.date(from: components_2017_09_01_1030)

出力結果は以下の様になります。

NSCalendar: 2017/09/01 10:30
DateComponents: 2017/09/01 10:30

この時指定しているeraとは紀元前、紀元後のことで、紀元前は0、紀元後は1になります。

以降、このDateを基準にコードを書いていきます。

■時間を変更する


最初に作成したdateの時間を12:00に変更します。

// 作成したDateの時間を変更する
// NSCalendar
let calDate_2017_09_01_1200 = calendar?.date(bySettingHour: 12, minute: 0,
                                             second: 0,
                                             of: calDate_2017_09_01_1030!,
                                             options: .matchLast)
// DateComponents
components_2017_09_01_1030.hour = 12
components_2017_09_01_1030.minute = 0
let compDate_2017_09_01_1200 = calendar?.date(from: components_2017_09_01_1030)

出力結果は以下の様になります。

NSCalendar: 2017/09/01 12:00
DateComponents: 2017/09/01 12:00

NSCalendarで指定しているoptionsについては以下Appleの公式ドキュメントに記載されています。
>> NSCalendarOptions
今回は.matchLast(一致する時刻が複数ある場合は、最後に発生した時刻を返す)を指定しています。

■日付を変更する


最初に作成したDateの日付を10日に変更します。

// 作成したDateの日付を変更する
// NSCalendar
let calDate_2017_09_10_1030 = calendar?.date(bySettingUnit: .day, value: 10,
                                             of: calDate_2017_09_01_1030!,
                                             options: .matchLast)

// DateComponents
components_2017_09_01_1030.day = 10
let compDate_2017_09_10_1200 = calendar?.date(from: components_2017_09_01_1030)

出力結果は以下の様になります。

NSCalendar: 2017/09/10 00:00
DateComponents: 2017/09/10 10:30

NSCalendarで時間までずれてしまいました。。
調べてみたところ、以下のサイトに記載されていました。
>> Swift 3 の日時操作チートシート
こちらのサイトの要素再設定の項目に記載されています。

■日付を加減算する


最初に作成したDateを10日前に変更します。

// 作成したDateの日付を加算する(10日前にする)
// NSCalendar
let calDate_2017_08_22_1030 = calendar?.date(byAdding: .day, value: -10,
                                             to: calDate_2017_09_01_1030!,
                                             options: .matchLast)

// DateComponents
components_2017_09_01_1030.day = components_2017_09_01_1030.day! - 10
let compDate_2017_08_22_1200 = calendar?.date(from: components_2017_09_01_1030)

出力結果は以下の様になります。

NSCalendar: 2017/08/22 10:30
DateComponents: 2017/08/22 10:30

■要素を取得する


最初に作成したDateの月を取得します。

// 要素を取得(月を取得)
// NSCalendar
let calMonth = calendar?.component(.month, from: calDate_2017_09_01_1030!)
// DateComponents
let compMonth = components_2017_09_01_1030.month

出力結果は以下の様になります。

NSCalendar: 9
DateComponents: 9

こうして比較してみると、NSCalendarの方がメソッド1つで操作できるので、
直感的に実装できそうだと思いました。
次回はNSCalendarに絞って、比較方法などについて書きたいと思います。