4

上記を C++ で実現するのがどれほど難しいかは信じられないほどです。ミリ秒の精度を維持しながら、これを可能な限り効率的に行う方法を探しています。

これまでのソリューションでは、多くのコードと関数呼び出しが必要で実装が遅くなるか、夏時間を考慮して年に 2 回コードを変更する必要がありました。

これが実行されるコンピューターは ntp を使用して同期され、DST に合わせて調整された現地時間に直接アクセスできる必要があります。これに関する専門知識を持つ誰かがいくつかの解決策を共有できますか?

私のプラットフォームは CentOS5、g++ 4.1.2、Boost 1.45 です。ソリューションは移植可能である必要はなく、プラットフォーム固有のものにすることができます。迅速で、年に 2 回のコード変更を避ける必要があるだけです。

4

6 に答える 6

5

古い質問に対する新しい回答。

新しい回答の根拠: 現在、より優れたツールがあります。

望ましい結果は、ローカルの真夜中からの「実際の」ミリ秒であると想定しています (真夜中から 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です。この組み合わせは、主に構文をより良くするためのものです。実際には計算を行いません。zonet

次のステップは、 に関連付けられた現地時間を取得することtです。それがそうですzt.get_local_time()。これは、が秒よりも粗くtない限り、任意の精度になります。その場合、現地時間の精度は秒になります。t

の呼び出しは、現地時間を の精度にfloor<days> 切り捨てdaysます。これによりlocal_time、ローカルの真夜中と同等の時間が効果的に作成されます。これをlocal_timeに戻すとzt、 のタイム ゾーンはまったく変更されませんがztlocal_timeztが真夜中に変更されます (したがって、 も変更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 行のコードであり、それ以外はすべてテストと、まれな例外的なケースの処理に関するものであることに注意してください。

于 2016-07-17T01:31:58.870 に答える
1

それは、なぜ「真夜中からミリ秒」が必要なのか、そしてそれを何に使用するのかによって異なります。

そうは言っても、DSTが関与している午前3時は、真夜中から3時間という意味ではないという事実を考慮する必要があります。何らかの理由で「真夜中からのミリ秒」が本当に必要な場合は、真夜中に1つのエポック時間を取得し、午前3時に別の時間を取得して、2を引くことができます。

しかし、繰り返しになりますが、「真夜中」の概念は、場合によってはそれほど安定していない可能性があります。地域のルールが、DSTが終了する午前1時から深夜0時までフォールバックすることである場合、1日のうちに2つの深夜があります。

ですから、あなたが「真夜中」に依存しているのではないかと私は本当に疑っています。通常、これらの内訳時間は表示と人間の理解のみを目的としており、すべての内部計時はエポック時間で行われます。

于 2012-06-20T22:37:15.750 に答える
0

を実行localtime_rし、mktimeの結果を調整した後localtime_r、エポックを基準にした「真夜中」の値を計算できます。

編集:ルーチンに渡しnowて、への不要な呼び出しを回避しtimeます。

time_t global_midnight;
bool checked_2am;

void update_global_midnight (time_t now, bool dst_check) {
    struct tm tmv;
    localtime_r(&now, &tmv);
    tmv.tm_sec = tmv.tm_min = tmv.tm_hour = 0;
    global_midnight = mktime(&tmv);
    checked_2am = dst_check || (now >= (global_midnight + 2*3600));
}

最初global_midnight0です。次に、午前2時と翌日の値を調整して、DSTとの同期が維持されるようにします。を呼び出すとclock_gettime、に対する差を計算できますglobal_midnight

編集: OPはルーチンのベンチマークを望んでいるため、高速パスであると想定するコンパイラーのコードを微調整しtrue、最も近いミリ秒に丸めます。

unsigned msecs_since_midnight () {
    struct timespec tsv;
    clock_gettime(CLOCK_REALTIME, &tsv);
    bool within_a_day = (tsv.tv_sec < (global_midnight + 24*3600));
    if (within_a_day)
        if (checked_2am || (tsv.tv_sec < (global_midnight + 2*3600))
            return ((tsv.tv_sec - global_midnight)*1000
                    + (tsv.tv_nsec + 500000)/1000000);
    update_global_midnight(tsv.tv_sec, within_a_day);
    return ((tsv.tv_sec - global_midnight)*1000
            + (tsv.tv_nsec + 500000)/1000000);
}
于 2012-06-20T22:19:34.323 に答える
0

Linuxを使用している場合はgettimeofday、エポックからの秒数/マイクロ秒数を指定します。これが役立つ場合があります。ただし、DSTは時間の内訳(つまり、年、月、日、時、分、秒)にのみ関係するため、これは実際にはDSTとは関係ありません。

内訳時間を取得するには、gmtimeまたはlocaltimeの結果の「秒」部分を使用しgettimeofdayます。

struct timeval tv;
gettimeofday(&tv, 0);
struct tm *t = localtime(&tv.tv_sec);  // t points to a statically allocated struct

localtimeローカルタイムゾーンの内訳時刻を示しますが、DSTの影響を受けやすい場合があります。gmtimeDSTの影響を受けないUTCでの内訳時刻を示します。

于 2012-06-20T21:58:24.193 に答える
0

提供された回答はどれも、私が必要とすることを実際に行っていません。私はうまくいくと思うスタンドアロンのものを思いつきました。誰かがエラーを見つけた場合、またはより高速な方法を考えられる場合は、お知らせください。現在のコードの実行には 15 マイクロ秒かかります。私はSOに何かをより速くするように挑戦します(そして、SOが成功することを本当に願っています= P)

inline int ms_since_midnight()
{
  //get high precision time
  timespec now;
  clock_gettime(CLOCK_REALTIME,&now);

  //get low precision local time
  time_t now_local = time(NULL);
  struct tm* lt = localtime(&now_local);

  //compute time shift utc->est
  int sec_local = lt->tm_hour*3600+lt->tm_min*60+lt->tm_sec;
  int sec_utc = static_cast<long>(now.tv_sec) % 86400;
  int diff_sec; //account for fact utc might be 1 day ahead
  if(sec_local<sec_utc) diff_sec = sec_utc-sec_local;
  else diff_sec = sec_utc+86400-sec_local;
  int diff_hour = (int)((double)diff_sec/3600.0+0.5); //round to nearest hour

  //adjust utc to est, round ns to ms, add
  return (sec_utc-(diff_hour*3600))*1000+(int)((static_cast<double>(now.tv_nsec)/1000000.0)+0.5);
}
于 2012-06-21T00:34:58.313 に答える
0

[こちら] の投稿を参照し、以下の関数が GMT 時間の午前 0 時からのミリ秒を返すように変更しました。

int GetMsSinceMidnightGmt(std::chrono::system_clock::time_point tpNow) {
    time_t tnow = std::chrono::system_clock::to_time_t(tpNow);
    tm * tmDate = std::localtime(&tnow);
    int gmtoff = tmDate->tm_gmtoff;
    std::chrono::duration<int> durTimezone(gmtoff); // 28800 for HKT
    // because mktime assumes local timezone, we shift the time now to GMT, then fid mid
    time_t tmid = std::chrono::system_clock::to_time_t(tpNow-durTimezone);
    tm * tmMid = std::localtime(&tmid);
    tmMid->tm_hour = 0;
    tmMid->tm_min = 0;
    tmMid->tm_sec = 0;
    auto tpMid = std::chrono::system_clock::from_time_t(std::mktime(tmMid));
    auto durSince = tpNow - durTimezone - tpMid;
    auto durMs = std::chrono::duration_cast<std::chrono::milliseconds>(durSince);
    return durMs.count();
}

現地時間を取得したい場合は、はるかに簡単です。

于 2016-07-14T04:02:24.333 に答える