【问题标题】:localtime_s fails where gmtime_s succeeds with dates before 1-1-1970localtime_s 失败,其中 gmtime_s 在 1970 年 1 月 1 日之前的日期成功
【发布时间】:2017-07-06 07:29:09
【问题描述】:

我正在尝试使用 std::chrono::time_point<std::chrono::system_clock> 将当前年份存储在 1970 年之前的日期中,但是我遇到了一个关于从其内容读取到 std::tm 结构的问题。

我首先将time_point 转换为time_t,然后读取其值以获得tm_year 值。但是,当尝试这样做时,代码在使用 localtime_s 时会失败,但在我使用 gmtime_s 时会成功。这仅适用于 1970 年 1 月 1 日之前的日期,之后的日期使用这两个函数都可以正常工作。

下面的代码重现了错误。如果terstGmTimeVsLocalTimeutc=true 调用它可以工作,如果它用utc=false 调用它不会产生正确的输出。

#include <iomanip>
#include <time.h>
#include <iostream>

void testGmTimeVsLocaltime(const bool& utc) {
    // Create time
    std::tm timeInfoWrite = std::tm();
    timeInfoWrite.tm_year = 1969 - 1900;    // Year to parse, here it is 1969
    timeInfoWrite.tm_mon = 0;
    timeInfoWrite.tm_mday = 1;
    timeInfoWrite.tm_hour = 1;
    timeInfoWrite.tm_min = 0;
    timeInfoWrite.tm_sec = 0;
    timeInfoWrite.tm_isdst = -1;

    std::chrono::time_point<std::chrono::system_clock> timePoint = std::chrono::system_clock::from_time_t(utc ? _mkgmtime(&timeInfoWrite) : std::mktime(&timeInfoWrite));

    // Convert to time_t
    std::time_t timeT = std::chrono::system_clock::to_time_t(timePoint);

    // Read values
    std::tm timeInfoRead;
    if (utc) {
        gmtime_s(&timeInfoRead, &timeT);
    } else {
        localtime_s(&timeInfoRead, &timeT);
    }

    // Output result
    std::cout << (timeInfoRead.tm_year + 1900) << '\n';

    // Wait for input
    std::getchar();
}

int main() {
    testGmTimeVsLocaltime(true); // Set to false to show bug

    return 0;
}

utc=true 输出 1969,正如预期的那样。但是,utc=false 输出 1899(可能是因为发生错误并且 tm_year 被设置为 -1)。

我有什么遗漏吗? The documentation 没有具体说明 localtime_s 在 1970 年 1 月 1 日之前的日期应该失败。

如果有什么不同,我使用的是 Windows 10 x64。

【问题讨论】:

  • MSDN 文档建议它会失败:“1970 年 1 月 1 日午夜之前。”见msdn.microsoft.com/en-us/library/bf12f0hc.aspx
  • @RichardCritten 啊,我知道我一定一直在使用特定于操作系统的实现。没有意识到localtime_s 不是标准的一部分。在 1970 年 1 月 1 日午夜之前,是否会有另一个函数不会因值而失败?或者也许是另一种策略?我想我可以检索 UTC 和本地时间之间的差异,只需使用 gmtime_s 函数,然后在需要本地时间时将差异添加到结果中,但似乎可以做得更简单。
  • 你链接的文档说 "Converts given time since epoch";其中 epoch 是实现决定时间开始的时间。在 Windows 上,有一些操作系统调用可以随时工作,但我不知道任何标准 API。
  • 您的代码因localtime_sgmtime_s 而失败。只是失败的方式不同。尝试在这两种情况下添加std::cout &lt;&lt; "timeT = " &lt;&lt; timeT &lt;&lt; '\n';; you'll see that it's -1`。 (另外,你需要#include &lt;chrono&gt;。)将1969 - 1900更改为1959 - 1900,它仍然会输出1969

标签: c++ chrono time-t


【解决方案1】:

使用Howard Hinnant's free, open-source date lib,您可以完全避开笨拙、错误和容易出错的C api,直接使用基于&lt;chrono&gt; 的现代系统:

#include "chrono_io.h"
#include "date.h"
#include <iostream>

void
testGmTimeVsLocaltime()
{
    using namespace date;
    // Create time
    auto timeInfoWrite = 1969_y/jan/1;
    sys_days timePoint = timeInfoWrite;  // this is a chrono::time_point
    std::cout << timePoint.time_since_epoch() << '\n'; // -365 days

    // Convert to time_t
    // no need

    // Read values
    year_month_day timeInfoRead = timePoint;

    // Output result
    std::cout << timeInfoRead.year() << '\n';
}

int
main()
{
    testGmTimeVsLocaltime();
}

输出:

-365[86400]s
1969

有一些文字可以很容易地填充year_month_day 结构,它类似于tm 的年、月和日部分。您可以轻松地将其转换为 std::chrono::time_point&lt;system_clock, days&gt; (sys_days)。这与system_clock::time_point 相同,但精度为天。它本身会隐式转换为秒精度的time_point(类型定义为sys_seconds)或system_clock::time_point

上面我只是输出了它的time_since_epoch(),这表明它比纪元早-365天。

实际上不需要转换为 C API 数据结构,但如果您愿意,这很容易。例如,假设time_t 是自 1970-01-01 以来的秒数:

std::time_t timeT = sys_seconds{timePoint}.time_since_epoch().count();
std::cout << timeT << '\n';

哪个输出:

-31536000

反向转换(返回year_month_day)同样简单。如果你想从timeT 转换,它只是稍微复杂一点:

year_month_day timeInfoRead = floor<days>(sys_seconds{seconds{timeT}});

这首先将time_t 转换为chrono::seconds,然后转换为seconds-precsion time_point,然后转换为days-precsion time_point,最后转换为year_month_day 字段类型(@ 987654349@-like)。

最后year_month_day 有一个year() 可流式的getter 成员函数。如果需要,您可以将year 显式转换为int

int{timeInfoRead.year()}

但我认为最好将诸如年、月和日之类的内容保留为不同的类型,以便编译器可以在您意外混淆它们时帮助您捕捉。

最后,如果您真的想要 1969-01-01 00:00:00 在您计算机的本地时区there's a library 也可以这样做。并且只是对上面的简单程序进行了小修改。

#include "tz.h"
#include <iostream>

void
testGmTimeVsLocaltime()
{
    using namespace date;
    using namespace std::chrono;
    // Create time
    auto timeInfoWrite = 1969_y/jan/1;
    auto timePoint = make_zoned(current_zone(), local_days{timeInfoWrite});

    // Convert to time_t
    std::time_t timeT = timePoint.get_sys_time().time_since_epoch().count();
    std::cout << timeT << '\n';

    // Read values
    timePoint = sys_seconds{seconds{timeT}};
    year_month_day timeInfoRead{floor<days>(timePoint.get_local_time())};

    // Output result
    std::cout << timeInfoRead.year() << '\n';
}

int
main()
{
    testGmTimeVsLocaltime();
}

输出:

-31518000
1969

现在您使用计算机的current_zone() 时区创建zoned_seconds,并将您的timeInfoWrite 转换为local_days 而不是sys_days

您可以从timePoint 中获取本地时间或系统时间。对于转换为time_t,系统时间最有意义:

std::time_t timeT = timePoint.get_sys_time().time_since_epoch().count();

现在(对我而言)输出是 5 小时后(18000 秒)。

-31518000

您可以使用.get_local_time().get_sys_time() 返回本地年份或系统 (UTC) 年份。对我来说这没什么区别 ("America/New_York")。但是,如果您在 "Australia/Sydney",如果您请求 UTC 年份而不是 1969,您将得到 1968。这一切都非常容易模拟,只需在上面的程序中将 "Australia/Sydney""America/New_York" 替换为 current_zone() .

是的,它适用于 Windows、VS-2013 及更高版本。时区库需要一些安装:https://howardhinnant.github.io/date/tz.html#Installation

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-09
    • 2020-06-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-04
    • 1970-01-01
    相关资源
    最近更新 更多