另外一个关于 mktime 和 DST 的问题
Linux、Ubuntu,时区设置为欧洲/柏林,即当前时间为 CEST:
>date
Mon Aug 22 16:08:10 CEST 2016
>date --utc
Mon Aug 22 14:08:14 UTC 2016
到目前为止一切都还好。
现在我尝试运行以下代码:
#include <stdio.h>
#include <time.h>
int main()
{
struct tm tm = {0};
int secs;
tm.tm_sec = 0;
tm.tm_min = 0;
tm.tm_hour = 12;
tm.tm_mon = 9 - 1;
tm.tm_mday = 30;
tm.tm_year = 2016 - 1900;
tm.tm_isdst = 0;
secs = mktime(&tm);
printf("%i\n", secs);
tm.tm_isdst = 1;
secs = mktime(&tm);
printf("%i\n", secs);
tm.tm_isdst = -1;
secs = mktime(&tm);
printf("%i\n", secs);
return 0;
}
并得到
1475233200
1475233200
1475233200
这三种情况都是错误的(偏移 1 小时):
>date -d @1475233200
Fri Sep 30 13:00:00 CEST 2016
所以我现在有点困惑,我的时区是否被破坏了?为什么 tm_isdst 标志被完全忽略?
编辑:@Nominal Animal 给出了答案:mktime 修改 tm_hour!我想知道它在哪里记录?!
#include <stdio.h>
#include <time.h>
void reset(struct tm* tm){
(*tm) = (const struct tm){0};
tm->tm_sec = 0;
tm->tm_min = 0;
tm->tm_hour = 12;
tm->tm_mon = 9 - 1;
tm->tm_mday = 30;
tm->tm_year = 2016 - 1900;
}
int main()
{
struct tm tm;
int secs;
reset(&tm);
tm.tm_isdst = 0;
secs = mktime(&tm);
printf("%i\n", secs);
reset(&tm);
tm.tm_isdst = 1;
secs = mktime(&tm);
printf("%i\n", secs);
reset(&tm);
tm.tm_isdst = -1;
secs = mktime(&tm);
printf("%i\n", secs);
return 0;
}
给予
1475233200
1475229600
1475229600
我想我现在可以明白人们会如何感到这令人困惑了。将
mktime()
视为具有签名
time_t mktime_actual(struct tm *dst, const struct tm *src);
其中
time_t
结果是根据(归一化)*src
计算的,归一化字段以及当时是否适用夏令时,保存到 *dst
。
只是 C 语言开发人员历史上选择仅使用一种指针,结合了
src
和 dst
。不过,上述逻辑仍然成立。
man mktime
手册页,尤其是这部分:
mktime() 函数转换分解的时间结构, 以当地时间表示,以日历时间表示。这 函数忽略调用者在 tm_wday 中提供的值和 tm_yday 字段。 tm_isdst 字段中指定的值通知 mktime() 夏令时 (DST) 是否有效 tm 结构中提供的时间:正值表示 DST 为 有效;零表示 DST 未生效;和一个负值 意味着 mktime() 应该(使用时区信息和系统 数据库)尝试确定 DST 是否在以下时间生效: 指定时间。
mktime()函数将tm结构的字段修改为 如下: tm_wday 和 tm_yday 设置为由 其他字段的内容;如果结构成员在其范围之外 有效间隔,它们将被标准化(例如,40 10月改为11月9日); tm_isdst 已设置(无论 其初始值)分别为正值或 0 指示 DST 在指定时间是否生效。 调用 mktime() 还会设置外部变量 tzname 有关当前时区的信息。
如果指定的故障时间无法用日历表示 时间(自纪元以来的秒数),mktime() 返回 (time_t) -1 并执行 不改变分解时间结构的成员。
换句话说,如果你稍微改变一下你的测试程序,就说成
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
static const char *dst(const int flag)
{
if (flag > 0)
return "(>0: is DST)";
else
if (flag < 0)
return "(<0: Unknown if DST)";
else
return "(=0: not DST)";
}
static struct tm newtm(const int year, const int month, const int day,
const int hour, const int min, const int sec,
const int isdst)
{
struct tm t = { .tm_year = year - 1900,
.tm_mon = month - 1,
.tm_mday = day,
.tm_hour = hour,
.tm_min = min,
.tm_sec = sec,
.tm_isdst = isdst };
return t;
}
int main(void)
{
struct tm tm = {0};
time_t secs;
tm = newtm(2016,9,30, 12,0,0, -1);
secs = mktime(&tm);
printf("-1: %04d-%02d-%02d %02d:%02d:%02d %s %lld\n",
tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec, dst(tm.tm_isdst), (long long)secs);
tm = newtm(2016,9,30, 12,0,0, 0);
secs = mktime(&tm);
printf(" 0: %04d-%02d-%02d %02d:%02d:%02d %s %lld\n",
tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec, dst(tm.tm_isdst), (long long)secs);
tm = newtm(2016,9,30, 12,0,0, 1);
secs = mktime(&tm);
printf("+1: %04d-%02d-%02d %02d:%02d:%02d %s %lld\n",
tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec, dst(tm.tm_isdst), (long long)secs);
return EXIT_SUCCESS;
}
然后运行它会产生输出
-1: 2016-09-30 12:00:00 (>0: is DST) 1475226000
0: 2016-09-30 13:00:00 (>0: is DST) 1475229600
+1: 2016-09-30 12:00:00 (>0: is DST) 1475226000
换句话说,它的行为与所描述的完全一样(在上面的引用中)。此行为记录在 C89、C99 和 POSIX.1 中(我认为 C11 也有,但尚未检查)。
成功完成后,结构的
和tm_wday
组件的值将被适当设置,并且 其他组件 将被设置为表示指定的日历 时间,... C11dr §7.27.2.3 2tm_yday
调用
mktime(&tm)
时,tm
的原始值不受范围限制。
因为第一个
mktime(&tm)
调用,当然tm.tm_isdst
和tm.tm_hour
被调整为1和11。所以OP的以下代码tm.tm_isdst = 1;
和tm.tm_isdst = -1;
并没有影响时间戳。
最好设置所有字段进行调查。
struct tm tm0 = {0};
struct tm tm;
int secs;
tm0.tm_sec = 0;
tm0.tm_min = 0;
tm0.tm_hour = 12;
tm0.tm_mon = 9 - 1;
tm0.tm_mday = 30;
tm0.tm_year = 2016 - 1900;
tm = tm0;
tm.tm_isdst = 0;
secs = mktime(&tm);
printf("%i\n", (int) secs);
tm = tm0;
tm.tm_isdst = 1;
secs = mktime(&tm);
printf("%i\n", (int) secs);
tm = tm0;
tm.tm_isdst = -1;
secs = mktime(&tm);
printf("%i\n", (int) secs);