将 UTC time_t 转换为 UTC tm
Convert UTC time_t to UTC tm
我所有的内部时间都是存储在 time_t
中的 UTC。我需要将它们转换为 struct tm
。如果我使用 localtime
the time is correct, except that tm_isdst
may be set resulting in the time being off an hour. If I use gmtime
它会得到错误的时间,因时区差异而关闭。
编辑 我正在寻找适用于 Windows 和 Linux
的跨平台解决方案
这是一个跨平台解决方案,需要 C++11 或更好的版本,并且 free, open-source, header-only date library. And when your vendor brings you C++20, you can loose the date library 因为它被合并到 C++20 <chrono>
.
实际上,通过 <chrono>
从 time_t
转换为 UTC tm
比使用 C API 更容易。在每个平台上确实存在各种扩展来执行此操作,但扩展具有不同的语法。此解决方案在所有平台上都有统一的语法。
在 C++11 中,虽然没有指定,但 time_t
和 std::chrono::system_clock
跟踪 Unix Time 是一个 de-facto 标准,尽管精度不同。在 C++20 中,这成为 std::chrono::system_clock
的指定。对于 time_t
,de-facto 精度为 seconds
。人们可以利用这一知识在 C API 和 C++ <chrono>
API.
之间创建极其高效的转换
第 1 步:将 time_t
转换为 chrono::time_point
这非常简单高效:
date::sys_seconds
to_chrono(std::time_t t)
{
using namespace date;
using namespace std::chrono;
return sys_seconds{seconds{t}};
}
date::sys_seconds
只是一个类型别名:
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>
即time_point
基于 system_clock
但具有 seconds
精度。
这个函数所做的就是将类型从 time_t
更改为 seconds
,然后再更改为 time_point
。没有进行实际计算。这是 to_chrono
:
的优化 clang 编译
.globl __Z9to_chronol ## -- Begin function _Z9to_chronol
.p2align 4, 0x90
__Z9to_chronol: ## @_Z9to_chronol
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
movq %rdi, %rax
popq %rbp
retq
.cfi_endproc
所有这些都是函数调用的样板。如果你内联它,它甚至会消失。
此外,通过简单地删除 using namespace date
并将 date::sys_seconds
更改为 std::chrono::sys_seconds
,此函数将移植到 C++20。
第 2 步:将 sys_seconds
转换为 tm
这是计算发生的地方:
std::tm
to_tm(date::sys_seconds tp)
{
using namespace date;
using namespace std::chrono;
auto td = floor<days>(tp);
year_month_day ymd = td;
hh_mm_ss<seconds> tod{tp - td}; // <seconds> can be omitted in C++17
tm t{};
t.tm_sec = tod.seconds().count();
t.tm_min = tod.minutes().count();
t.tm_hour = tod.hours().count();
t.tm_mday = unsigned{ymd.day()};
t.tm_mon = (ymd.month() - January).count();
t.tm_year = (ymd.year() - 1900_y).count();
t.tm_wday = weekday{td}.c_encoding();
t.tm_yday = (td - sys_days{ymd.year()/January/1}).count();
t.tm_isdst = 0;
return t;
}
所有计算都发生在前三行:
auto td = floor<days>(tp);
year_month_day ymd = td;
hh_mm_ss<seconds> tod{tp - td}; // <seconds> can be omitted in C++17
然后函数的其余部分只是提取字段以填写 tm
成员。
auto td = floor<days>(tp);
上面的第一行只是将 time_point
的精度从 seconds
截断为 days
,向下舍入到负无穷大(即使在 time_point
之前1970-01-01 纪元)。这只不过是除以 86400。
year_month_day ymd = td;
上面的第二行计算自纪元以来的天数,并将其转换为 {year, month, day}
数据结构。这是大部分计算发生的地方。
hh_mm_ss<seconds> tod{tp - td}; // <seconds> can be omitted in C++17
上面的第三行从 seconds-precision time_point
中减去 days-precision time_point
,得到自午夜 UTC 以来的 std::chrono::seconds
持续时间。然后将此持续时间分解为 {hours, minutes, seconds}
数据结构(类型 hh_mm_ss
)。在 C++17 中,这一行可以选择性地简化为:
hh_mm_ss tod{tp - td}; // <seconds> can be omitted in C++17
现在to_tm
只是根据C API提取字段填写tm
。
int tm_sec; // seconds after the minute -- [0, 60]
int tm_min; // minutes after the hour -- [0, 59]
int tm_hour; // hours since midnight -- [0, 23]
int tm_mday; // day of the month -- [1, 31]
int tm_mon; // months since January -- [0, 11]
int tm_year; // years since 1900
int tm_wday; // days since Sunday -- [0, 6]
int tm_yday; // days since January 1 -- [0, 365]
int tm_isdst; // Daylight Saving Time flag
首先 zero-initialize tm
很重要,因为不同的平台有额外的 tm
数据成员作为扩展,最好给值 0。
tm t{};
对于小时、分钟和秒,只需从 tod
中提取适当的 chrono::duration
,然后使用 .count()
成员函数提取整数值:
t.tm_sec = tod.seconds().count();
t.tm_min = tod.minutes().count();
t.tm_hour = tod.hours().count();
day
有到 unsigned
的显式转换,这是 C API 没有给 tm
数据成员意外的少数几个地方之一偏见:
t.tm_mday = unsigned{ymd.day()};
tm_mon
被定义为“自一月以来的月份”,因此必须考虑偏差。可以从 month
中减去 January
,得到 months
duration。这是一个chrono::duration
,可以用.count()
成员函数提取积分值:
t.tm_mon = (ymd.month() - January).count();
同样,tm_year
是自 1900 年以来的年数:
t.tm_year = (ymd.year() - 1900_y).count();
可以用转换语法将days-precisiontime_point
(td
)转换成weekday
,然后weekday
有一个成员函数.c_encoding()
来提取一个与 C API: days since Sunday -- [0, 6] 相匹配的整数值。或者,如果需要 ISO 编码 [Mon, Sun] -> [1, 7].
,还有一个 .iso_encoding()
成员函数
t.tm_wday = weekday{td}.c_encoding();
tm_yday
是自 1 月 1 日以来的天数 -- [0, 365]。这很容易通过从 days-precision time_point
(td
) 中减去第一年来计算,创建 days
chrono::duration
:
t.tm_yday = (td - sys_days{ymd.year()/January/1}).count();
最后 tm_isdst
应设置为 0 以指示夏令时未生效。从技术上讲,此步骤在 zero-initializing tm
时已经完成,但出于可读性目的在此重复:
t.tm_isdst = 0;
to_tm
可以通过以下方式移植到 C++20:
- 移除
using namespace date;
- 将
date::sys_seconds
更改为 std::chrono::sys_seconds
- 将
1900_y
更改为 1900y
使用示例:
给定 time_t
,下面是如何使用这些函数将其转换为 UTC tm
:
std::time_t t = std::time(nullptr);
std::tm tm = to_tm(to_chrono(t));
这里有必要的headers:
#include "date/date.h"
#include <chrono>
#include <ctime>
或者在 C++20 中,只需:
#include <chrono>
#include <ctime>
我所有的内部时间都是存储在 time_t
中的 UTC。我需要将它们转换为 struct tm
。如果我使用 localtime
the time is correct, except that tm_isdst
may be set resulting in the time being off an hour. If I use gmtime
它会得到错误的时间,因时区差异而关闭。
编辑 我正在寻找适用于 Windows 和 Linux
的跨平台解决方案这是一个跨平台解决方案,需要 C++11 或更好的版本,并且 free, open-source, header-only date library. And when your vendor brings you C++20, you can loose the date library 因为它被合并到 C++20 <chrono>
.
实际上,通过 <chrono>
从 time_t
转换为 UTC tm
比使用 C API 更容易。在每个平台上确实存在各种扩展来执行此操作,但扩展具有不同的语法。此解决方案在所有平台上都有统一的语法。
在 C++11 中,虽然没有指定,但 time_t
和 std::chrono::system_clock
跟踪 Unix Time 是一个 de-facto 标准,尽管精度不同。在 C++20 中,这成为 std::chrono::system_clock
的指定。对于 time_t
,de-facto 精度为 seconds
。人们可以利用这一知识在 C API 和 C++ <chrono>
API.
第 1 步:将 time_t
转换为 chrono::time_point
这非常简单高效:
date::sys_seconds
to_chrono(std::time_t t)
{
using namespace date;
using namespace std::chrono;
return sys_seconds{seconds{t}};
}
date::sys_seconds
只是一个类型别名:
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>
即time_point
基于 system_clock
但具有 seconds
精度。
这个函数所做的就是将类型从 time_t
更改为 seconds
,然后再更改为 time_point
。没有进行实际计算。这是 to_chrono
:
.globl __Z9to_chronol ## -- Begin function _Z9to_chronol
.p2align 4, 0x90
__Z9to_chronol: ## @_Z9to_chronol
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
movq %rdi, %rax
popq %rbp
retq
.cfi_endproc
所有这些都是函数调用的样板。如果你内联它,它甚至会消失。
此外,通过简单地删除 using namespace date
并将 date::sys_seconds
更改为 std::chrono::sys_seconds
,此函数将移植到 C++20。
第 2 步:将 sys_seconds
转换为 tm
这是计算发生的地方:
std::tm
to_tm(date::sys_seconds tp)
{
using namespace date;
using namespace std::chrono;
auto td = floor<days>(tp);
year_month_day ymd = td;
hh_mm_ss<seconds> tod{tp - td}; // <seconds> can be omitted in C++17
tm t{};
t.tm_sec = tod.seconds().count();
t.tm_min = tod.minutes().count();
t.tm_hour = tod.hours().count();
t.tm_mday = unsigned{ymd.day()};
t.tm_mon = (ymd.month() - January).count();
t.tm_year = (ymd.year() - 1900_y).count();
t.tm_wday = weekday{td}.c_encoding();
t.tm_yday = (td - sys_days{ymd.year()/January/1}).count();
t.tm_isdst = 0;
return t;
}
所有计算都发生在前三行:
auto td = floor<days>(tp);
year_month_day ymd = td;
hh_mm_ss<seconds> tod{tp - td}; // <seconds> can be omitted in C++17
然后函数的其余部分只是提取字段以填写 tm
成员。
auto td = floor<days>(tp);
上面的第一行只是将 time_point
的精度从 seconds
截断为 days
,向下舍入到负无穷大(即使在 time_point
之前1970-01-01 纪元)。这只不过是除以 86400。
year_month_day ymd = td;
上面的第二行计算自纪元以来的天数,并将其转换为 {year, month, day}
数据结构。这是大部分计算发生的地方。
hh_mm_ss<seconds> tod{tp - td}; // <seconds> can be omitted in C++17
上面的第三行从 seconds-precision time_point
中减去 days-precision time_point
,得到自午夜 UTC 以来的 std::chrono::seconds
持续时间。然后将此持续时间分解为 {hours, minutes, seconds}
数据结构(类型 hh_mm_ss
)。在 C++17 中,这一行可以选择性地简化为:
hh_mm_ss tod{tp - td}; // <seconds> can be omitted in C++17
现在to_tm
只是根据C API提取字段填写tm
。
int tm_sec; // seconds after the minute -- [0, 60]
int tm_min; // minutes after the hour -- [0, 59]
int tm_hour; // hours since midnight -- [0, 23]
int tm_mday; // day of the month -- [1, 31]
int tm_mon; // months since January -- [0, 11]
int tm_year; // years since 1900
int tm_wday; // days since Sunday -- [0, 6]
int tm_yday; // days since January 1 -- [0, 365]
int tm_isdst; // Daylight Saving Time flag
首先 zero-initialize tm
很重要,因为不同的平台有额外的 tm
数据成员作为扩展,最好给值 0。
tm t{};
对于小时、分钟和秒,只需从 tod
中提取适当的 chrono::duration
,然后使用 .count()
成员函数提取整数值:
t.tm_sec = tod.seconds().count();
t.tm_min = tod.minutes().count();
t.tm_hour = tod.hours().count();
day
有到 unsigned
的显式转换,这是 C API 没有给 tm
数据成员意外的少数几个地方之一偏见:
t.tm_mday = unsigned{ymd.day()};
tm_mon
被定义为“自一月以来的月份”,因此必须考虑偏差。可以从 month
中减去 January
,得到 months
duration。这是一个chrono::duration
,可以用.count()
成员函数提取积分值:
t.tm_mon = (ymd.month() - January).count();
同样,tm_year
是自 1900 年以来的年数:
t.tm_year = (ymd.year() - 1900_y).count();
可以用转换语法将days-precisiontime_point
(td
)转换成weekday
,然后weekday
有一个成员函数.c_encoding()
来提取一个与 C API: days since Sunday -- [0, 6] 相匹配的整数值。或者,如果需要 ISO 编码 [Mon, Sun] -> [1, 7].
.iso_encoding()
成员函数
t.tm_wday = weekday{td}.c_encoding();
tm_yday
是自 1 月 1 日以来的天数 -- [0, 365]。这很容易通过从 days-precision time_point
(td
) 中减去第一年来计算,创建 days
chrono::duration
:
t.tm_yday = (td - sys_days{ymd.year()/January/1}).count();
最后 tm_isdst
应设置为 0 以指示夏令时未生效。从技术上讲,此步骤在 zero-initializing tm
时已经完成,但出于可读性目的在此重复:
t.tm_isdst = 0;
to_tm
可以通过以下方式移植到 C++20:
- 移除
using namespace date;
- 将
date::sys_seconds
更改为std::chrono::sys_seconds
- 将
1900_y
更改为1900y
使用示例:
给定 time_t
,下面是如何使用这些函数将其转换为 UTC tm
:
std::time_t t = std::time(nullptr);
std::tm tm = to_tm(to_chrono(t));
这里有必要的headers:
#include "date/date.h"
#include <chrono>
#include <ctime>
或者在 C++20 中,只需:
#include <chrono>
#include <ctime>