
2025/12/26 13:57
**タイトル:** C++における時間:クロック間変換、エポック、期間 --- *本書では、モダン C++ で時間を扱うための主要概念と実践的テクニックについてまとめています。クロック種別、エポック計算、期間演算、および異なる時間表現間の変換方法を網羅しています。*
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
現在の要約は主要なポイントを正確に反映しているため、そのまま繰り返すことができます。
本文
これまでのシリーズで
ここまで、主要な標準クロックとその振る舞いについて見てきました。壁時計時間(wall‑clock time)、単調増分クロック(monotonic clocks)、そして「高解像度」の神話について語ってきました。本日はもう少し微妙な領域に踏み込みます:クロック同士の関係、エポック(epoch)の違い、そして 変換が必要になったとき に何が起こるかです。
一見シンプルそうに聞こえるでしょう。タイムスタンプはタイムスタンプで、期間は単なる秒数(あるいは他の時間単位)です。しかし
<chrono> は型安全を重視して設計されており、不適切な使用を防ぐルールが敷かれています。そのルールの意味を理解すれば、C++ での時間扱いはずっと安心し、テストも安定します。
クロックエポック:ゼロが普遍ではない理由
<chrono> の time_point は常に あるエポック を基準に測られます。しかしここで注意すべき点があります:各クロックは自分自身のエポックを定義します。
| クロック | エポック |
|---|---|
| Unix epoch(1970‑01‑01 00:00 UTC) |
| 未指定の単調増分エポック(ブート時刻など) |
| 通常は または のいずれかへの別名 |
影響
-
異なるクロックから得た
は意味のある比較ができません。time_point
例えば、システムクロックと単調増分クロックのタイムポイントを直接比較すると、何も示しません。 -
をsteady_clock::time_point
から引くことは、実質的に「1970‑01‑01 と任意のブート時刻カウンタとの差分は?」と尋ねるようなものです。答えはマシン・OS・月相まで変わります。system_clock::time_point
テストでもこの罠に陥りがちです。テストが
steady_clock がゼロから始まる、またはそのエポックが実行ごとやプラットフォーム間で安定していると仮定すると、テストは脆弱になります。決定的な動作を求めるなら、制御可能なテストクロックを使うか、むしろエポックの露出自体を避ける方がベターです。
クロック間変換:できることとできないこと
クロック間の変換は、そのエポックが異なるため難しいものです。長らく標準ライブラリには
time_point を別クロックへ変換する仕組みはありませんでした。しかし C++20 で登場した clock_cast と clock_time_conversion の特殊化により、クロックが意味のある関係を持つ場合に安全に変換できます。
標準サポートされる変換(clock_cast
& clock_time_conversion
)
clock_castclock_time_conversionC++20 で導入された
std::chrono::clock_cast は、変換が定義されているときに一方のクロックから別のクロックへ time_point を変換します。変換が定義される条件は次の二つです。
- クロック同士に 既知で安定した数式関係 があること。
の特殊化が存在すること。clock_time_conversion<FromClock, ToClock>
標準ライブラリはすでに以下のペア間で変換を提供しています:
↔︎system_clock
,utc_clock
,tai_clock
,gps_clockfile_clock- カスタムクロック(
が指定されている場合)clock_time_conversion
これらのクロックは共通のエポックとオフセットを持ちます(例:TAI は UTC より 37 秒先にある)。したがってライブラリは安全に変換計算を行えます。
例:utc_clock
を tai_clock
に変換
utc_clocktai_clockusing namespace std::chrono; auto utc_now = utc_clock::now(); auto tai_now = clock_cast<tai_clock>(utc_now);
この結果は、TAI と UTC の関係が標準で定義されているため、安定して決まります。
また自分自身の変換を定義することも可能です。
clock_time_conversion をカスタムクロック用に特殊化すれば、仮想/テストクロックやシミュレーションクロック、あるいはドメイン固有のクロック(フレームカウンタなど)にも対応できます。
変換が存在しない場合の手動相関
system_clock と steady_clock のように 固定された数式関係 が無いクロック同士では、標準は正確な変換を提供できません。エポックも挙動も異なるため、単純に「推定」しかできません。
auto system_now = std::chrono::system_clock::now(); auto steady_now = std::chrono::steady_clock::now(); auto offset = system_now - steady_now; // ここでオフセットを取得 // 後で steady を system に近似変換したいときは auto estimated_system_time = some_steady_tp + offset;
これは、
steady_clock で正確な時間間隔を測定しつつ、人が読めるタイムスタンプをログに残したい場合に便利です。ただし 注意:system_clock が NTP 同期や手動変更でジャンプするとオフセットは無効になります。長時間実行するプロセスでは危険な依存関係となります。
テスト側では、依存性注入を利用して「制御可能なクロック」を二つ渡し、変換ロジックを明示的に検証すべきです。実際のクロック間の相関関係に頼らず、あなたが書いた数学式をテストで確認します。
期間(Duration)のキャストと精度
期間は「数値+単位」だけのように見えますが、単位変換には微妙な精度問題があります。
より粗い単位への変換(切捨て)
using namespace std::chrono_literals; auto ns = 1500ns; // 1500 ナノ秒 auto us = std::chrono::duration_cast<std::chrono::microseconds>(ns); // us == 1 microsecond (残りの 500 ns は失われる)
より細かい単位への変換(「想像上の精度」)
std::chrono::milliseconds ms{1}; auto ns2 = std::chrono::duration_cast<std::chrono::nanoseconds>(ms); // ns2 == 1'000'000 ns、しかし実際にナノ秒で測定したわけではない
C++20 では
floor、ceil、round が追加され、意図を明確化し情報損失の場所を可視化できます。
using namespace std::chrono_literals; auto original = 1499ns; // 最も近いマイクロ秒へ丸める auto rounded_us = std::chrono::round<std::chrono::microseconds>(original); // rounded_us == 1us // ここで 1499 ns ≈ 1 µs と決定したことになる // 残りの 499 ns は意図的に失われる
オーバーフロー・アンダーフローと表現限界
大きな期間を減算または変換すると、オーバーフローやアンダーフローが起こる可能性があります。たとえば、数十年離れた
system_clock のタイムポイント同士を誤って減算したり、64 ビットの整数上限を超える期間を加算するケースです。
std::chrono::duration<Rep, Period> は内部で整数型(Rep)を使用します。符号付き整数がオーバーフローすると未定義動作に陥ります。符号付き Rep を使うメリットは、負の期間が合法的に発生する場合やロジックエラーを早期に検出できる点です。
まとめ
クロック間変換は
<chrono> の中で最も難しい部分です。クロックごとに異なるエポックがあり、ジャンプするものとしないものがあります。また期間の扱い方によって挙動が大きく変わります。
ベストプラクティス
- 時間間隔は常に同じクロックで測定(できれば
)。steady_clock - 人が読める表現へは境界でのみ変換。
- エポックの関係を仮定しない。二つのタイムスタンプが「近く見える」からといって、クロック間に安定した関係があるとは限らない。
- 切り捨て・切り上げ・丸めは明示的に指定。
、floor
、ceil
を使う。round - クロック自体を検証しない。ロジック(変換計算)を検証する。
これらを守れば、C++ での時間扱いは不思議が少なくなります。
次週予告
次回は C++20 で追加された新しいクロックについて語ります。お楽しみに!