旧问题的新答案。
新答案的理由:我们现在有了更好的工具。
我假设期望的结果是自当地午夜以来的“实际”毫秒数(当自午夜以来 UTC 偏移发生变化时得到正确答案)。
基于<chrono> 并使用此free, open-source library 的现代答案非常容易。该库已移植到 VS-2013、VS-2015、clang/libc++、macOS 和 linux/gcc。
为了使代码可测试,我将启用 API 以从任何 std::chrono::system_clock::time_point 中的任何 IANA time zone 获取自午夜以来的时间(以毫秒为单位)。
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 的时区,但我们将zt 的local_time 更改为午夜(因此也更改了它的sys_time )。
我们可以通过zt.get_sys_time()从zt中得到对应的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());
}
这只是从zoned_time 中提取system_clock::time_point 和时区(精度为秒),并将其转发给我们的实现。
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 点的当地时间,因此正确输出了自午夜以来的 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());
因为遇到这种情况确实很少见(例外),所以使用try/catch 是合理的。但是,如果您想完全不抛出任何东西,那么这个库中有一个低级 API 可以实现这一点。
最后请注意,这个冗长的答案实际上是大约 3 行代码,其他一切都是关于测试和处理罕见的例外情况。