8

time_t只使用 C 标准库(纯 ISO C,没有 POSIX,因此没有以“自纪元以来的秒数”表示的假设),获得time_t对应于 1970 年 1 月 1 日 00:00:00 的值的最简单方法是什么世界标准时间

UTC部分是关键;否则,仅mktime在正确初始化的情况下使用就struct tm可以轻松解决问题。

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

4

5 回答 5

4

这是一种方法的条目,如果没有人击败它,“最简单”:

  • 呼吁mktime_ struct tm_02 Jan 1970 00:00:00
  • 调用for 。mktime_ 这可以合理地返回 -1,在这种情况下将其视为 0。struct tm31 Dec 1969 00:00:00
  • 两者之间的二分搜索time_t,当传递给时gmtime,结果是01 Jan 1970 00:00:00

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

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

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

于 2012-12-19T17:29:30.953 回答
0

我以前的方法有太多问题,原因是关于如何mktime解决非规范化时间表示的模糊性让我无法适应它,所以我将尝试将这个想法与 Steve Jessop 的猜测/搜索想法合并以获得更好的方法:

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

  2. 打电话。mktime_ tm0但是,这将导致其被解释为本地时间,因此结果将不是所需的答案。调用这个time_tt0

  3. 应用于gmtimet0其转换为分解的世界时间。它应该与所需的时期相差不到 24 小时(实际上,最多 12 小时)。

  4. 通过差值进行调整tm0并返回步骤 2。如果步骤 3 给出了正确的细分世界时间纪元,我们就完成了。否则,重复步骤 2-4(应该没有必要)。

在代码中,

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;
        }
}
于 2012-12-20T06:49:36.903 回答
0

第一步:选择任何一个time_t(当前时间就可以了)作为参考点;叫它t0

第 2 步:调用gmtimet0以分解的struct tm形式计算结果与 epoch 之间的差异。

第 3 步:调用并将第 2 步localtimet0的细分差异应用于结果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 公式(http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_15)posix_time计算给定纪元以来秒数的函数,还有额外的工作struct tm如果需要,处理 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每年调用多次。

于 2012-12-19T17:42:38.477 回答
0

你的问题是相当基本的:ISO C 几乎完全在时区上踢球,简单地提供mktime()和转换,localtime()gmtime()带有夏令时的钩子。(他们实施,你决定。

因此,您似乎只能做两件事:

  1. 假设这time_t是秒自纪元 UTC 并用于gmtime()验证,如果失败则恐慌或警告;或者
  2. 依赖比 ISO C 更全面的标准
于 2012-12-19T17:30:11.227 回答
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
}

我仍在考虑是否需要 code:tm->tm_hour--等。

于 2012-12-19T21:47:56.077 回答