iOSでの時間を制する(NSDate, NSDateComponents)

もうひとつのプロフェッショナルプログラマーより転載です。

iOSで時間を利用する方法です。iOSでは, NSDate, NSDateComponentなどを使います。

アプリを開発する際に, 注意しなくてはならないのが, タイムゾーンと時間です。
APIがどこの時間を取得したかが問題になります。またユーザに対してどの時間を表示させるべきかも問題になります。

iOSで時間を扱う場合の基本方針

  1. Core Data に保存する値は, 常に GMT 時間を保存
  2. 表示するデータ(UIDatePickerも)は, 現地時間
  3. 保存したデータの検索は, GMT時間
  4. GMT, 現地時間との相互変換が必要

ユーザには, 現地時間, ソフトウェア内部ではGMT時間で操作する方法がよさそうです。

Sampleなど

時間を扱うクラスは, NSDate, NSDateComponentなんかがよさそうです。

ローカル時間(現在時間)を取得する

NSDate *now = [NSDate date];

NSDate を何も考えずに利用すると, ローカル時間が得られます。 NSLogなんかで

NSLog(@"Current Time %@", [NSDate date]);

と表示させてみるとGMTになります。

タイムゾーンの取得

NSDate *date = [NSDate date];
NSTimeZone *tz = [NSTimeZone systemTimeZone];
NSInteger seconds = [tz secondsFromGMTForDate: date];
NSDate *date = [NSDate date];
NSDateComponents *comps;
NSCalendar *calendar = [NSCalendar currentCalendar];
comps = [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:date];
NSDateComponents *new_comps = [[NSDateComponents alloc] init];
[new_comps setYear:comps.year];
[new_comps setMonth:comps.month];
[new_comps setDay:comps.day];
[new_comps setHour:23];
[new_comps setMinute:59];
[new_comps setSecond:59];
NSDate *tmp = [calendar dateFromComponents:new_comps];
NSTimeZone *tz = [NSTimeZone systemTimeZone];
NSInteger seconds = [tz secondsFromGMTForDate:tmp];
NSDate *local_date = [tmp dateByAddingTimeInterval:seconds];

※応用編です。
NSCalendar を利用しています NSCalendar, NSDateCompoenents で得られたcomponentは
, 現地時間になります。(currentCalendar は現地時間を表します)
[NSDate date] で, GMTを得たはずなのに, currentCalendar を通すと, 現地時間に変わってしまいます。
さらに, NSDateComponents を使って, 時間の操作をしました。またカレンダーを使って, 時間を得ていますが, この時間はGMTに戻っています。そこで再び現地時間に戻します。

UIDatePicker の場合

UIDatePicker は, タイムゾーンをstoryboard 上もしくは, コードで設定します
コードで設定(datePicker は, UIdatePicker オブジェクトのインスタンス or リファレンス)

datePicker.timezone = [NSTimeZone systemTimeZone];

これで現地時間になります。
このUIDatePicker で得られた値を再度, GMT時間に戻すには, 以下のコードを押し込みます

NSDate *local_date = [datePicker date];
NSTimeZone *tz = [NSTimeZone systemTimeZone];
NSInteger seconds = [tz secondsFromGMTForDate: local_date];
[local_date dateByAddingTimeInterval:-(seconds)];

※これは現地時間を取得したときと逆の計算をしています。
GMT時間で保存しておけば, のちにi端末のタイムゾーンを変更しても, そのタイムゾーンに合わせた時間によしなに変更してくれるということです。
グローバルアプリの場合は, 多少気を付ける必要がありそうです。