【问题标题】:Why do timelocal and mktime fail to handle daylight savings correctly?为什么 timelocal 和 mktime 无法正确处理夏令时?
【发布时间】:2017-02-17 23:07:43
【问题描述】:

我试图了解 UNIX timelocal 和 mktime 是如何工作的。假设当您在 struct tm tm_isdst 字段中传递正确的值时,它们会处理夏令时。

我正在测试一个非常具体的时刻。根据“America/New_York”的时区数据库,夏令时在 2005 年 10 月 30 日 01:00 发生了变化。这是zdump -v America/New_York 的输出,您可以在自己的系统上确认。我只显示了 2005 年左右的数据子集(向右滚动查看 gmtoff 值):

America/New_York Sun Apr 3 06:59:59 2005 UT = Sun Apr 3 01:59:59 2005 EST isdst=0 gmtoff=-18000 美国/纽约 2005 年 4 月 3 日星期日 07:00:00 UT = 2005 年 4 月 3 日星期日 03:00:00 EDT isdst=1 gmtoff=-14400 美国/纽约 2005 年 10 月 30 日星期日 05:59:59 UT = 2005 年 10 月 30 日星期日 01:59:59 EDT isdst=1 gmtoff=-14400 America/New_York Sun Oct 30 06:00:00 2005 UT = Sun Oct 30 01:00:00 2005 EST isdst=0 gmtoff=-18000 America/New_York Sun Apr 2 06:59:59 2006 UT = Sun Apr 2 01:59:59 2006 EST isdst=0 gmtoff=-18000 美国/纽约 2006 年 4 月 2 日星期日 07:00:00 UT = 2006 年 4 月 2 日星期日 03:00:00 EDT isdst=1 gmtoff=-14400

为了测试这种转换,我设置了一个 struct tm 以包含特定日期的 01:30。如果我为tm_isdst 传递0,它应该给我一个-18000 的gmtoffset。如果我通过 1 并启用夏令时,那么 gmtoffset 应该是 -14400。

这是我用来在 Darwin/OSX 和 FreeBSD 上测试的代码:

#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>


void print_tm(struct tm* tm) {
  printf("tm: sec [%d] min [%d] hour [%d] mday [%d] mon [%d] year [%d] wday [%d] yday [%d] isdst [%d] zone [%s] gmtoff [%ld]\n",
  tm->tm_sec,
  tm->tm_min,
  tm->tm_hour,
  tm->tm_mday,
  tm->tm_mon + 1,
  tm->tm_year,
  tm->tm_wday,
  tm->tm_yday + 1,
  tm->tm_isdst,
  tm->tm_zone,
  tm->tm_gmtoff);
  }

  struct tm* set_tm(int sec, int min, int hour, int mday, int mon, int year, int wday, int yday, int isdst, int gmtoff, char* zone) {
    struct tm* tm;

    tm = malloc(sizeof(struct tm));
    memset(tm, 0, sizeof(struct tm));

    tm->tm_sec = sec;
    tm->tm_min = min;
    tm->tm_hour = hour;
    tm->tm_mday = mday;
    tm->tm_mon = mon - 1;
    tm->tm_year = year;
    tm->tm_wday = wday;
    tm->tm_yday = yday - 1;
    tm->tm_isdst = isdst;
    tm->tm_zone = zone;
    tm->tm_gmtoff = gmtoff;

    return tm;
  }

  void test_timelocal(struct tm* tm, int isdst) {
    time_t seconds = -1;

    if(!setenv("TZ", "America/New_York", 1)) {
      printf("isdst is [%d]\n", isdst);
      tm->tm_isdst = isdst;

      tzset();
      seconds = timelocal(tm);

      localtime_r(&seconds, tm);
      print_tm(tm);
    } else {
      printf("setenv failed with [%s]\n", strerror(errno));
    }

    printf("\n");
  }

  void test_mktime(struct tm* tm, int isdst) {
    time_t seconds = -1;

    if(!setenv("TZ", "America/New_York", 1)) {
      printf("isdst is [%d]\n", isdst);
      tm->tm_isdst = isdst;

      tzset();
      seconds = mktime(tm);

      localtime_r(&seconds, tm);
      print_tm(tm);
    } else {
      printf("setenv failed with [%s]\n", strerror(errno));
    }

    printf("\n");
  }

int main(void) {
  struct tm* tm;

  printf("Test with timelocal\n");
  tm = set_tm(0, 30, 1, 30, 10, 2005, 0, 0, 0, 0, "");
  test_timelocal(tm, 0);

  tm = set_tm(0, 30, 1, 30, 10, 2005, 0, 0, 0, 0, "");
  test_timelocal(tm, 1);

  tm = set_tm(0, 30, 1, 30, 10, 2005, 0, 0, 0, 0, "");
  test_timelocal(tm, -1);


  printf("Test with mktime\n");
  tm = set_tm(0, 30, 1, 30, 10, 2005, 0, 0, 0, 0, "");
  test_mktime(tm, 0);

  tm = set_tm(0, 30, 1, 30, 10, 2005, 0, 0, 0, 0, "");
  test_mktime(tm, 1);

  tm = set_tm(0, 30, 1, 30, 10, 2005, 0, 0, 0, 0, "");
  test_mktime(tm, -1);

  return 0;
}

在不同的操作系统上运行它会产生不同的结果。在 FreeBSD 上,此代码输出(向右滚动查看 gmtoffset 值):

使用 timelocal 进行测试 isdst 是 [0] tm: sec [0] min [30] hour [1] mday [30] mon [10] year [2005] wday [1] yday [303] isdst [1] zone [EDT] gmtoff [-14400] isdst 是 [1] tm: sec [0] min [30] hour [1] mday [30] mon [10] year [2005] wday [1] yday [303] isdst [1] zone [EDT] gmtoff [-14400] isdst 是 [-1] tm: sec [0] min [30] hour [1] mday [30] mon [10] year [2005] wday [1] yday [303] isdst [1] zone [EDT] gmtoff [-14400] 使用 mktime 进行测试 isdst 是 [0] tm: sec [0] min [30] hour [2] mday [30] mon [10] year [2005] wday [1] yday [303] isdst [1] zone [EDT] gmtoff [-14400] isdst 是 [1] tm: sec [0] min [30] hour [1] mday [30] mon [10] year [2005] wday [1] yday [303] isdst [1] zone [EDT] gmtoff [-14400] isdst 是 [-1] tm: sec [0] min [30] hour [1] mday [30] mon [10] year [2005] wday [1] yday [303] isdst [1] zone [EDT] gmtoff [-14400]

在 darwin/OSX 上,完全相同的代码会产生这个(向右滚动查看 gmtoffset 值):

使用 timelocal 进行测试 isdst 是 [0] tm: sec [0] min [30] hour [1] mday [30] mon [10] year [2005] wday [1] yday [303] isdst [0] zone [EST] gmtoff [-18000] isdst 是 [1] tm: sec [0] min [30] hour [1] mday [30] mon [10] year [2005] wday [1] yday [303] isdst [0] zone [EST] gmtoff [-18000] isdst 是 [-1] tm: sec [0] min [30] hour [1] mday [30] mon [10] year [2005] wday [1] yday [303] isdst [0] zone [EST] gmtoff [-18000] 使用 mktime 进行测试 isdst 是 [0] tm: sec [0] min [30] hour [1] mday [30] mon [10] year [2005] wday [1] yday [303] isdst [0] zone [EST] gmtoff [-18000] isdst 是 [1] tm: sec [0] min [30] hour [0] mday [30] mon [10] year [2005] wday [1] yday [303] isdst [0] zone [EST] gmtoff [-18000] isdst 是 [-1] tm: sec [0] min [30] hour [1] mday [30] mon [10] year [2005] wday [1] yday [303] isdst [0] zone [EST] gmtoff [-18000]

在我看来,他们俩都弄错了。 tm_isdst 字段似乎对 tm_gmtoff 字段没有影响。使用mktime 时,tm_hour 输出发生变化,但偏移量仍然错误。

如果您将 tm_mday 更改为几天前或几天后,gmtoffset 根本不会改变,这让我感到困惑。

是我做错了什么还是我误解了这些功能的工作原理?

【问题讨论】:

  • 夏令时更改在 02:00,而不是 01:00。
  • struct tm 中的月份从0 变为11,因此十月是9,而不是10。您打印的是 11 月 30 日的结果,而不是 10 月 30 日。
  • 我解决了。 tm-&gt;tm_year 字段基于 1900 年。因此,对于 2005 年,我需要在该字段中输入 (2005 - 1900) = 105。然后它工作。至于@barmar 的评论,代码已经针对 0-11 个月的计数进行了调整,因此这不是错误。 set_tm 函数将月份值减 1。
  • 那个也是。我刚刚注意到您已经为 tm_mon 加/减 1。
  • 您应该将其发布为答案。您可以回答自己的问题。

标签: c datetime time freebsd darwin


【解决方案1】:

UNIX 时间非常混乱。原来我的错误与struct tm 中的tm_year 字段有关。它应该代表自 1900 年以来的年数,因此该字段中的值应该是 105 而不是 2005(例如 2005 - 1900 = 105)。这现在产生了正确的答案。

这个结构的定义可以在this very useful page.找到

【讨论】:

  • 这实际上是一个非常有用的橡皮鸭课程。我发布了这个问题,然后带狗去散步。答案是在散步时告诉我的。 :)
  • 这没有什么“unix”。 Unix 时间是自纪元以来的整数秒,单位为 UTC,但行为更像 UT1。您正在查看的内容,struct tm 或“故障时间”,完全由 ISO C 指定,与 unix 无关。
  • 那是无用的评论。
  • 我不明白这是怎么回事。这是对您答案第一句话的不相关/误传的评论。
  • 当然,对。我的 pdp-11 使用了相同的设置。对于 OS/390 也是如此。我的 Amiga 采用了同样的方法。 10 版之前的 MacOS 同上。那是什么? C 在所有这些平台上运行,但没有强加这种扭曲?这种愚蠢的时间设置主要来自 UNIX。这是操作系统错误,而不是 C 语言问题。
猜你喜欢
  • 2014-04-28
  • 1970-01-01
  • 2021-01-20
  • 1970-01-01
  • 2017-09-25
  • 2019-12-16
  • 2018-11-02
  • 2014-04-21
  • 2013-03-18
相关资源
最近更新 更多