古い質問に対する新しい回答。
新しい回答の根拠: 現在、より優れたツールがあります。
望ましい結果は、ローカルの真夜中からの「実際の」ミリ秒であると想定しています (真夜中から UTC オフセットが変更された場合に正しい答えが得られます)。
<chrono>
この無料のオープンソース ライブラリに基づく最新の回答は非常に簡単です。このライブラリは、VS-2013、VS-2015、clang/libc++、macOS、および linux/gcc に移植されています。
std::chrono::system_clock::time_point
コードをテスト可能にするために、API を有効にして、任意のIANA タイム ゾーンの任意の時刻から真夜中 (ミリ秒単位) を取得できるようにします。
std::chrono::milliseconds
since_local_midnight(std::chrono::system_clock::time_point t,
const date::time_zone* zone);
そして、ローカル タイム ゾーンの午前 0 時以降の現在の時刻を取得するには、このテスト可能なプリミティブの上に簡単に記述できます。
inline
std::chrono::milliseconds
since_local_midnight()
{
return since_local_midnight(std::chrono::system_clock::now(),
date::current_zone());
}
問題の本質を書くことは比較的簡単です:
std::chrono::milliseconds
since_local_midnight(std::chrono::system_clock::time_point t,
const date::time_zone* zone)
{
using namespace date;
using namespace std::chrono;
auto zt = make_zoned(zone, t);
zt = floor<days>(zt.get_local_time());
return floor<milliseconds>(t - zt.get_sys_time());
}
最初に行うことは、 と のペア以外は何もしない を作成することzoned_time
です。この組み合わせは、主に構文をより良くするためのものです。実際には計算を行いません。zone
t
次のステップは、 に関連付けられた現地時間を取得することt
です。それがそうですzt.get_local_time()
。これは、が秒よりも粗くt
ない限り、任意の精度になります。その場合、現地時間の精度は秒になります。t
の呼び出しは、現地時間を の精度にfloor<days>
切り捨てdays
ます。これによりlocal_time
、ローカルの真夜中と同等の時間が効果的に作成されます。これをlocal_time
に戻すとzt
、 のタイム ゾーンはまったく変更されませんがzt
、local_time
のzt
が真夜中に変更されます (したがって、 も変更sys_time
されます)。
から対応する を取得sys_time
できzt
ますzt.get_sys_time()
。これは、現地の午前 0 時に対応する UTC 時間です。これを入力から差し引いてt
、結果を目的の精度に切り捨てるのは簡単なプロセスです。
std::exception
ローカルの真夜中が存在しないか、あいまいな場合 (2 つある場合)、このコードは非常に有益な から派生した例外をスローしますwhat()
。
現地の真夜中からの現在の時刻は、次のように簡単に出力できます。
std::cout << since_local_midnight().count() << "ms\n";
関数が機能していることを確認するために、いくつかの日付の例を出力する価値があります。これは、タイム ゾーン (ここでは "America/New_York" を使用します) と、正しい答えがわかっている現地の日付/時刻を指定することで、最も簡単に実行できます。テストで適切な構文を容易にするために、別の方法since_local_midnight
が役立ちます。
inline
std::chrono::milliseconds
since_local_midnight(const date::zoned_seconds& zt)
{
return since_local_midnight(zt.get_sys_time(), zt.get_time_zone());
}
system_clock::time_point
これは単純に a からタイム ゾーンをzoned_time
(秒の精度で)抽出し、それを実装に転送します。
auto zt = make_zoned(locate_zone("America/New_York"), local_days{jan/15/2016} + 3h);
std::cout << zt << " is "
<< since_local_midnight(zt).count() << "ms after midnight\n";
これは真冬の午前 3 時で、次のように出力されます。
2016-01-15 03:00:00 EST is 10800000ms after midnight
正しいです(10800000ms == 3h)。
に新しい現地時間を割り当てるだけで、テストを再度実行できますzt
。以下は、「春先」の夏時間への移行直後 (3 月の第 2 日曜日) の午前 3 時です。
zt = local_days{sun[2]/mar/2016} + 3h;
std::cout << zt << " is "
<< since_local_midnight(zt).count() << "ms after midnight\n";
これは以下を出力します:
2016-03-13 03:00:00 EDT is 7200000ms after midnight
午前 2 時から午前 3 時までの現地時間がスキップされたため、これは午前 0 時から 2 時間を正しく出力します。
真夏の真夜中の例では、真夜中の 3 時間後に戻ります。
zt = local_days{jul/15/2016} + 3h;
std::cout << zt << " is "
<< since_local_midnight(zt).count() << "ms after midnight\n";
2016-07-15 03:00:00 EDT is 10800000ms after midnight
そして最後に、夏時間から標準に戻った秋の移行直後の例では、4 時間が得られます。
zt = local_days{sun[1]/nov/2016} + 3h;
std::cout << zt << " is "
<< since_local_midnight(zt).count() << "ms after midnight\n";
2016-11-06 03:00:00 EST is 14400000ms after midnight
必要に応じて、午前 0 時が存在しないかあいまいな場合の例外を回避できます。あいまいなケースでは、事前に決定する必要があります。最初の午前 0 時から測定するか、それとも 2 番目から測定するか。
最初から測定する方法は次のとおりです。
std::chrono::milliseconds
since_local_midnight(std::chrono::system_clock::time_point t,
const date::time_zone* zone)
{
using namespace date;
using namespace std::chrono;
auto zt = make_zoned(zone, t);
zt = make_zoned(zt.get_time_zone(), floor<days>(zt.get_local_time()),
choose::earliest);
return floor<milliseconds>(t - zt.get_sys_time());
}
午前0時から計測したい場合は、choose::latest
代わりに使用してください。真夜中が存在しない場合は、いずれかを使用できますchoose
。これは、真夜中のローカル タイム ギャップに隣接する単一の UTC 時点から測定されます。これはすべて非常に混乱する可能性があるため、デフォルトの動作は単にスローするだけです。非常に有益な例外what()
:
zt = make_zoned(locate_zone("America/Asuncion"), local_days{sun[1]/oct/2016} + 3h);
std::cout << zt << " is "
<< since_local_midnight(zt).count() << "ms after midnight\n";
what():
2016-10-02 00:00:00.000000 is in a gap between
2016-10-02 00:00:00 PYT and
2016-10-02 01:00:00 PYST which are both equivalent to
2016-10-02 04:00:00 UTC
choose::earliest/latest
上記の例外の代わりに式を使用すると、次のようwhat()
になります。
2016-10-02 03:00:00 PYST is 7200000ms after midnight
存在しない真夜中の使用のような非常にトリッキーなことをしたいがchoose
、あいまいな真夜中に例外をスローする場合、それも可能です:
auto zt = make_zoned(zone, t);
try
{
zt = floor<days>(zt.get_local_time());
}
catch (const date::nonexistent_local_time&)
{
zt = make_zoned(zt.get_time_zone(), floor<days>(zt.get_local_time()),
choose::latest);
}
return floor<milliseconds>(t - zt.get_sys_time());
このような状態になることは非常にまれ (例外的) であるため、 の使用try/catch
は正当化されます。ただし、まったくスローせずに実行したい場合は、このライブラリ内にそれを実現するための低レベル API が存在します。
最後に、この長々とした回答は実際には約 3 行のコードであり、それ以外はすべてテストと、まれな例外的なケースの処理に関するものであることに注意してください。