系统调用mktime忽略tm_isdst的标志

问题描述:

另外一个问题,关于mktime和DST系统调用mktime忽略tm_isdst的标志

的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动物有答案: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 
+1

1)行为似乎很奇怪。在调用'mktime(&tm);'为'mktime()')后,可以调整'tm'字段来重新设​​置所有**字段printf(“%i \ n”,(int)secs);'或'printf(“%lld \ n”,(long long)secs);'以避免未定义的行为。 – chux

+0

它可能看起来很奇怪,但它确实不奇怪。的确,['mktime()'](http://man7.org/linux/man-pages/man3/mktime.3.html)修改了它的论点 - 特别是'tm.tm_hour'和'tm。 tm_isdst'字段。 –

+0

@Nominal Animal什么是奇怪的是'mktime()'不会改变'tm',因为'tm'在前2个主要范围内。 _ahh_可能不在第一种情况。嗯 - 在夏季的0 dst可能会更改为1. – chux

我想我现在可以看到如何找到这个令人困惑。想的mktime()为具有签名

time_t mktime_actual(struct tm *dst, const struct tm *src); 

其中time_t结果基于(归一化)*src计算,并且归一化的领域和夏令时间是否适用在那个时候,被保存到*dst

这只是C语言开发人员历史上选择只使用一个指针,结合srcdst。但是,上述逻辑仍然存在。

`man mktime手册页,特别是这部分:

的mktime()函数将一个破旧的时间结构, 表示为本地时间,日历时间表示。 函数将忽略tm_wday和 tm_yday字段中由调用方提供的值。在tm_isdst字段中指定的值通知 mktime()夏时制时间(DST)是否生效 在tm结构中提供的时间:正值表示DST为 生效;零意味着DST不起作用;而负值 意味着mktime()应该(使用时区信息和系统 数据库)尝试确定DST是否在 指定的时间生效。

()函数修改tm结构作为 领域的mktime如下:tm_wday和tm_yday被设置为从所述其他字段的内容 确定的值;如果结构成员超出其有效时间间隔,它们将被标准化(例如,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_wdaytm_yday分量的值被适当地设定,并且被设置其他组件来代表指定的日历 时间... C11dr§7.27.2.32

致电mktime(&tm)时,tm的原始值不受范围限制。

因为第一mktime(&tm)电话,肯定tm.tm_isdsttm.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);