老问题的新答案。
新答案的理由:我们现在有更好的工具。
我假设所需的结果是自当地午夜以来的“实际”毫秒数(当午夜以来 UTC 偏移发生变化时得到正确答案)。
基于<chrono>
和使用这个免费的开源库的现代答案非常简单。该库已移植到 VS-2013、VS-2015、clang/libc++、macOS 和 linux/gcc。
为了使代码可测试,我将启用 API 从std::chrono::system_clock::time_point
任何IANA 时区中的任何时间获取自午夜以来的时间(以毫秒为单位) 。
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());
}
首先要做的是创建一个除了 pair和zoned_time
之外什么都不做的。这种配对主要是为了使语法更好。它实际上不做任何计算。zone
t
下一步是获取与 关联的本地时间t
。就是zt.get_local_time()
这样。这将具有任何精度t
,除非t
比秒更粗,在这种情况下,本地时间将具有秒精度。
调用将本地时间floor<days>
截断days
为. 这有效地创建了一个local_time
等于当地午夜的时间。通过将其分配local_time
回zt
,我们根本不会更改时区zt
,但我们将local_time
of更改zt
为午夜(因此sys_time
也更改了它)。
我们可以从 with 中得到对应sys_time
的。这是对应于当地午夜的 UTC 时间。然后从输入中减去它并将结果截断到所需的精度是一个简单的过程。zt
zt.get_sys_time()
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
从 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 点的当地时间,因此正确输出了自午夜以来的 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 行代码,其他一切都是关于测试和处理罕见的例外情况。