首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >将UTC time_t转换为UTC tm

将UTC time_t转换为UTC tm
EN

Stack Overflow用户
提问于 2021-07-27 16:05:17
回答 1查看 618关注 0票数 1

我所有的内部时间都存储在time_t中。我需要把它们转换成struct tm。如果我使用localtime,则时间是正确的,但tm_isdst可能会被设置,导致时间中断一小时。如果我使用gmtime,它会得到错误的时间,除以时区差。

编辑我正在寻找一个跨平台的解决方案,在和Linux中工作

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-07-28 15:17:05

这里有一个跨平台的解决方案,它需要C++11或更高版本,还有一个免费的,开放源码的,只有头的日期库。当您的供应商给您带来C++20时,您可以松掉数据库,因为它被合并到C++20 <chrono>中。

实际上,通过遍历time_ttm转换为UTC tm比使用C更容易。在每个平台上确实存在不同的扩展来实现这一点,但是扩展有不同的语法。该解决方案在所有平台上都具有统一的语法。

在C++11中,虽然没有指定,但time_tstd::chrono::system_clock跟踪Unix时间的精度不同,这是一个事实上的标准。在C++20中,这将为std::chrono::system_clock指定。对于time_t来说,事实上的精度是seconds。我们可以利用这些知识在can和C++ <chrono> API之间创建非常高效的转换。

步骤1:将time_t转换为chrono::time_point

这是非常容易和有效的:

代码语言:javascript
运行
复制
date::sys_seconds
to_chrono(std::time_t t)
{
    using namespace date;
    using namespace std::chrono;

    return sys_seconds{seconds{t}};
}

date::sys_seconds只是一个类型别名,用于:

代码语言:javascript
运行
复制
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>

即基于time_pointsystem_clock,但具有seconds的精度。

这个函数所做的就是将类型从time_t更改为seconds,然后更改为time_point。没有进行实际计算。下面是to_chrono的优化clang编译

代码语言:javascript
运行
复制
    .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

这里有一个函数调用的锅炉板。如果你把这个连在一起,那也就消失了。

此外,通过简单地删除C++20并将date::sys_seconds更改为std::chrono::sys_seconds,此函数将移植到std::chrono::sys_seconds

步骤2:将sys_seconds转换为tm

计算就是在这里进行的:

代码语言:javascript
运行
复制
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;
}

所有的计算都发生在前三行:

代码语言:javascript
运行
复制
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成员。

代码语言:javascript
运行
复制
auto td = floor<days>(tp);

上面的第一行简单地将time_point的精度从seconds截断到days,向下舍入到负无穷大(即使是在1970-01-01年以前的time_points )。这只不过是除以86400。

代码语言:javascript
运行
复制
year_month_day ymd = td;

上面的第二行计算了自该时代以来的天数,并将其转换为{year, month, day}数据结构。这是大多数计算发生的地方。

代码语言:javascript
运行
复制
hh_mm_ss<seconds> tod{tp - td};  // <seconds> can be omitted in C++17

上面的第三行从秒精度time_point中减去日精度time_point,从而导致从协调世界时午夜开始的std::chrono::seconds时间持续时间。然后将此持续时间分解为{hours, minutes, seconds}数据结构(类型为hh_mm_ss)。在C++17中,可以选择将该行简化为:

代码语言:javascript
运行
复制
hh_mm_ss tod{tp - td};  // <seconds> can be omitted in C++17

现在,to_tm只需提取字段,根据C API填写tm

代码语言:javascript
运行
复制
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

首先零初始化tm是很重要的,因为不同的平台都有额外的tm数据成员作为扩展,最好给值为0。

代码语言:javascript
运行
复制
tm t{};

对于小时、分钟和秒,只需从tod中提取适当的tod,然后用.count()成员函数提取积分值:

代码语言:javascript
运行
复制
t.tm_sec  = tod.seconds().count();
t.tm_min  = tod.minutes().count();
t.tm_hour = tod.hours().count();

day有向unsigned的显式转换,这是data不给tm数据成员意外偏倚的少数地方之一:

代码语言:javascript
运行
复制
t.tm_mday = unsigned{ymd.day()};

tm_mon被定义为“一月以来的几个月”,因此必须考虑到偏见。可以从January中减去month,从而导致months持续时间。这是一个chrono::duration,可以使用.count()成员函数提取积分值:

代码语言:javascript
运行
复制
t.tm_mon  = (ymd.month() - January).count();

类似地,tm_year是自1900年以来的几年:

代码语言:javascript
运行
复制
t.tm_year = (ymd.year() - 1900_y).count();

我们可以使用转换语法将一个天精度time_point (td)转换为一个weekday,然后weekday有一个成员函数.c_encoding()来提取一个与can相匹配的整数值:从周日开始- 0,6。或者,如果你想要ISO编码Mon,Sun -> 1,7,也可以使用.iso_encoding()成员函数。

代码语言:javascript
运行
复制
t.tm_wday = weekday{td}.c_encoding();

tm_yday是从1月1日开始的- 0,365。这很容易通过从天-精度time_point (td)中减去一年中的第一个时间来计算,从而创建一个days chrono::duration

代码语言:javascript
运行
复制
t.tm_yday = (td - sys_days{ymd.year()/January/1}).count();

最后,应将tm_isdst设置为0,以指示夏时制时间无效。从技术上讲,这个步骤在零初始化tm时已经完成了,但为了便于阅读,在这里重复如下:

代码语言:javascript
运行
复制
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

代码语言:javascript
运行
复制
std::time_t t = std::time(nullptr);
std::tm tm = to_tm(to_chrono(t));

以下是必要的标题:

代码语言:javascript
运行
复制
#include "date/date.h"
#include <chrono>
#include <ctime>

或者在C++20,只是:

代码语言:javascript
运行
复制
#include <chrono>
#include <ctime>
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68548288

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档