C语言 系统调用mktime忽略tm_isdst标志

k97glaaz  于 2023-08-03  发布在  其他
关注(0)|答案(2)|浏览(143)

另一个关于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

irlmq6kh

irlmq6kh1#

我想我现在可以看到人们如何发现这一点令人困惑。将mktime()视为具有签名

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

字符串
其中time_t结果是基于(归一化的)*src计算的,并且归一化的字段以及当时是否应用夏令时被保存到*dst
只是C语言开发人员在历史上选择只使用一个指针,将srcdst组合在一起。但上述逻辑仍然成立。
请参阅man mktime手册页,尤其是以下部分:
函数的作用是:将分解的时间结构(表示为本地时间)转换为日历时间表示。该函数忽略调用方在tm_wday和tm_yday字段中提供的值。tm_isdst字段中指定的值通知mktime()夏令时(DST)是否在tm结构中提供的时间内生效:正值表示DST有效; 0表示DST未生效;负值意味着mktime()应该(使用时区信息和系统数据库)尝试确定DST是否在指定时间生效。
mktime()函数修改tm结构的字段如下:tm_wday和tm_yday被设置为根据其它字段的内容确定的值;如果结构成员超出其有效间隔,则将对其进行规范化(例如,将10月40日更改为11月9日); tm_isdst分别被设置(不管其初始值如何)为正值或0,以指示DST在指定时间是否生效。调用mktime()还将使用有关当前时区的信息设置外部变量tzname。
如果指定的分解时间不能表示为日历时间(从Epoch开始的秒数),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也有,但没有检查)。

aij0ehis

aij0ehis2#

成功完成后,结构的tm_wdaytm_yday组件的值将被适当设置,其他组件将被设置为表示指定的日历时间,... C11dr §7.27.2.3 2
调用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);

字符串

相关问题