【问题标题】:C++ std::tm returns wrong value after converting it from std::chrono::time_pointC++ std::tm 从 std::chrono::time_point 转换后返回错误值
【发布时间】:2020-01-03 05:43:01
【问题描述】:

TL;DR:如何使用std::chrono::system_clock::time_point 仅基于某些参数进行比较(例如,我只需要小时、分钟和秒,而不是日、月等)。

另外:将std::chrono::system_clock::time_point 转换为std::tm 后,std::tm.tm_hours 包含的值比std::chrono::system_clock::time_point 的原始输入值高一。


我让std::chrono::system_clock::time_point 工作的理论方法:

typedef std::chrono::system_clock::time_point TimePoint;
TimePoint MainWindow::createTimePoint(int h, int m)
{
    TimePoint createdTime = std::chrono::system_clock::time_point{std::chrono::hours(h) + std::chrono::minutes(m)};

    time_t tt = std::chrono::system_clock::to_time_t(createdTime);
    tm timeExtracted = *localtime(&tt);

    std::cout << "input:\t\t" << "H = " << h << ", M = " << m << std::endl; 
    std::cout << "timeExtracted:\t" << "H = " << timeExtracted.tm_hour << ", M = " << timeExtracted.tm_min << std::endl; 

    return createdTime;
}

如果我运行它,timeExtracted 的小时数始终是输入 h 的 +1。

为什么会这样?以及如何解决这个问题?我查看了其他一些显示此问题的帖子,但他们无法帮助我。可能也是因为这个:

我认为当我创建TimePoint时,日、月等也设置为随机值或初始化为某个值。关键是:我希望它们始终是相同的值,以便我的TimePoint(转换后)基本上显示:

timeExtracted.tm_sec = 0
timeExtracted.tm_min = m
timeExtracted.tm_hour = h
timeExtracted.tm_mon = 0
timeExtracted.tm_wday = 0
timeExtracted.tm_mday = 0
timeExtracted.tm_yday = 0
timeExtracted.tm_year = 0
timeExtracted.tm_isdst = 0

如何使用 std::chrono 的比较操作比较其中两个 TimePoint,但只比较小时和分钟。


如果我的问题不清楚,很抱歉,已经很晚了。我明天早上再检查一次。谢谢。

【问题讨论】:

  • 你在哪个时区?对于奇怪的与时间相关的行为,我总是首先看到的是是否存在来自区域设置/时区的某些东西导致问题。
  • 当您谈到小时和分钟等“字段”时,您是指您当地的时区吗?因为system_clock 是UTC。如果在 UTC 中比较是可以的,那是一个答案。如果您需要在当地时区进行比较,那就另当别论了。
  • @1201ProgramAlarm 哦,好吧。我会调查的。谢谢!

标签: c++ time chrono


【解决方案1】:

我将开始回答,但这不会是一个完整的答案,因为我还不确定完整的问题。不过,我可以帮忙。

TimePoint createdTime = system_clock::time_point{hours(h) + minutes(m)};

(我剪掉了std::chrono:: 限定词,以便更容易阅读和讨论)

这会创建一个 1970-01-01 hh:mm:00 UTC 的时间戳。简而言之,system_clock::time_point 正在测量自 1970 年新年(UTC)以来的持续时间(以微秒或纳秒等某些单位为单位)。从技术上讲,上面是一个近似值,system_clock 不计算闰秒,但我们现在可以(并且应该)忽略这个细节。

这个:

tm timeExtracted = *localtime(&tt);

将根据您计算机的本地时区设置引入 UTC 偏移校正。时区调整规则(希望)将基于您所在地区 1970 年生效的规则。

存在用于获取system_clock::time_point 并将其分解为诸如{year, month, day, hours, minutes, seconds, microseconds} 等字段的技术和库。但这种转换还取决于您是否希望这些字段采用 UTC、本地时间或其他任意时区。

非常的第一步是根据需要应用与某个时区相关的 UTC 偏移量。如果意图是 {h, m} 代表本地时间而不是 UTC,则您的 {h, m} 输入可能需要在将它们放入 system_clock::time_point 之前进行 UTC 偏移调整。

更新:商店营业时间示例

这个例子将使用我的free, open-source time zone library,因为我觉得它更容易使用并且允许更多可读性和表达性的代码。

此示例将system_clock::time_point 作为输入,并将其与一周中每一天的打开/关闭时间列表进行比较,并确定输入时间是否在工作日的这些时间范围之内或之外与输入时间t 相关联。假定商店营业时间相对于商店的本地时区,这也是为运行此代码的计算机设置的当前时区。

#include "date/tz.h"

#include <algorithm>
#include <cassert>
#include <chrono>

bool
is_store_open_at(std::chrono::system_clock::time_point tp)
{
    using namespace date;
    using namespace std::chrono;

    struct day_schedule
    {
        weekday wd;
        minutes open;
        minutes close;
    };

    // hours are expressed in terms of local time
    static constexpr day_schedule store_hours[]
    {
      // week day   open-time   close-time
        {Monday,    0h,                0h},  // closed all day
        {Tuesday,   8h,               18h},
        {Wednesday, 8h,               18h},
        {Thursday,  8h,               18h},
        {Friday,    8h,               18h},
        {Saturday,  8h,         15h+30min},
        {Sunday,    9h+30min,         15h}
    };

    auto local_tp = current_zone()->to_local(tp);
    auto local_day = floor<days>(local_tp);
    auto local_time_of_day = local_tp - local_day;
    weekday local_weekday{local_day};
    auto ds = std::find_if(std::begin(store_hours), std::end(store_hours),
                           [local_weekday](day_schedule const& x)
                           {
                               return x.wd == local_weekday;
                           });
    assert(ds != std::end(store_hours));
    return ds->open <= local_time_of_day && local_time_of_day < ds->close;
}

#include <iostream>

int
main()
{
    std::cout << is_store_open_at(std::chrono::system_clock::now()) << '\n';
}

该函数首先定义一些方便的数据结构来存储一周中每一天的打开和关闭时间。 day_scheduleopenclose 成员以当地时间测量“自午夜以来的分钟数”。

输入时间tp 以UTC 表示,因为它的类型是system_clock::time_point。 C++ 标准目前没有规定,但明年的 C++20 会规定。

zoned_seconds用于根据调用current_zone()获取的计算机时区设置,将UTC时间t转换为本地时间。我已将t 截断为几秒钟以简化一些语法。这不是绝对必要的。我已经编辑使用稍微简单的语法来消除zoned_secondszoned_seconds 在其他示例中可能非常有用,但在这个示例中,麻烦多于其价值。 auto local_tp = current_zone()-&gt;to_local(tp) 是将 UTC 转换为本地时间点的更简单方法。

local_tp 是一个chrono::time_point,它被认为是“本地时间”,它不同于与system_clock 关联的chrono::time_points 系列。这样做的好处是,如果本地时间和 UTC 时间不小心混合在一起,就会出现编译时错误。

local_days 只是将local_tp 截断为days 精度。它仍然是一个chrono::time_point,只是一个粗略的表示当地时区描述的一天的开始。

从当地时间午夜开始的持续时间就是local_tp - local_day

可以通过将local_day 转换为类型weekday 来获得星期几(由当地时区定义)。这是与 tp 关联的星期几。

现在只需在store_hours 中搜索与local_weekday 匹配的条目即可。

如果local_time_of_day 处于或超过open 时间且尚未达到close 时间,则商店营业。

如果“商店营业时间”指定为 UTC 而不是本地时间,那么这个程序会稍微简化一些,但仍然类似。

【讨论】:

  • 好的,谢谢!它已经清除了一点。谢谢您的帮助!老实说,我还没有完全理解这一点。现在我没有时间,所以我想在今天晚些时候(当我有时间的时候)提出并具体说明这个问题。但到目前为止:谢谢你!!!
  • 好的,现在我重新访问了。我现在明白你的意思了。我可以摆脱*localtime(&amp;tt) 的偏移时间吗?我真的只需要一个基于工作日、小时和分钟(也许是秒)的时间。或者我应该问这个:你能给我一个我想做的事情的代码示例吗?做一个起点,做一个终点,检查当前时间是否在它之间,当它在那个范围内时做点什么?也许这让它更清楚一点,也许我可以在看到一些示例代码后工作或提出更好的问题(顺便说一句,我在网络上没有找到。(对于这样的操作))
  • 喜欢商店的营业时间吗?这是否意味着输入时间以本地时区而不是 UTC 表示?
  • 非常感谢!这正是我所需要的!我仍然需要更全面地理解它,但我想就是这样。太感谢了!有什么问题可以再问吗?
  • 当然!你也可以在这里打开问题:github.com/HowardHinnant/date/issues 在这里:gitter.im/HowardHinnant/…
猜你喜欢
  • 2015-09-24
  • 1970-01-01
  • 1970-01-01
  • 2020-10-13
  • 1970-01-01
  • 1970-01-01
  • 2013-05-22
  • 2019-10-30
  • 1970-01-01
相关资源
最近更新 更多