提问人:glades 提问时间:5/1/2023 最后编辑:glades 更新时间:5/2/2023 访问量:537
使用 std::chrono 提取自本地时间午夜以来经过的秒数
Use std::chrono to extract number of seconds passed since midnight in local time
问:
我想计算自开始以来的秒数。问题是这给了我UTC的日间起始时间,而不是我当地时区的起始时间。比较一下:std::chrono::floor
演示:
#include <time.h>
#include <cstdio>
#include <iostream>
#include <cstdint>
#include <chrono>
#include <ctime>
int main() {
setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0", 1);
tzset();
const auto tp_daystart = std::chrono::floor<std::chrono::days>(std::chrono::system_clock::now());
const auto tp_now = std::chrono::system_clock::now();
const auto daysec = std::chrono::duration_cast<std::chrono::seconds>(tp_now - tp_daystart);
std::time_t ttp = std::chrono::system_clock::to_time_t(tp_now);
std::time_t ttp_s = std::chrono::system_clock::to_time_t(tp_daystart);
std::cout << "time start: " << std::ctime(&ttp_s) << " / time now: " << std::ctime(&ttp) << "seconds since start of day = " << daysec.count() << "\n";
}
这会产生:
time start: Mon May 1 02:00:00 2023
/ time now: Mon May 1 16:26:04 2023
seconds since start of day = 51964
从一天开始的秒数当然是错误的,因为它从凌晨 2 点到下午 4 点计算,而实际上它应该从 .,因为我确实想在这个时区计算自这一天开始以来的秒数!我该如何实现?00:00:00
答:
这个问题实际上有一些棘手的细节,当夏令时等奇怪的事情使事情复杂化时,必须决定你到底想要发生什么。但无论你想发生什么,它都可以在时间内完成。
我假设你想要自一天开始以来的物理秒数。这意味着,如果在午夜和现在之间有 UTC 偏移量变化(例如,减去一小时),则将考虑此减法。
这意味着我们需要:
- 立即获取 UTC 时间。
- 获取当地午夜的 UTC 时间。
- 减去两个 UTC 时间。
由于您对秒精度感兴趣,我们将从一开始就截断它,然后不再担心它:
#include <chrono>
#include <format>
#include <iostream>
int
main()
{
using namespace std;
using namespace chrono;
auto utc_now = floor<seconds>(system_clock::now());
出于可读性目的,我使用 local 来减少冗长。using directives
接下来获取对应的本地时间:utc_now
auto local = zoned_time{current_zone(), utc_now};
current_zone()
查找当前设置的时区。并且只是一个方便的“对”,它将 a 和 a 结合在一起。可以使用此结构来提取本地时间。或者你可以直接格式化本地时间:zoned_time
time_zone const*
sys_time
cout << "time now : " << format("{:%a %b %e %T %Y}", local) << '\n';
对我来说,这只是输出我的本地时间和日期:
time now : Mon May 1 10:56:07 2023
现在,要获取本地午夜,截断必须以本地时间进行,而不是以系统时间进行:days
local = floor<days>(local.get_local_time());
这将提取本地时间,将其截断为,然后将该本地时间分配回 .这将更改基础 UTC 时间点 (sys_time),但不会更改时区。days
zoned_time
现在您可以再次格式化:local
cout << "time start: " << format("{:%a %b %e %T %Y}", local) << '\n';
输出示例:
time start: Mon May 1 00:00:00 2023
最后,从本地午夜(存储在 中)中提取 UTC 时间,并从起始 UTC 时间中减去它:local
auto delta = utc_now - local.get_sys_time();
cout << "seconds since start of day = " << delta << '\n';
输出示例:
seconds since start of day = 39367s
还有一个复杂情况:如果存在导致本地时间完全跳过本地午夜的 UTC 偏移更改,该怎么办?或者,如果当地有两个午夜呢?
你想如何处理这个问题?
有几种可能性:
您可以忽略这种可能性,因为您确定您的本地时区永远不会这样做。在这种情况下,上面的代码可以按原样进行。如果确实发生,则会在代码行上抛出异常,该代码行将本地午夜分配回 。
local
如果有两个午夜,你要选择第一个,如果午夜为零,你要从午夜之后的第一个本地时间开始计算。例如,如果本地时间从 23:30 跳到 00:30,则从本地时间 00:30 开始计数。
在这种情况下,更改:
local = floor<days>(local.get_local_time());
自:
local = zoned_time{local.get_time_zone(),
floor<days>(local.get_local_time()),
choose::earliest};
这:
- 提取本地时间,并将其截断为精确度。
days
- 形成与 相同的time_zone的新 。如果您使用的是移动移动设备,您不想再次拨打电话。
zoned_time
local
current_zone()
- 用于选择第一个本地时间,以防存在从本地到 UTC 的两个映射。这也将映射到与本地时间间隔(午夜零点)相关的 UTC 时间点。
choose::earliest
- 将此新名称重新分配到 .
zoned_time
local
现在永远不会抛出异常,你总是会从今天的第一个时刻开始计算秒数,即使也有昨天的片段。
如果您想要第二个午夜,以便永远不会计算昨天的点数,则更改为 .choose::earliest
choose::latest
如果您不想实际计算物理秒数,而是“日历秒数”,那么请用当地时间而不是UTC进行算术运算。矛盾的是,这不会涉及在白天更改 UTC 偏移量的复杂性,因此更简单。在此计算中,凌晨 4 点将始终是午夜后的 4 小时,即使凌晨 2 点的 UTC 偏移量更改将本地时间设置回凌晨 1 点也是如此。
auto utc_now = floor<seconds>(system_clock::now());
auto local_now = zoned_time{current_zone(), utc_now}.get_local_time();
auto local_midnight = floor<days>(local_now);
auto delta = local_now - local_midnight;
cout << "time now : " << format("{:%a %b %e %T %Y}", local_now) << '\n';
cout << "time start: " << format("{:%a %b %e %T %Y}", local_midnight) << '\n';
cout << "seconds since start of day = " << delta << '\n';
在此版本中,本地时间永远不会映射回 UTC,因此没有机会发现所述映射不是唯一的,随后引发异常。
需要明确的是,此代码的所有三个不同版本几乎总是给出相同的结果。只有当在“现在”和上一个本地午夜之间进行 UTC 偏移量更改时,这些不同的版本才会给出不同的结果。
评论
您正在寻找 .std::chrono::time_zone
setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0", 1);
tzset();
std::chrono::time_zone const* time_zone = std::chrono::current_zone();
if (!time_zone)
return -1;
一旦你有一个有效的,这很容易time_zone
const auto local = tz.to_local(sys);
const auto local_daystart = std::chrono::floor<std::chrono::days>(local);
const auto sys_daystart = tz.to_sys(local_daystart);
time_zone
在 C++20 中添加。您可以直接在系统数据库中搜索特定时区,也可以使用进程的当前时区(如上所述)。
你必须接受失败,因为有时系统没有时区。
评论