C++ 和 mktime:26/6/1943 和 27/6/1943 是同一天
C++ and mktime: 26/6/1943 and 27/6/1943 are the same day
这是一个奇怪的情况。查看 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 日到 2030 年 12 月 31 日)。
但如果 1943 年处于日期间隔的中间,则似乎丢失了一天。第二个例子:1/1/1930 - 31/12/2030.
经过大量测试,我发现问题出在Juny/1943。第三个例子:1/6/1943 - 30/6/1943,返回 28 天而不是 29.
更具体地说,26号和27号好像是同一天。第四个示例:26/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 实现的某种错误?
在 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
分别是四个字节和八个字节,因此代码中的日期差异计算会溢出。但我没有看到您的旧版本中可能是库错误。
这是一个时区问题。在 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 之外还有其他问题,例如闰秒)。
您要做的是计算(预推)公历中两个日期之间的天数。您应该自己实现它,例如将它们转换为儒略日数字。
这不是时区问题,即使该问题不显示为 UTC。
这个程序有几个问题,但关键的一个是 tm_isdst
字段没有初始化。此字段影响 mktime
的行为,它在 tm_hour
字段中引入了调整。
使用这个特定日期 (26/6/1943, 27/6/1943)、时区 (Europe/Madrid) 和 0 作为两个 tm_isdst
字段的内容,你会得到不同的两个日期之间相隔 23 小时(82800 秒),而不是 24 小时(86400 秒)。然后,在函数 days
中,82800 除以 86400 整数除法 。结果为 0,休息日完全丢失。
请添加:
date.tm_isdst = -1;
到你的 mkdate
函数来避免这种情况,方法是告诉 mktime
在计算自纪元以来的秒数之前根据夏令时的时区检查日期。
这是一个奇怪的情况。查看 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 日到 2030 年 12 月 31 日)。
但如果 1943 年处于日期间隔的中间,则似乎丢失了一天。第二个例子:1/1/1930 - 31/12/2030.
经过大量测试,我发现问题出在Juny/1943。第三个例子:1/6/1943 - 30/6/1943,返回 28 天而不是 29.
更具体地说,26号和27号好像是同一天。第四个示例:26/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 实现的某种错误?
在 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
分别是四个字节和八个字节,因此代码中的日期差异计算会溢出。但我没有看到您的旧版本中可能是库错误。
这是一个时区问题。在 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 之外还有其他问题,例如闰秒)。
您要做的是计算(预推)公历中两个日期之间的天数。您应该自己实现它,例如将它们转换为儒略日数字。
这不是时区问题,即使该问题不显示为 UTC。
这个程序有几个问题,但关键的一个是 tm_isdst
字段没有初始化。此字段影响 mktime
的行为,它在 tm_hour
字段中引入了调整。
使用这个特定日期 (26/6/1943, 27/6/1943)、时区 (Europe/Madrid) 和 0 作为两个 tm_isdst
字段的内容,你会得到不同的两个日期之间相隔 23 小时(82800 秒),而不是 24 小时(86400 秒)。然后,在函数 days
中,82800 除以 86400 整数除法 。结果为 0,休息日完全丢失。
请添加:
date.tm_isdst = -1;
到你的 mkdate
函数来避免这种情况,方法是告诉 mktime
在计算自纪元以来的秒数之前根据夏令时的时区检查日期。