std::chrono:将时钟的纪元设置为 1/1/0000

Posted

技术标签:

【中文标题】std::chrono:将时钟的纪元设置为 1/1/0000【英文标题】:std::chrono: Set clock's epoch to 1/1/0000 【发布时间】:2014-01-22 07:13:49 【问题描述】:

是否可以手动将纪元日期/时间设置为 0000 年 1 月 1 日,所以我可以使用 std::chrono::time_point::time_since_epoch 来计算给定日期与 0000 年 1 月 1 日之间的差异?

我尝试了以下方法:

#include <iostream>
#include <chrono>
#include <ctime>

int main(int argc, char*argv[])

    std::tm epochStart = ;
    epochStart.tm_sec = 0;
    epochStart.tm_min = 0;
    epochStart.tm_hour = 0;
    epochStart.tm_mday = 0;
    epochStart.tm_mon = 0;
    epochStart.tm_year = -1900;
    epochStart.tm_wday = 0;
    epochStart.tm_yday = 0;
    epochStart.tm_isdst = -1;

    std::time_t base = std::mktime(&epochStart);

    std::chrono::system_clock::time_point baseTp=
        std::chrono::system_clock::from_time_t(base);
    std::time_t btp = std::chrono::system_clock::to_time_t(baseTp);
    std::cout << "time: " << std::ctime(&btp);


但这给了我

time: Thu Jan  1 00:59:59 1970

【问题讨论】:

【参考方案1】:

我会完全避免std::time_t。使用来自chrono-Compatible Low-Level Date Algorithms 的days_from_civil,您可以立即计算std::chrono::system_clock::time_point 和proleptic Gregorian calendar 中的任何 日期之间的任何差异1

除了 days_from_civil 需要年/月/日三倍并将其转换为 1970-01-01 之前/之后的天数(与计时兼容的纪元)之外,还可以方便地创建自定义chrono::duration代表24小时:

typedef std::chrono::duration
        <
            int,
            std::ratio_multiply<std::ratio<24>, std::chrono::hours::period>
        > days;

现在你可以创建任何你想要的纪元:

constexpr days epoch = days(days_from_civil(0, 1, 1));  // 0000-01-01

在 C++1y 中,这甚至是编译时计算!

你可以从任何其他std::chrono::duration中减去这个std::chrono::duration

auto delta = std::chrono::system_clock::now().time_since_epoch() - epoch;

delta 现在是 std::chrono::duration,表示从现在到 0000-01-01 之间的时间量。然后,您可以根据需要将其打印出来,或以其他方式对其进行操作。例如,这是一个完整的工作演示:

#include "../date_performance/date_algorithms"
#include <iostream>
#include <chrono>

typedef std::chrono::duration
        <
            int,
            std::ratio_multiply<std::ratio<24>, std::chrono::hours::period>
        > days;

int
main()

    constexpr days epoch = days(days_from_civil(0, 1, 1));
    auto delta = std::chrono::system_clock::now().time_since_epoch() - epoch;
    days d = std::chrono::duration_cast<days>(delta);
    std::cout << "It has been " << d.count() << " days, ";
    delta -= d;
    auto h = std::chrono::duration_cast<std::chrono::hours>(delta);
    std::cout << h.count() << " hours, ";
    delta -= h;
    auto m = std::chrono::duration_cast<std::chrono::minutes>(delta);
    std::cout << m.count() << " minutes, ";
    delta -= m;
    auto s = std::chrono::duration_cast<std::chrono::seconds>(delta);
    std::cout << s.count() << " seconds ";
    std::cout << " since 0000-01-01\n";

对我来说输出:

It has been 735602 days, 19 hours, 14 minutes, 32 seconds  since 0000-01-01

关于溢出的警告:

std::chrono::system_clock::time_point::duration 不能保证有足够大的范围来执行此操作。事实证明,在我的系统上确实如此。它是一个有符号的 long long 中的微秒,它将跨越 +/- 292,000 年。如果您需要避免溢出问题,您可以在减去 0000-01-01 之前将 std::chrono::system_clock::time_point::duration 截断为更粗的单位(例如秒或天)以扩大范围。

我开始思考

这通常会导致灾难。但是在这种情况下,我决定无论如何我都应该添加到这篇文章中。这个:

constexpr days epoch = days(days_from_civil(0, 1, 1));

具有days 类型,即duration。但它真的不是duration。这是一个时间点。这是一个约会。它是一个time_point,具有粗略的精度。通过引入一个新的 typedef,这篇文章中的代码可以稍微清理一下:

typedef std::chrono::time_point<std::chrono::system_clock, days> date_point;

现在不用写了:

constexpr days epoch = days(days_from_civil(0, 1, 1));

可以写:

constexpr date_point epochdays(days_from_civil(0, 1, 1));

但更重要的是,而不是:

auto delta = std::chrono::system_clock::now().time_since_epoch() - epoch;

我们现在可以写了:

auto delta = std::chrono::system_clock::now() - epoch;

这个delta 仍然具有与以前完全相同的类型和值,并且演示中的所有其他内容仍然与以前完全相同。

这既是一个小变化,也是一个大变化。通过将epoch 视为time_point 而不是durationtime_pointduration 的代数对我们有用,既简化了表达式的类型检查帮助我们编写更简洁、错误更少的代码。

例如,可以将两个duration 加在一起。但这根本没有任何意义:

epoch + epoch

通过使用time_point 而不是duration 来表示epoch 的类型,编译器会在编译时捕获此类无意义的表达式。

1proleptic Gregorian calendar 有第 0 年。在第 0 年,它比儒略历晚 2 天。使用第 0 年也符合 ISO 8601。只要所有相关方都知道您使用的是什么日历,那么一切都很好。如果需要,非正数年和“BC 年”之间的转换是微不足道的。

【讨论】:

+1 表示兼容计时的低级日期算法。非常感谢!【参考方案2】:

有可能,您给出的代码(减去一个小修复,tm_mday1 开头)产生:

Sat Jan  1 00:00:00 0

Live example

真正的问题是:您使用的是 32 位还是 64 位?对于 32 位系统,time_t 也只有 32 位,并且您被限制为 1970 +/- 68 年。

在 64 位系统上,限制由 std::mktimestd::strftime 给出,在我自己的代码中,我对这些字符串和相应的值进行了单元测试:

"-2147481748-01-01 00:00:00" maps to -67768040609740800
"2147483647-12-31 23:59:59" maps to 67767976233532799

我可能还应该提到,由于底层操作系统功能存在错误,因此上述系统无法正常工作。郑重声明:我在 Linux 上。

【讨论】:

我正在使用 MacOS X 10.9.1 (Mavericks) - 我的可执行文件是 64 位:文件 src/demo src/demo:Mach-O 64 位可执行文件 x86_64 并且:您的代码仍然给我 Thu Jan 1 00:59:59 1970 ;-(,而现场示例是 Sat Jan 1 00:00:00 0. @JohannHorvat 这可能是你的问题,我现在无法确认,但我记得 MacOS 在这方面存在一些问题。如果您有兴趣:0-01-01 00:00:00 的正确值是 -62167219200(所有值都是 UTC)。【参考方案3】:

没有。 mktime 和朋友们基于 UNIX 时间,从 1970 年 1 月 1 日开始。

实际上没有 0000 年 1 月 0 日这样的事情,因此您最好找到另一种方法来解决您的实际问题。

【讨论】:

01st January 1970,但值可以是负数。 @DanielFrey:这是为了允许两个日期之间的间隔,而不是为了及时回到任意虚构的日期。 (提示:1BC 导致 1AD)

以上是关于std::chrono:将时钟的纪元设置为 1/1/0000的主要内容,如果未能解决你的问题,请参考以下文章

如何找到纪元时间戳和 std::chrono::system_clock::now 之间的时间差(以毫秒为单位)

c ++ chrono给出负纪元时间

`std::chrono` 时钟与`boost::xtime` 的比较

C++ chrono 系统时间(以毫秒为单位),时间操作

C++ 11 时间编程 std::chrono::steady_clock使用--计算程序执行时间

获取 POSIX 纪元作为 system_clock::time_point