如何比较两个 tm(来自 ctime)变量

How to compare two tm (from ctime) variables

我刚开始学C++。

我正在使用 Windows 7 Ultimate x64 Visual Studio 版本:

Microsoft Visual Studio Enterprise 2017 
Version 15.9.12
VisualStudio.15.Release/15.9.12+28307.665
Installed Version: Enterprise

Visual C++ 2017   00369-90013-89248-AA631
Microsoft Visual C++ 2017

Visual Studio Tools for CMake   1.0
Visual Studio Tools for CMake

我正在尝试比较这两个日期:

1582 October 15
2009 June 19 18:00

因为我想知道一个日期是否晚于1582年10月15日

要做到这一点,我有这段代码:

#include <ctime>
tm date = { 0, 0, 18, 19, 5, 109, 0, 0, 0 };
tm gregorian = { 0, 0, 0, 15, 9, (1582 - 1900), 0, 0, 0};

double aux = std::difftime(std::mktime(&date), std::mktime(&gregorian));

但是 aux 等于 0.0。我认为它一定不等于零。

是我做错了什么还是结果是正确的?

一旦您使用 较旧的 日期,您应该小心使用日期和时间函数。 年长者 可能在 1970-01-01. And if you go back to the 1582-10-15, that is the entry in force of the Gregorian calendar 前一天就开始了,因此需要格外小心。

有什么问题吗?

在您的代码中,mktime() 将日期转换为 time_t,然后您可以使用它来区分两个日期。根据 C 标准:

The range and precision of times representable in clock_t and time_t are implementation-defined.

通常,time_t is expressed in the number of elapsed seconds since 1970-01-01. This requires to know (or assume) the timezone of the date and time (by default 0:00:00), and to have a perfect understanding not only of the leap years but also the leap seconds

mktime() returns 如果日期无效或无法在实现支持的 time_t 范围内转换,则错误代码为 -1。因此,您的代码的更安全版本将检查潜在错误:

std::time_t th = std::mktime(&date); 
std::time_t tl = std::mktime(&gregorian); 
if (th==-1 || tl==-1) {
    std::cout<<"At least one of the date couldn't be converted"<<std::endl;
}
else {
    double aux = std::difftime(th, tl);
    std::cout << aux<< " "<< aux/3600.0/24.0/365.25 <<std::endl; 
}

Online demo with an implementation on which it works well. For MSVC, the conversion fails for any date before 1970-01-01. It's documented here

有更好的解决方案吗?

现在,如果您回到 1582-10-15,将其转换为用于比较两个旧日期的精确秒数就没有多大意义了。
因此,您可能需要考虑 boost::gregorian::date。该库能够处理从 1400-Jan-01 到 9999-Dec-31 的日期。它将日期转换为天数,而不是转换为秒数。

然后您可以可靠地比较日期,如 1582-10-15 和 1582-10-14,通过获取天数差异:

using namespace boost::gregorian;
date d0(1582, 10, 15);
date d1(1582, 10, 25); 
date d2(1582, 10, 14); 

std::cout << d1-d0 << std::endl;
std::cout << d2-d0 << std::endl;

为什么旧日期如此复杂?

有趣的是,在上面的例子中你会看到BOOST会进行理论计算。它将找到 1582-10-14 和 1582-10-15 (demo) 之间的一天差异。

当然,我们都知道这是荒谬的,因为 introduction of the Gregorian calendar 导致 1582 年 10 月 4 日紧随其后的是 1582 年 10 月 15 日。

另请注意,其他日期也存在此类日历不一致的情况,具体取决于国家/地区。以英国为例,根据维基百科,1752 年 9 月 2 日星期三之后是 1752 年 9 月 14 日星期四。

最后,如果您比较公历之前的日期,则需要接受歧义。以 1492 年 10 月 12 日为例,即哥伦布踏足美洲的那一天。它实际上是儒略历中的一个日期。在公历it would in reality be 21 October 1492

还有更简单的选择吗?

现在,time_t 有点像传统。 C++标准指的是相关函数定义的C标准。

现代 C++ 提供 std::chronostd::chrono::time_point 类型。问题是目前没有可用的函数来转换日历和从日历转换:这些只会随 C++20 一起提供。

但是,如果您只想比较两个旧日期,假设没有日历不一致,您可以只比较年份,如果相等,则比较月份,如果相等,则比较日期。我知道这听起来很荒谬,但无论您的 C++ 版本和实现是什么,这都可以毫无问题地工作。