系统调用 mktime 忽略 tm_isdst 标志

问题描述 投票:0回答:2

另外一个关于 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
c linux unix posix mktime
2个回答
1
投票

我想我现在可以明白人们会如何感到这令人困惑了。将

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 也有,但尚未检查)。


1
投票

成功完成后,结构的

tm_wday
tm_yday
组件的值将被适当设置,并且 其他组件 将被设置为表示指定的日历 时间,... C11dr §7.27.2.3 2

调用

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);
© www.soinside.com 2019 - 2024. All rights reserved.