まいまいワークス

主にiPhoneアプリの開発で考えた事、調べた事、感じた事などを記していきます。

NSDateの罠

自社サービスでも複数のサービスで数回NSDateの罠にはまっているので、あらためて要点をまとめたいと思います。

NSDateが登場する場面

・端末から現在の時刻を取得
・UIDatePickerから任意の時刻を取得
・サーバーからyyyy-MM-dd HH:mm:ssなどの形式で取得

過去に問題が起こったケース

1.日付の書式が和暦など西暦以外に設定されている
2.24時間表示をOFFにしてAM/PM表記になっている
3.NSDateが標準時(GMT)を返すことに気づかず9時間の時差を生んでしまう

1.のケースは例えば、西暦で取得する前提で設計しているにも関わらず平成の数字が返ってくるので西暦26年3月12日といった扱いになってしまうケース

2.のケースはyyyy-MM-dd HH:mm:ssで値を取得するところに、2014-03-12 午前03:34:56 +0000というフォーマットで値が返ってくるので値が取れなくなるケース

3.は設定した時刻にコンテンツが表示されず、9時間後に表示されるといったケース

どんな環境でも同じフォーマットで値を取得したい

サーバーから値を取得して現在の時刻と比較するケース

    //例えば、この値をサーバーから取得したとする
    NSString* timeStr = @"2014-03-12 12:34:56";
    
    //フォーマットの定義
    NSDateFormatter* format = [NSDateFormatter new];
    [format setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
    [format setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];

    //フォーマットの適用
    NSDate* date = [format dateFromString:timeStr];
    
    //NSDateは標準時間&端末に設定されたフォーマットで取得
    NSLog(@"GMT=%@",date);
    

    //ローカル時間に補正してNSString出力
    //(サーバーから取得した値と同じなので実用上は意味がありませんが、フォーマット適用したNSDateからNSstringへの変換の確認)
    NSString* str = [format stringFromDate:date];
    NSLog(@"localTime=%@",str);

    
    //現在時刻からの差を計算
    //マイナスだと過去の日付、プラスで未来
    NSTimeInterval interval = [date timeIntervalSinceNow];
    NSLog(@"interval=%f",interval);

    //これで端末の状態に関わらずtimeIntervalが取得できるはず

端末でNSDateを取得するケース

上記とほとんど一緒です。

  
    //UIDatePickerで値を取得
    //picker部分の記述は割愛
    NSDateFormatter* format = [NSDateFormatter new];
    [format setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
    [format setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];

    NSString* dateStr = [format stringFromDate:[picker date]];
    NSLog(@"dateStr=%@",dateStr);

 

その他のケース 

これで、ほとんど問題なく時刻が扱えると思うのですが、世界中で一斉にキャンペーンを行いたいとか、時差が出ると困る場合は標準時でかつ端末のフォーマットに左右されずに値を取得したい場合

    NSDate* date = [NSDate date];

    //標準時との秒差を取得
    float diff = [[NSTimeZone localTimeZone] secondsFromGMT];
    NSLog(@"sec=%f",diff);
    
    
    //統一フォーマットで標準時を取得したい(キャンペーンの開始など...)
    NSDateFormatter* format = [NSDateFormatter new];
    [format setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
    [format setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
    
    NSString* NSdateStr = [format stringFromDate:[NSDate dateWithTimeIntervalSinceNow:-diff]];
    NSLog(@"date=%@",NSdateStr);

結論

これが重要!!!
[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]]

3番目はもうちょっとスマートなやり方ないかなー