iPhoneで和暦表示設定のとき、日付や曜日がずれる

GoogleCalendarの予定をiPhoneに表示するアプリを作成中、
実機で動かすと、2012年4月1日の曜日が1日ずれていることを発見。

4000年4月1日(土) ←2012年4月1日は日曜日…

ん?4000年??

年まで変わってる!

iPhoneの設定で、カレンダーの設定を和暦にしているとこうなる。
西暦に変更すればちゃんと「2012年4月1日(日)」と表示されるのだが。

よく分からないので、Googleで調べてみると、以下のページを発見。

カレンダー設定を和暦にすると2011年が西暦3999年と解釈されてしまう - 酢ろぐ!

まさに同じ状態。文字列からNSDate型に変換するとき、逆にNSDate型から文字列に変換するときには「NSGregorianCalendar」を使う必要があるようだ。
他のブログには、NSJapaneseCalendarの記事もあった。
カレンダーにもいろいろと種類があるんだな。*1

文字列からNSDate型へ変換

文字列を切り取って、NSDateComponentsにセットし、
そこからNSDate型変数を作成する。

    NSDateComponents *comps = [[NSDateComponents alloc] init];
    NSDate *date;
    
    @try {
        [comps setYear: 2012];    // Year
        [comps setMonth:   4];    // Month
        [comps setDay:     1];    // Day
        [comps setHour:   15];    // Hour
        [comps setMinute:  0];    // Minute
        [comps setSecond:  0];    // Second
    }
    @catch (NSException *exception) {
        NSLog(@"error was cought at NSDateComponents set values");
    }
    NSCalendar *calendar = [NSCalendar currentCalendar];
    NSCalendar *gregorianCalendar = [[NSCalendar alloc]initWithCalendarIdentifier:NSGregorianCalendar];
    NSCalendar *japaneseCalendar = [[NSCalendar alloc]initWithCalendarIdentifier:NSJapaneseCalendar];
    
    date = [calendar dateFromComponents:comps];    // currentCalendarで実行
    NSLog(@"calendar date[%@]", date);
    
    date = [gregorianCalendar dateFromComponents:comps];    // NSGregorianCalendarで実行
    NSLog(@"gregorianCalendar date[%@]", date);
    
    date = [japaneseCalendar dateFromComponents:comps];    // NSJapaneseCalendarで実行
    NSLog(@"japaneseCalendar date[%@]", date);

実行結果をまとめるとこんな感じ。

西暦設定 和暦設定
currentCalendar 2012-04-01 06:00:00 +0000 (西暦2012年) 2012-04-01 06:00:00 +0000 (平成2012年)
NSGregorianCalendar 2012-04-01 06:00:00 +0000 (西暦2012年) 0024-04-01 06:00:00 +0000 (平成24年)
NSJapaneseCalendar 4000-04-01 06:00:00 +0000 (西暦4000年) 2012-04-01 06:00:00 +0000 (平成2012年)

※()内は追記した

数字だけを見ていれば「currentCalendar」で問題なさそうだが、
システムの表示形式が変わっているため、「currentCalendar+和暦設定」では平成2012年になるのだ Σ(゚д゚lll)

ちなみによく見ると、時間が9時間ずれている。
Timezoneが「+0000」になっているため、合っているのだがわかりにくいな(´・ω・`)

NSJapaneseCalendarでは、NSDateを作るときに「2012年」を渡しているが、
これが「平成2012年」と解釈されるため、西暦にすると「西暦4000年」になっている。

つまり、NSJapaneseCalendarを使うときは、年は和暦にして作業しなければならないということ。

もろもろ考えると、日付は西暦のGMT(標準時)で作業することに統一して、
NSGregorianCalendarを使うことにしたほうがよさそう。

NSDate型から文字列を作成

今度は逆にNSDate型から日付文字列を作るときの話。

    NSDateFormatter *format;
    NSString *str;
    
    NSDate *date = [NSDate date];  // 現在時間で作成
    format = [[NSDateFormatter alloc]init];
    [format setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"ja_JP"]];
    
    [format setDateFormat:@"yyyy年MM月dd日(EE) HH時mm分ss秒"];  // 時間文字列のフォーマット定義
    
    NSCalendar *calendar = [NSCalendar currentCalendar];
    NSCalendar *gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    NSCalendar *japaneseCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSJapaneseCalendar];
    
    [format setCalendar:calendar];    // currentCalendarで実行
    str = [format stringFromDate:date];
    NSLog(@"currentCalendar date[%@]", str);
    
    [format setCalendar:gregorianCalendar];    // NSGregorianCalendarで実行
    str = [format stringFromDate:date];
    NSLog(@"NSGregorianCalendar date[%@]", str);
    
    [format setCalendar:japaneseCalendar];    // NSJapaneseCalendarで実行
    str = [format stringFromDate:date];
    NSLog(@"japaneseCalendar date[%@]", str);

実行結果はこんな感じ。

西暦設定 和暦設定
currentCalendar 2012年03月31日(土) 20時32分27秒 0024年03月31日(土) 20時33分58秒
NSGregorianCalendar 2012年03月31日(土) 20時32分27秒 2012年03月31日(土) 20時33分58秒
NSJapaneseCalendar 0024年03月31日(土) 20時32分27秒 0024年03月31日(土) 20時33分58秒

西暦表示したいときは「NSGregorianCalendar」を使い、
和暦表示したいときは「NSJapaneseCalendar」を使えばいいようだ。

「currentCalendar」はカレンダーの設定が西暦の時には「NSGregorianCalendar」
和暦の時には「NSJapaneseCalendar」を使っているような動きをしているけど、
アプリが西暦のつもりでユーザが和暦設定にすると、変な値(西暦4000年など)になるから使わない方がいい。

欧米なんかはカレンダーの設定を変えようがないだろうから、currentCalendarでも問題は起きないだろうけど、
日本や中国など、独自の暦を使っている環境だと、ユーザ設定によってこんな問題が起きるんだなと。

LocaleやTimezoneは付属的なものだからいいとしても、
日付処理はなかなかやっかいだな。

*1:詳しく知りたいときは「NSLocale.h」を参照