将 std::chrono::time_point 转换为 Unix 时间戳

45

我该如何得到自一个固定日期以来的std::chrono::duration?我需要这个来将std::chrono::time_point转换为Unix时间戳。

将代码插入XXX中

auto unix_epoch_start = XXX;
auto time = std::chrono::system_clock::now();
auto delta = time - unix_epoc_start;
auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(delta).count();

我知道time_point有一个方法time_since_epoch(),但不能保证它与Unix纪元(1970年1月1日UTC 00:00:00)相同。


2
这个有帮助吗?std::time_t t = std::chrono::system_clock::to_time_t(tp);(其中 tptime_point 类型) - Andy Prowl
1
@AndyProwl:在非POSIX系统上,time_t也不能保证是基于Unix纪元的。 - Mike Seymour
1
@AndyProwl:mktime函数以本地时间作为输入,而不是UTC时间。我认为没有任何方法可以使用标准库将UTC日期/时间转换为time_t - Mike Seymour
1
@MikeSeymour:没错,那么怎么样使用gmttime()呢?它似乎可以与UTC时间一起使用。 - Andy Prowl
类似这样:使用mktime()将编码在tm结构中的所需日期转换为本地时间 time_t值,然后使用gmtime()将该值转换为UTC tm结构,然后找出哪个tm结构在作为输入提供给mktime()时生成所需的UTC time_t值... - Andy Prowl
显示剩余10条评论
5个回答

38

Unix时间戳被定义为自1970年1月1日UTC以来的秒数,但不计算所有的秒数。这有点荒谬,人们不禁要想一下它的意义所在,因此我认为这是一个愚蠢的问题。

无论如何,让我们看一下一些平台关于time_ttime()的文档。

Linux

time()返回自1970-01-01 00:00:00 +0000 (UTC)时刻以来的秒数。

POSIX.1使用一个公式近似计算从指定时间到时代之间的秒数,该公式考虑了所有能被4整除的年份都是闰年,但如果它们也能被100整除,则不是闰年,除非它们也能被400整除,在这种情况下,它们是闰年。由于跳秒和系统时钟不需要与标准参考同步,因此该值与实际时间和时代之间的秒数不同。意图是使自时代以来的秒值的解释保持一致;详见POSIX.1-2008理由A.4.15。

Windows

time函数返回自协调世界时(UTC)1970年1月1日午夜(00:00:00)以来经过的秒数,根据系统时钟。

Mac OS X

ctime()、gmtime()和localtime()函数都使用以自1970年1月1日UTC以来的秒数表示日期和时间的参数。

asctime()、ctime()、difftime()、gmtime()、localtime()和mktime()函数符合ISO/IEC 9899:1990(即ISO C90)规范,并符合ISO/IEC 9945-1:1996(即POSIX.1)规范,前提是所选本地时区不包含闰秒表(请参阅zic(8))。

类似的文档可以在其他系统上找到,例如AIX、HP-UX、Solaris等。

因此,虽然在C++中没有规定,但有一种简单而广泛可移植的方法可以获取UNIX时间戳:

auto unix_timestamp = std::chrono::seconds(std::time(NULL));

如果您想要从1970年1月1日UTC以来的毫秒数(同样不计算所有毫秒数),则可以执行以下操作:

int unix_timestamp_x_1000 = std::chrono::milliseconds(unix_timestamp).count();

请记住,这些值不是真实的时间戳,因此通常无法在算术中使用Unix时间戳。例如,减去Unix时间戳并不能给出两个时间之间精确的秒数。如果你执行以下操作:

std::chrono::steady_clock::now() - unix_timestamp;

你将无法获得与实际对应于1970年01月01日00:00:00 +0000的时间点。


正如Andy Prowl所建议的,您可以做一些愚蠢的事情,比如:

// 1 Jan 1970 (no time zone)
std::tm c = { 0, 0, 0, 1, 0, 70, 0, 0, -1};

// treat it as 1 Jan 1970 (your system's time zone) and get the
// number of seconds since your system's epoch (leap seconds may
// or may not be included)
std::time_t l = std::mktime(&c);

// get a calender time for that time_point in UTC. When interpreted
// as UTC this represents the same calendar date and time as the
// original, but if we change the timezone to the system TZ then it
// represents a time offset from the original calendar time by as
// much as UTC differs from the local timezone.
std::tm m = *std::gmtime(&l);

// Treat the new calendar time as offset time in the local TZ. Get
// the number of seconds since the system epoch (again, leap seconds
// may or may not be counted).
std::time_t n = std::mktime(&m);

l -= (n-l); // subtract the difference

l现在应该表示自1970年1月1日UTC以来(错误的)秒数。只要系统时区的系统纪元和1970年1月1日之间没有闰秒,或者与系统纪元相同量的时间内没有闰秒,那么任何计算出的闰秒都应该被抵消,l 就会像Unix时间戳一样错误。


另一个选择是使用一个不错的日期库,比如Howard Hinnant's chrono::date。(Howard Hinnant 是 C++11 的 <chrono> 库的开发人员之一。)

auto now = system_clock::now();
sys_days today = time_point_cast<days>(now);
system_clock::time_point this_morning = today;

sys_days unix_epoch = day(1)/jan/1970;
days days_since_epoch = today - unix_epoch;

auto s = now - this_morning;

auto tz_offset = hours(0);
int unix_timestamp = (days_since_epoch + s + tz_offset) / seconds(1);

如果您想处理闰秒,Howard Hinnant还提供了一个库,其中包括处理闰秒的设施以及解析时区数据库作为闰秒数据源的功能。


1
请参见上面的评论中的讨论。 - Daniel
2
Howard Hinnant的日期类似乎并没有被专门提出,但委员会似乎正在研究标准化某种日期格式。请参见[链接](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3344.pdf)。 - John Schug
Howard Hinnant的链接现在已经失效。 - Eric
1
我的日期库也基于Unix时间(chrono::system_clock的每个实现都是如此)。换句话说,它也不考虑闰秒。总之,计算机通常将闰秒视为系统时间的常见修正,而不是自1970年元旦以来的秒数增加。 - Howard Hinnant
1
我认为这不是一个愚蠢的问题。在两个系统之间交换日历日期/时间非常有用:这两个系统必须就表示方式达成一致(例如自纪元以来的秒数),但C++并不提供这样的保证。特别地,time()函数并没有指定其返回值的格式。 - Julien-L
显示剩余3条评论

24

这个 C++11 实现怎么样?

auto microsecondsUTC = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();

伟大的现代解决方案! - ipid
2
优秀的解决方案 - 我喜欢将其除以1000000.0,以获得作为双精度浮点数的秒数。根据我的估计,在双精度浮点数的尾数中给出2^52位精度的情况下,这将具有微秒分辨率,直到2112年9月17日。 - Techniquab
time_since_epoch() 不能保证是 Unix 纪元。这个解决方案肯定会在“几乎所有地方”运行……“几乎”。 - Gabriele Giuseppini
1
@GabrieleGiuseppini 在C++20中已经保证是Unix纪元时间。请参考https://en.cppreference.com/w/cpp/chrono/system_clock - Marcin Tarsier
@MarcinTarsier,确实 - 这是C++20中的一个不错的改进! - Gabriele Giuseppini

8

长话短说,这是我用来获取Unix时间戳的函数(自1970年1月1日UTC开始计算的秒数):

static uint64_t getUnixTimeStamp(const std::time_t* t = nullptr)
{
    //if specific time is not passed then get current time
    std::time_t st = t == nullptr ? std::time(nullptr) : *t;
    auto secs = static_cast<std::chrono::seconds>(st).count();
    return static_cast<uint64_t>(secs);
}

这个函数的优点是默认参数值会给你当前时间。但是如果你想将某个特定的时间转换为Unix时间戳,也可以这么做。


1
在Windows上使用unsigned long会失败。将其更改为unsigned long long可以正常工作。 - atkawa7
你说得对。我之前更新了我的函数,现在也已经在这个答案中更新了。 - Shital Shah
请注意,Unix时间戳是以秒为单位而不是毫秒。代码返回正确的结果(以秒为单位),因为强制转换根本不起作用。实际上,您只是将time_t转换为uint64_t。作为副作用,您将当前时间设置为参数t中。 - VolkA
@VolkA - 很好的观点!我已经更新了答案。但是我认为需要进行强制类型转换,将 long long 转换为 uint64_t - Shital Shah
这并没有回答问题啊?这将time_t转换为时间戳,而不是chrono::time_point - scry

2
您可以使用mktime()将以tm结构编码的所需日期转换为一个本地时间time_t值。
如果您需要一个UTC时间,则使用gmttime()将该time_t值转换为UTC tm结构,并从输出中找出哪个tm结构在输入给mktime()时会产生所需的UTCtime_t
有点复杂,但希望能起作用或至少提供一些提示。

好的,我的当前实现看起来有点不同(http://liveworkspace.org/code/1OVIQ7$0)。我仍在寻找一个纯C++11的解决方案。如果没有人提出更好的解决方案,我会在几个小时内接受这个方案。 - Daniel
@Daniel 这会得到当前时区自1970年1月1日00:00:00以来的时间。你需要的是自1970年1月1日00:00:00 UTC以来的时间。 - bames53

1
我知道time_point有一个time_since_epoch()方法,但不能保证它与UNIX纪元(1970年1月1日UTC 00:00:00)相同。
从C++20和P0355R7开始,当std::chrono::time_point是std::chrono::system_clock时,std::chrono::time_point::time_since_epoch可以保证为UNIX纪元。

C++20与此无关。 - borune

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接