如何计算纯ISO C中的纪元的time_t?

时间:2012-12-19 17:17:48

标签: c time timezone

只使用C标准库(普通ISO C,没有POSIX,因此不假设time_t在“自纪元以来的秒数”中表示),获得{{1}的最简单方法是什么对应于01 Jan 1970 00:00:00 UTC

的值

UTC部分是关键;否则,只需在正确初始化的time_t上使用mktime即可轻松解决问题。

或者(这实际上是问题的“要点”),如何可移动地确定给定struct tm值之间的POSIX秒数,例如:通过time_t获得的当前时间和时代? “POSIX秒”是指POSIX的“自大纪以来的秒”中使用的定义,它不使用闰秒。如果这听起来太复杂,只需回到第一段中最初陈述的问题,并假设该纪元在time(0)中可以表示。

5 个答案:

答案 0 :(得分:4)

这里有一个方法可以做到这一点,“最简单”,如果没人打败它:

  • mktime struct tm
  • 上致电02 Jan 1970 00:00:00
  • mktime上为struct tm致电31 Dec 1969 00:00:00。这可以合理地返回-1,在这种情况下将其视为0。
  • 二者之间的二进制搜索,其值为time_t,当传递给gmtime时,会产生01 Jan 1970 00:00:00

假设当地时间不超过24小时与UTC不同,我很确定这是事实。如有必要,我们可以扩大边界,在极端情况下,我们可以在0time(0)之间进行搜索。

可以改进二进制搜索,例如通过插值。但谁知道,也许一些疯狂的时区(或破坏的tzinfo数据)可能导致12月/ 1月的夏令时变化。我怀疑这发生在任何实时区域。但是C标准并没有禁止这种做法,只有常识。

如果不是这样,我认为我们可以根据gmtime(mktime(01 Jan))(获取时区)和01 Jan 1970 00:00:0001 Jan 1970 00:00:01的比较来计算(以获得精确度) time_t)。

答案 1 :(得分:0)

你的问题是相当基本的:ISO C在时区上几乎完全支持,仅提供mktime()localtime()gmtime()转换,以及夏令时的钩子。 (他们实施,你决定。

所以你似乎只能做两件事:

  1. 假设time_t是自UTC时代以来的秒,并使用gmtime()来验证,并且如果它失败则会发生恐慌或警报;
  2. 依赖比ISO C更全面的标准

答案 2 :(得分:0)

步骤1:选择任何time_t(当前时间将正常工作)作为参考点;称之为t0

第2步:在gmtime上致电t0,以分解的struct tm形式计算结果与纪元之间的差异。

第3步:在localtime上致电t0,并将步骤2中的细分差异应用于生成的struct tm。然后在其上调用mktime即可获得time_t

结果应该是代表纪元的time_t

当本地时间偏移随时间不变时,我第一次尝试实现这一点时会出现问题,例如在已添加或放弃日光时间或从观察一个区域切换到另一个区域的地方。这似乎是因为时区信息所基于的struct tm中的数据发生了变化。以下是原始实现及其问题:

time_t get_epoch(time_t t0)
{
    struct tm gmt = *gmtime(&t0);
    struct tm tmp = *localtime(&t0);
    tmp.tm_sec -= gmt.tm_sec;
    tmp.tm_min -= gmt.tm_min;
    tmp.tm_hour -= gmt.tm_hour;
    tmp.tm_mday -= gmt.tm_mday-1;
    tmp.tm_mon -= gmt.tm_mon;
    tmp.tm_year -= gmt.tm_year-70;
    return mktime(&tmp);
}

和改进版本,其中posix_time是使用POSIX公式计算给定struct tm的纪元以来的秒数的函数(http://pubs.opengroup.org/onlinepubs/9699919799 /basedefs/V1_chap04.html#tag_04_15),如果需要,可以在1970年之前处理其他工作,等等:

time_t get_epoch(time_t t0)
{
    struct tm gmt = *gmtime(&t0);
    struct tm tmp = *localtime(&t0);
    long long offset = posix_time(&gmt);
    while (offset > INT_MAX) {
        offset -= INT_MAX;
        tmp.tm_sec -= INT_MAX;
        mktime(&tmp);
    }
    while (offset < -INT_MAX+61) {
        offset -= -INT_MAX+61;
        tmp.tm_sec -= -INT_MAX+61;
        mktime(&tmp);
    }
    tmp.tm_sec -= offset;
    return mktime(&tmp);
}

对于C89兼容性,必须删除long long,并且所需的mktime次呼叫数量会急剧增加; offset无法计算为单个值,但每年多次调用mktime需要一个循环。

答案 3 :(得分:0)

编辑:也许以下非标准timegm的实现符合问题的非POSIX要求:

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

time_t my_timegm(struct tm *tm) {
   time_t t, g;
   double dt; // seconds
   struct tm *gm;
   t = mktime(tm);
   gm = gmtime(&t);
   gm->tm_isdst = 0;
   g = mktime(gm);
   dt = difftime(t, g);
   if (dt >= 0) {
      tm->tm_sec += fmod(dt, 60); // needed to handle 16-bit ints
      tm->tm_min += dt / 60;
   }
   else {
      tm->tm_sec -= fmod(-dt, 60);
      tm->tm_min -= -dt / 60;
   }
   return mktime(tm);
}

int main(void) { // prints time_t for 01 Jan 1970 00:00:00 UTC
   struct tm start;
   memset(&start, 0, sizeof start);
   start.tm_year = 70; // = 1970
   start.tm_mday = 1;  // = 1st
   printf("%ld\n" my_timegm(&start)); // gives 0 on any POSIX system
   return 0;
}

这假设mktime将根据Linux手册页的行为进行structure members .. outside their valid interval ... will be normalized,或者至少确保返回合理的内容。我不知道普通的ISO C是否能保证这一点。

my_timegm的初始版本来自http://lists.samba.org/archive/samba-technical/2002-November/025571.html,并在那里归功于Beeman,Baushke,Sabol和Zawinski:

time_t my_timegm(struct tm *tm) {
   time_t t, g;
   struct tm *gm;
   t = mktime(tm);
   if (t == -1) { // perhaps needed for DST changeover?
      tm->tm_hour--;
      if ((t = mktime(tm)) != -1)
         t += 3600;
   }
   gm = gmtime(&t);
   gm->tm_isdst = 0;
   g = mktime(gm);
   if (g == -1) {
      gm->tm_hour--;
      if ((g = mktime(gm)) != -1)
         g += 3600;
   }
   return (t == -1 || g == -1) ? -1 : t - (g - t); // or difftime
}

我仍然在考虑代码的需要:tm->tm_hour--等等。

答案 4 :(得分:0)

我之前的方法有很多问题源于对mktime如何解决非规范化时间表示的模糊性让我对它感到满意,所以我将尝试将这个想法与Steve Jessop的猜测/搜索理念合并更好的方法:

  1. struct tm对象tm0初始化为纪元的日历时间。

  2. mktime上致电tm0。然而,这将导致其被解释为本地时间,因此结果将不是期望的答案。请拨打time_tt0

  3. gmtime应用于t0,将其转换为细分的世界时。它应该与期望的时期不同,不到24小时(实际上,最多12小时)。

  4. 按差异调整tm0并返回步骤2.如果步骤3给出了正确的细分世界时间纪元,我们就完成了。否则,重复步骤2-4(不一定必要)。

  5. 在代码中,

    time_t get_epoch()
    {
            struct tm tm0 = { .tm_year = 70, .tm_mday = 1 }, gmt;
            time_t t0;
            for (;;) {
                    t0 = mktime(&tm0);
                    gmt = *gmtime(&t0);
                    if (!gmt.tm_sec && !gmt.tm_min && !gmt.tm_hour &&
                            !gmt.tm_yday && gmt.tm_year==70) return t0;
                    tm0.tm_sec -= gmt.tm_sec;
                    tm0.tm_min -= gmt.tm_min;
                    tm0.tm_hour -= gmt.tm_hour;
                    tm0.tm_mday -= gmt.tm_mday-1;
                    tm0.tm_mon -= gmt.tm_mon;
                    tm0.tm_year -= gmt.tm_year-70;
            }
    }