C++,获取自当地时间午夜以来的毫秒数

use*_*171 5 c++ time dst

令人难以置信的是,用 C++ 来完成上述任务是多么困难。我正在寻找一种尽可能高效地完成此操作,同时仍保持毫秒精度的方法。

到目前为止,我的解决方案要么需要大量代码和函数调用,导致实现速度变慢,要么要求我每年更改代码两次以考虑夏令时。

将运行该程序的计算机使用 ntp 进行同步,并且应该可以直接访问根据 DST 调整的本地时间。有这方面专业知识的人可以分享一些解决方案吗?

我的平台是CentOS5,g++ 4.1.2,Boost 1.45,解决方案不需要可移植,可以是特定于平台的。它只需要快速并避免每年两次代码更改。

How*_*ant 7

C++20 更新旧问题的新答案

不再需要第 3方库。

假设有人想要测量“物理秒”。即,如果自午夜起进行了夏令时调整,则此计算会将其考虑在内。此代码与下面的代码非常相似,但缩短为仅在没有本地午夜或有两个本地午夜时抛出异常的版本。

#include <chrono>
#include <iostream>

std::chrono::milliseconds
since_local_midnight(std::chrono::system_clock::time_point t = std::chrono::system_clock::now(),
                     std::chrono::time_zone const* zone = std::chrono::current_zone())
{
    using namespace std::chrono;
    zoned_time zt{zone, t};
    zt = floor<days>(zt.get_local_time());
    return floor<milliseconds>(t - zt.get_sys_time());
}

int
main()
{
    std::cout << since_local_midnight() << '\n';
}
Run Code Online (Sandbox Code Playgroud)

演示。

解释:

  • time_zone和组合time_point成一个zoned_time。Azoned_time无非就是这两条信息的配对。它可以方便地检索本地时间或 UTC ( sys) 时间。
  • 检索本地时间和楼层,精确到天以获得本地午夜。将这个新的本地时间分配回zoned_time. 如果该时区的该日期没有午夜,则此分配将引发异常。
  • 检索相当于当地午夜的 UTC 时间并从输入中减去该值time_point。按照OP中的要求将答案截断为毫秒精度。

请参阅下面更详细的答案,了解处理或解决当地午夜 0 或 2 个情况的方法。

老问题的新答案。

新答案的理由:我们现在拥有更好的工具。

我假设期望的结果是自当地午夜以来的“实际”毫秒(当自午夜以来 UTC 偏移量发生变化时得到正确的答案)。

基于<chrono>并使用这个免费开源库的现代答案非常简单。该库已移植到 VS-2013、VS-2015、clang/libc++、macOS 和 linux/gcc。

为了使代码可测试,我将启用 API 以获取任意IANA 时区std::chrono::system_clock::time_point中午夜以来的时间(以毫秒为单位) 。

std::chrono::milliseconds
since_local_midnight(std::chrono::system_clock::time_point t,
                     const date::time_zone* zone);
Run Code Online (Sandbox Code Playgroud)

然后,要获取本地时区午夜以来的当前时间,很容易在这个可测试的原语之上编写:

inline
std::chrono::milliseconds
since_local_midnight()
{
    return since_local_midnight(std::chrono::system_clock::now(), 
                                date::current_zone());
}
Run Code Online (Sandbox Code Playgroud)

写出问题的实质是相对简单的:

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());
}
Run Code Online (Sandbox Code Playgroud)

要做的第一件事是创建一个zoned_time除了配对zone和之外什么都不做的对象t。这种配对主要是为了让语法变得更好。它实际上不做任何计算。

下一步是获取与 关联的当地时间t。就是这样zt.get_local_time()。这将具有任何精度t,除非t比秒更粗糙,在这种情况下,本地时间将具有秒的精度。

调用会将本地时间floor<days> 截断为精度days。这实际上创建了local_time等于当地午夜的时间。通过将其分配local_time回,我们根本zt不会更改 的时区,但我们将 的更改为午夜(因此也更改了它)。ztlocal_timeztsys_time

我们可以从withsys_time中得到相应的。这是对应于当地午夜的 UTC 时间。然后,从输入中减去该值并将结果截断为所需的精度是一个简单的过程。ztzt.get_sys_time()t

如果当地午夜不存在或不明确(有两个),则此代码将抛出一个异常,该异常源自std::exception信息丰富的what().

可以简单地打印出自当地午夜以来的当前时间:

std::cout << since_local_midnight().count() << "ms\n";
Run Code Online (Sandbox Code Playgroud)

为了确保我们的函数正常工作,有必要输出一些示例日期。通过指定时区(我将使用“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());
}
Run Code Online (Sandbox Code Playgroud)

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";
Run Code Online (Sandbox Code Playgroud)

现在是冬天的凌晨 3 点,输出:

2016-01-15 03:00:00 EST is 10800000ms after midnight
Run Code Online (Sandbox Code Playgroud)

并且是正确的(10800000ms == 3h)。

我只需将新的本地时间分配给 即可再次运行测试zt。以下是“春季前进”夏令时过渡(三月第二个星期日)之后的凌晨 3 点:

zt = local_days{sun[2]/mar/2016} + 3h;
std::cout << zt << " is "
          << since_local_midnight(zt).count() << "ms after midnight\n";
Run Code Online (Sandbox Code Playgroud)

这输出:

2016-03-13 03:00:00 EDT is 7200000ms after midnight
Run Code Online (Sandbox Code Playgroud)

由于跳过了当地时间凌晨 2 点到凌晨 3 点,因此这会正确输出自午夜以来的 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
Run Code Online (Sandbox Code Playgroud)

最后一个例子是在秋季从夏令时转换回标准后不久,我们得到了 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
Run Code Online (Sandbox Code Playgroud)

如果需要,您可以避免在午夜不存在或不明确的情况下出现异常。在模棱两可的情况下,您必须事先决定:您想从第一个午夜还是第二个午夜开始测量?

以下是您从一开始就进行测量的方法:

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());
}
Run Code Online (Sandbox Code Playgroud)

如果您想从第二个午夜开始测量,请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
Run Code Online (Sandbox Code Playgroud)

如果您使用choose::earliest/latest公式,而不是上面的异常what(),您将得到:

2016-10-02 03:00:00 PYST is 7200000ms after midnight
Run Code Online (Sandbox Code Playgroud)

如果你想做一些非常棘手的事情,比如用于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());
Run Code Online (Sandbox Code Playgroud)

因为遇到这种情况确实很少见(例外),所以使用try/catch是合理的。但是,如果您想在不抛出异常的情况下执行此操作,该库中存在一个低级 API 可以实现此目的。

最后请注意,这个冗长的答案实际上只有 3 行代码,其他所有内容都是关于测试和处理罕见的异常情况。


use*_*171 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);
}
Run Code Online (Sandbox Code Playgroud)