难以置信的是,用C++很难完成上述任务。我正在寻找一种在保持毫秒级精度的同时尽可能高效地执行此操作的方法。
到目前为止,我拥有的解决方案要么需要大量代码和函数调用,从而使实现速度变慢,要么需要我每年两次更改代码以节省夏时制。
将使用ntp同步将运行该计算机的计算机,并且应该可以直接访问针对DST调整的本地时间。对此有专业知识的人可以分享一些解决方案吗?
我的平台是CentOS5,g++ 4.1.2,Boost 1.45,解决方案不需要便携式,可以针对特定平台。它只是需要快速并且避免每年两次代码更改。
最佳答案
旧问题的新答案。
新答案的理由:我们现在有更好的工具。
我假设期望的结果是本地午夜以来的“实际”毫秒(当午夜以来UTC偏移量发生变化时,将获得正确的答案)。
基于<chrono>
并使用此free, open-source library的现代答案非常简单。该库已被移植到VS-2013,VS-2015,clang / libc++,macOS和linux / gcc。
为了使代码可测试,我将使API能够从任何IANA time zone中的任何std::chrono::system_clock::time_point
获取午夜以来的时间(以毫秒为单位)。
std::chrono::milliseconds
since_local_midnight(std::chrono::system_clock::time_point t,
const date::time_zone* zone);
然后,可以在此可测试原语之上轻松地获取本地时区午夜以来的当前时间:
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()
。这是与当地午夜相对应的UTC时间。这是一个简单的过程,可以从输入的t
中减去它,并将结果截断为所需的精度。如果本地午夜不存在或模棱两可(其中有两个),则此代码将引发从
std::exception
派生的异常,该异常具有非常丰富的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
(精确到秒)中提取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
来再次运行测试。以下是“ Spring 向前”夏时制转换(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点的本地时间,所以从午夜开始正确输出了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
如果需要,可以避免不存在午夜或模棱两可的情况。在模棱两可的情况下,您必须事先做出决定:您要从第一个午夜还是第二个午夜开始测量?
从头开始,这是您的测量方式:
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());
}
如果要从第二个午夜开始测量,请改用
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());
因为碰到这种情况确实很少见(异常(exception)),所以使用
try/catch
是合理的。但是,如果您想不做任何事情就这样做,则该库中存在一个低级API来实现该目标。最后请注意,这个漫长的答案实际上是关于3行代码,而其他所有内容都与测试有关,并要注意罕见的特殊情况。
关于c++ - C++,自本地时间午夜起算的毫秒数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11128629/