Home / ぼやきごと / 2015-02-21
2015-02-21

VC++2012の system_clock クラス to_time_t 関数は未来の時刻を返す場合がある

てっきり切り捨てだと思っていて、それを想定した作りにしていたらハマったという話。

次のコードを見てください。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 
 
 
 
-
-
!
|
-
!
|
-
!
|
-
!
|
-
!
|
|
!
#include <chrono>
#include <iostream>
#include <ctime>
 
int main(int, char**)
{
    // 現在時刻を time_t 値で取得
    const std::time_t t1 = std::time(nullptr);
 
    // システムクロックの time_point 値に変換
    auto tp = std::chrono::system_clock::from_time_t(t1);
 
    // 500ミリ秒を加算
    tp += std::chrono::milliseconds(500);
 
    // time_t 値に変換
    const std::time_t t2 = std::chrono::system_clock::to_time_t(tp);
 
    // t1, t2 を出力
    std::cout << t1 << '\n' << t2 << std::endl;
 
    return 0;
}

何をやっているかはコメントで記している通りです。
上記のコードを実行すると、 Visual C++ 2013 、 gcc 4.9.1 、 clang 3.5.0 では例えば次のように出力されます。

1424458932
1424458932

しかし Visual C++ 2012 では実行結果が異なり、例えば次のように出力されます。

1424459283
1424459284

500ミリ秒加算後の time_t 値が 1 増えています。

std::time_t 型の数値が何を表すかはコンパイラ依存ですが、VC++、gcc、clangといった主要コンパイラはEpoch*1からの経過秒数を表しています。
よって std::chrono::system_clock::to_time_t 関数では秒未満を何らかの方法で捨てることになります。

一番単純な実装としては秒未満切り捨てであり、VC++2013等の主要コンパイラはそうなっています。
前述のコードの 500 を 999 に変えても time_t 値は増加しません。

しかしVC++2012は最も近い秒数値に丸める実装になっています。
つまり秒未満が500ミリ秒以上であれば、変換元の time_point 値よりも未来の時刻を返してきます。
VC++2012でも、前述のコードの 500 を 499 に変えれば time_t 値は増加しなくなります。

関数の仕様としては「引数に渡した time_point 値と同等の time_t 値を返す」としか定義されていないはずなのでVC++2012の実装でも誤りではないと思いますが、注意する必要はあるでしょう。

Category: [C++][Visual Studio][プログラミング] - 2015-02-21 04:35:12

*1 1970年1月1日0時0分0秒