Hatena::Groupiphone-dev

iOS プログラミングメモ

2013-09-02 (Mon)

西暦ではなく和暦を使用してる場合にNSDateComponentsのyearが返す値の衝撃 14:44  西暦ではなく和暦を使用してる場合にNSDateComponentsのyearが返す値の衝撃 - iOS プログラミングメモ を含むブックマーク

和暦*1とか使ってないから気付かなかったけど、和暦環境限定の不具合を出してしまいました。

拙作の 記念日リマインダー というアプリでは、特定の日をベースにして1年毎のNSDateを取得して諸々やってる部分があります。日付を計算するのにNSDateComponentsを使うと便利でして、その辺りのコードが以下のような感じになっています。

NSCalendar *calendar = [NSCalendar currentCalendar];

NSDateComponents *components =
   [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:baseDate];

for(int i = 1; i <= 80; i++) {
   components.year ++;
   anniversaryDate = [calendar dateFromComponents:components];

baseDateから80年分の日付オブジェクトを生成してるんですね。NSYearCalendarUnit,NSMonthCalendarUnit,NSDayCalendarUnitの3つを指定してるので、年月日の年の部分だけを変更したモノが得られます。「時分秒」は指定してないので、baseDateの時刻がいくつであってもdateFromComponentsが返すNSDateは00時00分00秒になります。

西暦の場合はこれで何の問題もないのですが、和暦を使ってる場合は元号の値も固定しないと意図した通りに動かない可能性があります。たとえばbaseDateを昭和54年にした場合でもdateFromComponentsが返す時は元号が平成になるようなので、year++すると平成55年にジャンプします。これを解決するには、NSDateComponentsにNSEraCalendarUnitを追加しておくだけです。

NSDateComponents *components =
   [calendar components:NSEraCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:baseDate];

これで元号もbaseDateのものが使われるようになるので、晴れて問題は解決となりました。前々からeraってなんだろうなと思ってたんですけど、元号のことだったんですね。まったく、わかってみればなんてことはないけど、ぜんっぜん気付きませんでした。

追記

岸川さんが教えてくれたんですが、NSCalendarにlocaleを指定すれば西暦のままで計算できるようです。

最初の例を直すと、以下のような形でうまく動くようになりました。

NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
calendar.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];

NSDateComponents *components =
   [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:baseDate];

for(int i = 1; i <= 80; i++) {
   components.year ++;
   anniversaryDate = [calendar dateFromComponents:components];

components.year = 1900 みたい値を直接代入したい場合は元号がからむと厄介なので、このやり方はいいですね。

*1:設定アプリの「一般>言語環境>カレンダー」で変更可

トラックバック - http://iphone-dev.g.hatena.ne.jp/ktakayama/20130902