2012年11月17日土曜日

iOSの日付処理まとめ

日付処理をするとき、どーだったっけ.. と迷うので私なりにまとめ。

日付関連の要素


NSCalendar

[NSCalendar currentCalendar]
[NSCalendar autoupdatingCurrentCalendar]

OSの設定 > 一般 > 言語環境 > カレンダー によって変わる。
「和暦」になっていると、NSJapaneseCalendar
「西暦」になっていると、NSGregorianCalendar 

Calendarには、後述の TimeZoneやLocale を保持しています。
autoupdatingCurrentCalendar は、ユーザが変更した内容を自動的に反映してくれるカレンダー。
クラス変数としてカレンダーを保持する場合などはこちらを使用するとよいかも。
だけど、保持せず使うタイミングで取得するのであれば、[NSCalendar currentCalendar] のままでよさそう。

NSDateFormatterで "yyyy-MM-dd" のようなフォーマットを使用している場合には、OSのカレンダー設定で和暦の設定されている場合に注意。

常に西暦で処理したい場合には、
[[NSCalendar alloc]initWithCalendarIdentifier:NSGregorianCalendar]
NGregorianCalendarを作成して、NSDateFormatterなどに設定して使用します。

NSTimeZone

[NSTimeZone defaultTimeZone]
[NSTimeZone systemTimeZone]
[NSTimeZone localTimeZone]

OSの設定 > 一般 > 日付と時刻 > 時間帯 によって変わる。

時差を管理するもの。これは分かりやすい。
日本だったら、GMT +0900 / JST
NSDateFormatter などを init すると、OSの設定が入ります。

基本、あまり入れ替えることはないはず。あるとしたら、各国での時間を表示するときぐらいかな?
アプリ内部で日時情報を保持する場合には、
[NSDate timeIntervalSince1970] のGMT +0000 での経過時間で管理して、表示時にOS設定のCalendar, TimeZoneを使って 年月時間を表記 というのが素直な方法。

TimeZoneにはDaylightSavingTime(サマータイム)の情報も含みます。

NSLocale

[NSLocale currentLocale]
[NSLocale systemLocale]
[NSLocale autoUpdatingCurrentLocale]

OSの設定 > 一般 > 言語環境 > 書式 
OSの設定 > 一般 > 日付と時刻 > 24時間表示
などによって変わる。

国毎に変わるもの(フォーマットなど)を管理。
日本 : 「11月」 アメリカ:「November」
日本 : 「午前」  アメリカ : 「AM」
とかお金の単位とか文字が変わるのもあるし、文章の方向とか、カンマ と ピリオドの扱いとか国によって違うものが管理されています。

全世界で使われるようなアプリでは、それらの差を自分で書かないといけない... ところをLocaleを設定してFormatterに処理を任せるようにすれば対応できてしまう、本当はありがたいもの。ハマる所でもありますが。

逆に、ここは常に英語の表記したいのになーー  という場合には
[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]
と自分でLocale作成して設定もできます。

cunnretLocaleはユーザがOSの設定でしている情報。
systemLocaleは端末でフォルトの情報。

NSDate

GMTの経過時間(NSTimerInterval)を保持している。
Calendarなどの概念が含んでいないのでtimezoneが違っても同じものとして扱える。
内部で保持するデータモデル的クラスです。
UI上に表示する場合には、NSDate と、上記Calendarなどを組み合わせて日時をフォーマットして表示する感じ。


日付を扱うクラス


NSDateFormatter

@"yyyy-MM-dd HH:mm:ss" でフォーマットを指定しているのに、

「0024-11-16 21:09:17」になっちゃった!
(カレンダーに和暦が設定されているとき)

「2012-11-17 午後11:07:47」 になっちゃった!
(24時間表示の設定がOFFのとき)

とういのが、あるある 事件。
カレンダーについてはNSDateFormatterにNSGregorianCalendarを設定して、
24表示については、Localeに[NSLocale systemLocale]をすると上手くいきます。

systemLocaleにすると24時間表示になる理由はわからず。。
どのパラメータが影響しているのだろうかー。

OSで設定されている日付フォーマットに沿って文字列を生成するには
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateStyle: NSDateFormatterShortStyle];
[formatter setTimeStyle:NSDateFormatterShortStyle];
NSString *dateStr = [formatter stringFromDate:date];
のようにします。
日付部分のフォーマット、時間部分のフォーマット を CFDateFormatterStyle の定数で指定します。CFDateFormatterNoStyle を指定すると表示なしになるので時間だけを表示ということも可能。

NSDateComponent

NSCalendar と NSDate から、年、月、日、時間 などの取得や、日付計算ができる。

NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponent *comp = [calendar components:params fromDate:[NSDate date]];

本日の00:00:00のNSDateを取得
NSDateComponent *comp = [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:[NSDate date]];
NSDate *date = [calendar dateFromComponents:comp];

1日後のNSDateを取得
NSDateComponent *comp = [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:[NSDate date]];
comp.day += 1;
NSDate *date = [calendar dateFromComponents:comp];

2つのNSDateの日数を取得
NSDate *fromDate;
NSDate *toDate;
NSDateComponent *comp = [calendar components:NSDayCalendarUnit fromDate:fromDate toDate:toDate options:0];
NSInteger between = comp.day;

曜日を取得
NSDateComponent *comp = [calendar components:NSWeekdayCalendarUnit fromDate:[NSDate date]];
NSInteger weekday = comp.weekday;

曜日の文字列は、NSDateFormatter の weekdaySymbols から取得できるけど、曜日の並び順番がズレているので注意。

NSDatePicker

これに先日ハマっていた...

DatePickerにGrigorianCalendarを設定する

NSCalendar *gregorian = [[NSCalendar alloc]initWithCalendarIdentifier:NSGregorianCalendar];
datePicker.calendar = gregorian;

iOSの設定で、24表示無し(AM PM表示) にしているとき
iOS5(iPhone4S)の端末では、AM PM表示になるのに、
iOS6(iPhone5)の端末では、24時間表示になってしまう。

NSDateFormatter では Locale を SystemLocaleにすると24時間表示にできたが、
DatePickerのLocale、設定するCalendarのLocale ともにSystemLocaleにしても、24時間表示にはならない。

端末設定に依存せず、常に24時間表示にする方法はわからず。

NSCalendar *gregorian = [[NSCalendar alloc]initWithCalendarIdentifier:NSGregorianCalendar];
gregorian.locale = [NSLocale currentLocale];
datePicker.calendar = gregorian;

...と 自作したGregorianカレンダーのLocaleにCurrentLocaleを設定すると、iOS5,6ともに設定された24時間表示どおりになるようです。

• • •