【问题标题】:System call mktime ignores tm_isdst flag系统调用 mktime 忽略 tm_isdst 标志
【发布时间】:2016-12-29 03:22:46
【问题描述】:

关于 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

【问题讨论】:

  • 1) 行为似乎很奇怪。为了更好地解决问题,请在调用mktime(&amp;tm); 后再次重置所有 字段(或至少打印它们),因为mktime() 可能会调整tm 字段。 2) 使用匹配的格式说明符,如 printf("%i\n", (int) secs);printf("%lld\n", (long long) secs); 以避免未定义的行为。
  • 这可能看起来很奇怪,但实际上并不奇怪。实际上,mktime() 修改了它的参数——特别是在这种情况下,tm.tm_hourtm.tm_isdst 字段。
  • @Nominal Animal 什么是oddmktime() 预计不会改变tm,因为tm 在前2 个案例中处于主要范围内。 ahh 可能不是第一种情况。嗯 - 夏季时间 0 的 dst 可能会更改为 1。
  • @chux 能否请您发布一个答案以便我接受,它似乎有效.. 这东西真的很奇怪
  • @Stasik: mktime() 总是tm_isdst 更改为 01。该行为在man 3 mktime 手册页以及更缩写形式in POSIX.1 中进行了描述(正如我在回答中解释的那样)。

标签: c linux unix posix mktime


【解决方案1】:

我想我现在可以看到人们会如何感到困惑。将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 是否在 指定时间。

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

【讨论】:

    【解决方案2】:

    成功完成后,适当设置结构的tm_wdaytm_yday组件的值,并设置其他组件代表指定的日历 时间,... C11dr §7.27.2.3 2

    调用mktime(&amp;tm)时,tm的原始值不受范围限制。

    由于第一次调用mktime(&amp;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);
    

    【讨论】:

      猜你喜欢
      • 2012-02-09
      • 2011-09-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-15
      • 2013-07-07
      相关资源
      最近更新 更多