##はじめに
これも第14回 yidevでお話させていただいた小ネタです。
システム間で時刻のやりとりをするとき色々なやり方をすることがありますが、その一つに「unixtimeでやりとりする」ことがあります。
※unixtime とは 1970/1/1 00:00:00 からの秒数表現
unixtime を使う理由としては、文字列表現でやりとりすると日時の区切り記号やらロケールやらでパースが面倒になったりするとかまぁ色々です。
今回はサーバから取得した unixtime を Objective-C で NSDate に復元したときに数秒〜数十秒のズレが起きたことがあるので、その時の対処について忘備録も兼ねてエントリを投稿してみました。
##NSDateへの復元(間違い編)
早速 unixtime で 1388477900 という値 (日時表記は 2013-12-31 08:18:20 +0000)を NSDate に復元してみます。
// サーバからの返り値をエミュレート
NSDictionary *dataDictionary = @{@"unixtime" : @"1388477900"};
NSString *time = [dataDictionary objectForKey:@"unixtime"];
NSLog(@"NSDate:%@", [NSDate dateWithTimeIntervalSince1970:[time floatValue]]);
すると出力はこのようになります。
2013-12-31 17:24:05.197 TimeIntervalSample[16040:70b] NSDate:2013-12-31 08:19:12 +0000
どうでしょう。
本当は 2013-12-31 08:18:20 +0000 という結果を期待したはずが 2013-12-31 08:19:12 +0000 という結果になりました。
##NSDateへの復元(正解編)
感の良い方ならばもうお気づきでしょうが(むしろツッコミ入れたい気分でいっぱいだと思いますが)、floatValueを使っているのが問題でした。
- (instancetype)dateWithTimeIntervalSince1970:(NSTimeInterval)secs;
もともとのメソッドシグニチャを見て NSTimeInterval だから浮動小数点型にしなきゃね!と思ってやった変換がいけなかったわけですが、 unixtime の意味合いを考えて intValue を使ったらうまくいきました。
ちなみに doubleValue を使ってもうまくいきます。
NSDictionary *dataDictionary = @{@"unixtime" : @"1388477900"};
NSString *time = [dataDictionary objectForKey:@"unixtime"];
NSLog(@"double NSDate:%@", [NSDate dateWithTimeIntervalSince1970:[time doubleValue]]);
NSLog(@"float NSDate:%@", [NSDate dateWithTimeIntervalSince1970:[time floatValue]]);
NSLog(@"int NSDate:%@", [NSDate dateWithTimeIntervalSince1970:[time intValue]]);
出力結果
2013-12-31 17:24:05.197 TimeIntervalSample[16040:70b] double NSDate:2013-12-31 08:18:20 +0000
2013-12-31 17:24:05.197 TimeIntervalSample[16040:70b] float NSDate:2013-12-31 08:19:12 +0000
2013-12-31 17:24:05.198 TimeIntervalSample[16040:70b] int NSDate:2013-12-31 08:18:20 +0000
この結果を見てかなりゾワッときたのですが、どうも文字列における数値変換で想像以上に変なことが起きている様子。
なのでこんなコードも実行してみました。
NSDictionary *dataDictionary = @{@"unixtime" : @"1388477900"};
NSString *time = [dataDictionary objectForKey:@"unixtime"];
NSLog(@"double value:%lf", [time doubleValue]);
NSLog(@"float value:%f", [time floatValue]);
NSLog(@"int value:%d", [time intValue]);
出力結果
2013-12-31 17:24:05.197 TimeIntervalSample[16040:70b] double value:1388477900.000000
2013-12-31 17:24:05.198 TimeIntervalSample[16040:70b] float value:1388477952.000000
2013-12-31 17:24:05.198 TimeIntervalSample[16040:70b] int value:1388477900
float だけズレとる。。。
かなり gkbr する結果ですね。52もズレております。
floatValue、恐ろしい子!
##終わりに
今回の例でいえば floatValue だけがおかしかったわけですが、小数点の演算精度問題にかかわるこういった話は昔からあるわけでちゃんと使うときに気をつけないと痛い目を見るよ!という良い教訓になりました。
ショウスウテンコワイデス。