C ++和mktime:26/6/1943和27/6/1943是同一天

时间:2015-05-12 13:01:18

标签: c++ ctime

这是一个奇怪的情况。查看this Coliru code

#include <iostream>
#include <utility>
#include <ctime>

using namespace std;

#define SEGS 60
#define MINS 60
#define HOURS 24

int days(tm* date1, tm* date2)
{ return (mktime(date1) - mktime(date2)) / SEGS / MINS / HOURS; }

tm mkdate(int day, int mon, int year)
{
    tm date = {0, 0, 0};

    date.tm_mday = day;
    date.tm_mon = mon - 1;
    date.tm_year = year - 1900;

    return date;
}

int main()
{
    tm date1 = mkdate(31, 12, 2030);
    tm date2 = mkdate(1, 1, 2000);

    cout << days(&date1, &date2) << endl;
    // 11322... OK 30 * 365  (1/1/2000 - 1/1/2030)
    // + 8 (leap years) + 364 (from 1/1/2030 - 31/12/2030).

    date1 = mkdate(31, 12, 2030);
    date2 = mkdate(1, 1, 1930);
    cout << days(&date1, &date2) << endl;
    // 36889... OK; but in my machine, it returns 36888.

    date1 = mkdate(31, 12, 1943);
    date2 = mkdate(1, 1, 1943);
    cout << days(&date1, &date2) << endl;
    // 364... OK: but in my machine, it returns 363.

    date1 = mkdate(30, 6, 1943);
    date2 = mkdate(1, 6, 1943);
    cout << days(&date1, &date2) << endl;
    // 29... OK; but in my machine, it returns 28.

    date1 = mkdate(27, 6, 1943);
    date2 = mkdate(26, 6, 1943);
    cout << days(&date1, &date2) << endl;
    // 1... OK; but in my machine, it returns 0.

    return 0;
}

每个例子之后的评论都来自Coliru和我的笔记本电脑。 Coliru输出是纠正的,但我的机器打印错误的数字。

如果您阅读了代码,则会正确计算天数之间的差异(第一个示例,从2000年1月1日到2010年12月31日)。

但如果1943年处于日期间隔的中间,似乎有一天会失去。第二个例子:1/1/1930 - 31/12/2030。

经过大量测试,我发现问题出现在Juny / 1943。第三个例子:1943年1月6日 - 1943年3月30日,返回28天而不是29天。

更具体地说,似乎第26天和第27天是同一天。第四个例子:19/6/1943 - 27/6/1943,返回0天。

我的机器是Ubuntu 14.02.2 LTS,Linux 3.13.0-52-generic x86_64,使用gcc(g ++)4.8.2。

问题是什么来的? GNU libc实现的某种bug?

3 个答案:

答案 0 :(得分:3)

这是一个时区问题。在 main()

的开头添加它
// set the timezone to UTC
setenv("TZ", "", 1);
tzset();

此外,您的 days()函数应如下所示:

double days (tm *date1, tm *date2) {
  return difftime (mktime(date1), mktime(date2)) / 86400;
}

根本原因是time_t表示自UTC时期以来的秒数,而mktime()将其参数解释为本地时区中的日期。因此,它必须将其转换为UTC以生成time_t。因此,如果在本地时区的两个日期之间存在相对于UTC的不连续性,则结果可能是错误的。

真正的解决方案不是使用mktime()。它不适用于您的应用程序(除了DST之外还有其他问题,如闰秒)。

您要做的是计算(预感)格里高利历中两个日期之间的天数。您应该自己实现,例如将它们转换为朱利安日数。

答案 1 :(得分:1)

这是时区问题,即使问题没有显示在UTC上。

此程序存在一些问题,但关键的一个问题是tm_isdst字段未初始化。此字段会影响mktime的行为,这会在tm_hour字段中引入调整。

根据这个特定日期(19/6/1943,27 / 6/1943),时区(欧洲/马德里)和0作为tm_isdst两个字段的内容,您将获得23小时的差异(82800) s)两个日期之间而不是24小时(86400秒)。然后,在函数days中,82800被86400除以整数除法。结果为0,休息日完全丢失。

请注意,只需添加:

date.tm_isdst = -1;

到你的mkdate函数以避免这种情况,通过告诉mktime在计算自纪元以来的秒数之前检查DST时区的日期。

答案 2 :(得分:0)

在Ubuntu 15.04,32位,我得

edd@don:/tmp$ ./mkdate 
11322
-12821
364
29
1
edd@don:/tmp$ 

在Ubuntu 15.04上,64位,我得

edd@max:/tmp$ ./mkdate 
11322
36889
364
29
1
edd@max:/tmp$ 

在这两种情况下都是g ++ 4.9.2。差异是由于time_t分别为4和8个字节,因此代码中的日期差异计算会出现溢出。但我不知道你的旧版本中可能存在库错误。