【问题标题】:mktime function of LIBC returns different values for the same inputLIBC 的 mktime 函数为相同的输入返回不同的值
【发布时间】:2014-08-19 02:36:27
【问题描述】:

我们知道时区 UTC+8 有一些时钟变化。例如,在 1928 年 1 月 1 日 00:00:00,时钟倒转 0:05:52 到 1927 年 12 月 31 日 23:54:08。
此外,在 1940-1941 年和 1986-1991 年使用夏令时。当我在 linux 下使用这些日期测试函数 mktime 时,我有不同的返回值。代码如下:

#include <stdio.h>
#include <string.h>
#include <time.h>
int main(int argc, char *argv[])
{
    struct tm timeinfo;
    memset(&timeinfo, 0, sizeof(timeinfo));

    while(fscanf(stdin, "%d%d%d%d%d%d",
            &timeinfo.tm_year, &timeinfo.tm_mon, &timeinfo.tm_mday,
            &timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec) != EOF)
    {
        timeinfo.tm_year -= 1900;
        timeinfo.tm_mon -= 1;
        fprintf(stdout, "%lld\n", mktime(&timeinfo));
    }

    return 0;
}

以测试输入输出为例,同样的输入“1940 6 2 23 59 59”和“1940 6 3 1 0 0”会根据调用顺序不同返回值不同:

1940 6 2 23 59 59
-933494401    
1940 6 3 1 0 0
-933490800
1940 6 3 1 0 0
-933494400
1940 6 2 23 59 59
-933498001
1940 6 2 23 59 59
-933494401

相同的输入 1940 6 3 1 0 0

这是为什么呢?为什么mktime的返回值根据调用顺序不同?

我已经阅读了mktime 的某些版本的源代码,但没有找到任何可能导致此问题的代码部分。

谁能解释为什么会这样?非常感谢。

新增案例:

1927 12 31 23 54 8
-1325491552
1927 12 31 23 54 7
-1325491905
1927 12 31 23 54 8
-1325491904
1928 1 1 0 0 0
-1325491200
1927 12 31 23 54 8
-1325491552

【问题讨论】:

  • 建议:最好使用while(fscanf(stdin, "%d%d%d%d%d%d", ...) == 6)
  • 在调用mktime(&amp;timeinfo)之前,使用timeinfo.tm_isdst = -1(DST不可用)来避免继承之前的值。只有tm_wdaytm_yday 可以保持未初始化状态。
  • 尝试"%lld\n" --> "%ld\n" 并检查是time_t 类型long long int
  • @BLUEPIXY 是正确的,考虑fprintf(stdout, "%lld\n", (long long) mktime(&amp;timeinfo)); 以消除潜在的printf() 问题。
  • "为什么mktime的返回值不同,取决于调用顺序?"因为需要的7个字段中只有6个被初始化。

标签: c++ c timezone glibc mktime


【解决方案1】:

您从 timeinfo.tm_isdst 设置为 0 开始,这会要求 mktime 将时间视为非 DST。

mktime 然后将规范化传入的struct tm,以便字段处于适当的范围内;该过程的一部分将根据 DST 在指定时间是否实际生效来调整 DST 标志。 (请参阅Documentation。)如果 DST 生效,它将将该标志设置为正值,并相应地调整 struct tm 的其他字段。

循环的后续迭代将覆盖传递给fscanf 的六个字段,但不会覆盖 DST 字段。因此,如果循环的较早迭代导致该标志被设置,则稍后的迭代仍将设置该标志。因此,您实际上并没有将相同的时间传递给mktime,它会返回不同的结果。

从您打印的内容来看,情况似乎是:

1940 6 2 23 59 59  // tm_isdst == 0, asks mktime to consider this non-DST, and DST not in effect at this time
-933494401         // tm_isdst still 0
1940 6 3 1 0 0     // tm_isdst == 0, asks mktime to consider this non-DST, but at this time DST was in effect
-933490800         // tm_isdst now positive
1940 6 3 1 0 0     // tm_isdst > 0, asks mktime to consider this DST, and DST was actually in effect
-933494400         // tm_isdst still positive
1940 6 2 23 59 59  // tm_isdst > 0, asks mktime to consider this DST, but actually DST wasn't in effect
-933498001         // tm_isdst becomes 0
1940 6 2 23 59 59  // tm_isdst == 0, asks mktime to consider this non-DST
-933494401         // tm_isdst still 0

Demo.

【讨论】:

  • 这可以解释第一个和第二个不同,但不能解释第二个和第三个调用。 DST 字段已经设置为 mktime 设置的任何值。此外,我不确定我是否相信你关于 mktime 调整它的输入。我会是一个非常糟糕的设计。
  • @EvanDark 如果您不相信我,请自行查阅文档。
  • @T.C.这真的很有帮助。非常感谢。我有一些无法解释的案例。文中添加了这些案例
  • @ZiyuChen 我无法重现这个,但首先打印出struct tmmktime 调用前后的所有字段,看看是否有任何变化。
  • @T.C.当我尝试这些情况时,mktime 调用前后的 tm_isdst 字段、tm_year、..、tm_sec 字段没有改变。 1927年上海的时钟变化与夏令时无关。 This page 可能有用。
【解决方案2】:

问题可能不在于mktime,而在于sscanf。我无法测试它,因为对我来说你的例子有效(我不确定这是正确的时间,但它是一致的)但很可能scanf 读取的数字比它应该的少,这样输入来自前一行与当前行混在一起。

fscanf 已知行尾有问题,因此检查返回值(应该是6)值尝试将fgets 逐行读取到缓冲区中,然后使用sscanf

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多