如何在c++中使用time_t在epoch之前计算时间差

How to calculate time differences in C++ with time_t before the epoch?

本文关键字:epoch 计算 时间差 time c++      更新时间:2023-10-16

我想用这个简单的程序来计算两个日期之间的秒差。

time_t referenceDate;
time_t dateNow = time(0);
struct tm referenceDateComponent = {0};
referenceDateComponent.tm_hour = 0;
referenceDateComponent.tm_min = 0;
referenceDateComponent.tm_sec = 0;
referenceDateComponent.tm_year = 89;
referenceDateComponent.tm_mon = 11;
referenceDateComponent.tm_mday = 31;
referenceDate = mktime(&referenceDateComponent);  
long seconds = difftime(dateNow, referenceDate);

上面的代码应用程序工作正常,但如果尝试设置tm.year为负(构建1900年之前的日期),mktime()函数返回-1

我知道time_t类型只管理从1970年1月1日开始的日期UTC根据文档:

由于历史原因,它通常被实现为一个整数值,表示从UTC时间1970年1月1日00:00开始经过的秒数(即unix时间戳)。尽管库可以使用其他时间表示来实现此类型。

我知道还有Boost库,但对我来说不是一个可用的解决方案。

所以我的问题是,有没有办法从1970年之前的日期得到秒差?

我建议使用c++ 11 std::chrono命名空间和<chrono>标准头以及其中的标准函数和类。

您还可以考虑使用C标准中的difftime和localtime &mktime

还有很多很好的理由至少升级到c++ 11(或者如果可以的话,升级到c++ 14)。最近几个不错的自由软件编译器GCC和Clang/LLVM都支持这个标准(如果你想要GNU扩展,可以用-std=c++11-std=gnu++14编译)。c++ 14)

顺便说一句,你的问题比你想象的要复杂得多。日历变了。儒略历/公历的过渡发生在XX世纪的俄罗斯。我已故的母亲于1919年出生,当时正值移民和内战时期,出生在一个法律制度备受争议的俄罗斯村庄(俄国革命并没有立即发生)。她有一些论文提到13 the december 1919,而其他论文提到26 the december 1919,指的是两个不同日历中的同一日期。你的软件会如何处理这种情况?我甚至不确定时区是否足够!

顺便说一句,我不确定Boost或c++ 11 <chrono>能否可靠地处理这样的日历问题。

nwp在评论中提到了一个非常好的电脑爱好者视频:时间问题& &;时区。

你已经回答了这个问题。time_t表示自UNIX纪元以来的秒数,而不是自此之前的任意时间以来的秒数。你想做的事根本没有意义。

如果你被c++ 03困住了,不管你对什么能用什么不能用的模棱两可的声明,你将不得不使用Boost.DateTime

否则,标准库在<chrono>中有一些很好的现代计时功能。

当您尝试使用time_t进行日历运算时,您自然要担心time_t的类型和表示,这当然是实现定义的。它几乎都是有符号的整型。在Unix和现代MacOS系统上,它是自1970年以来的第二次,为了兼容性,我认为它可能也会在Windows上这样使用。它趋向于32位。把所有这些放在一起,它通常可以表示1901年12月13日至2038年1月18日之间的日期。

当我把你代码中的tm_year行改为

referenceDateComponent.tm_year = 60;

代码工作并打印1721200226,这大约是19921天或54.5年,这正是1960年12月31日与今天的差。

但是如果你将tm_year设置为负数,你就会要求一个1900年之前的日期,而使用我们已经讨论过的time_t的典型定义是行不通的。

(的确,time_t还有其他的可能性。也可以是浮点数。它可以是无符号的而不是有符号的。它可能是64位类型,这意味着它的范围几乎是600,000,000,000年,顺便说一句,这超过了32位tm_year所能容纳的范围。

因此,尽管这里有几个反对者告诉你不要这样做,尽管肯定有很多与时区、闰秒和公历以外的日历有关的模糊困难,但你通常可以使用time_t对20世纪和本世纪的日期进行基本的日历数学计算,直到2038年,那时臭名昭著的"Y2.038K问题"将会发生。(我担心,它会比不那么臭名昭著的千年虫问题更严重,但那是另一个故事。)

我说过,你的代码在1970年之前对我有效。以下是我建议用于简单的基于time_t的日期计算的方法(前面已经提到过的注意事项):

time_t makedate(int year, int month, int day)
{
    struct tm tm = {0};
        tm.tm_hour = 12;
    tm.tm_min = tm.tm_sec = 0;
    tm.tm_year = year - 1900;
    tm.tm_mon = month - 1;
    tm.tm_mday = day;
    tm.tm_isdst = -1;
    return mktime(&tm);
}
int main()
{
    long int d = difftime(makedate(2015, 7, 17), makedate(2015, 6, 1));
    printf("%ld sec = %ld days = %.2f yearsn", d, d/86400, d/86400./365.25);
    d = difftime(makedate(2015, 7, 17), makedate(2014, 7, 17));
    printf("%ld sec = %ld days = %.2f yearsn", d, d/86400, d/86400./365.25);
    d = difftime(makedate(1975, 7, 17), makedate(1965, 7, 17));
    printf("%ld sec = %ld days = %.2f yearsn", d, d/86400, d/86400./365.25);
    d = difftime(makedate(1950, 1, 11), makedate(1925, 1, 1));
    printf("%ld sec = %ld days = %.2f yearsn", d, d/86400, d/86400./365.25);
    d = difftime(makedate(2030, 12, 31), makedate(2025, 12, 31));
    printf("%ld sec = %ld days = %.2f yearsn", d, d/86400, d/86400./365.25);
}

就像您的代码一样,这利用了令人惊讶的强大的mktime函数,并且可以做它能做的一切。它能处理闰年,没问题。它不处理闰秒或日历更改。

如果,如你所说,你对1900年以前的日期感兴趣,恐怕你运气不好。在大多数系统上,time_t根本无法表示这些日期,因此您将不得不寻求其他解决方案。

可以使用公历400年的周期来处理纪元之前的日期。(当然,您需要注意您感兴趣的国家的公历开始时间)。

在你比较的两个日期上加上400年,使它们超过1970年(纪元),用mktime()规范化,并计算与difftime()的差值:

#include <time.h>
#include <stdio.h>
double compare_dates (const struct tm *date_1, const struct tm *date_2)
{
    struct tm normalized_1 = *date_1, normalized_2 = *date_2;
    normalized_1.tm_year += 400, normalized_2.tm_year += 400;
    time_t t1 = mktime(&normalized_1);
    time_t t2 = mktime(&normalized_2);
    return difftime(t1, t2);
}
int main (void)
{
    // how many seconds between June 1st 1880 and 15th September 1892 ?
    struct tm june_1st_1880       = { .tm_mon = 6 - 1, .tm_mday =  1, .tm_year = 1880 - 1900 };
    struct tm september_15th_1892 = { .tm_mon = 9 - 1, .tm_mday = 15, .tm_year = 1892 - 1900 };
    printf("%f seconds", compare_dates(&june_1st_1880, &september_15th_1892));
}