【问题标题】:Easy way to convert a struct tm (expressed in UTC) to time_t type将 struct tm(以 UTC 表示)转换为 time_t 类型的简单方法
【发布时间】:2012-06-22 19:08:51
【问题描述】:

我该怎么做?有 mktime 函数,但它会将输入视为以本地时间表示,但是如果我的输入 tm 变量恰好是 UTC,我该如何执行转换。

【问题讨论】:

    标签: c++ c


    【解决方案1】:

    使用 timegm() 代替 mktime()

    【讨论】:

    • 好答案 - 唯一的缺点是它是一个非标准的(如在,不是在 POSIX 或 C 标准中)函数。
    • 我在网上看到了其他关于使用类似获取时区偏移量的答案(注意这是伪代码 - 参数不正确): difftime( mktime( gmtime( time( ))),mktime(本地时间(时间())))。但是没有人说过如何将此偏移量应用于 time_t 变量。
    • 查看我的答案以获得更便携的版本。
    • @Dana:你的贬损来源是什么(我在我的任何系统的手册页中都找不到它的引用)?我会告诉你它是一个非标准的 GNU 函数。但它在 BSD Linux 系统上很常见。但 OP 接受了答案这一事实意味着它对他们有用。
    • 投反对票,因为你没有解释为什么应该使用timegm() 而不是mktime();在没有解释的情况下,我被包括在内以支持 Dana 的论点。
    【解决方案2】:

    windows 用户可以使用以下功能:

    _mkgmtime
    

    更多信息链接:https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/mkgmtime-mkgmtime32-mkgmtime64

    【讨论】:

      【解决方案3】:

      这是我在不是 Windows 平台时使用的解决方案(不记得在哪里找到的)

      time_t _mkgmtime(const struct tm *tm) 
      {
          // Month-to-day offset for non-leap-years.
          static const int month_day[12] =
          {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
      
          // Most of the calculation is easy; leap years are the main difficulty.
          int month = tm->tm_mon % 12;
          int year = tm->tm_year + tm->tm_mon / 12;
          if (month < 0) {   // Negative values % 12 are still negative.
              month += 12;
              --year;
          }
      
          // This is the number of Februaries since 1900.
          const int year_for_leap = (month > 1) ? year + 1 : year;
      
          time_t rt = tm->tm_sec                             // Seconds
              + 60 * (tm->tm_min                          // Minute = 60 seconds
              + 60 * (tm->tm_hour                         // Hour = 60 minutes
              + 24 * (month_day[month] + tm->tm_mday - 1  // Day = 24 hours
              + 365 * (year - 70)                         // Year = 365 days
              + (year_for_leap - 69) / 4                  // Every 4 years is     leap...
              - (year_for_leap - 1) / 100                 // Except centuries...
              + (year_for_leap + 299) / 400)));           // Except 400s.
          return rt < 0 ? -1 : rt;
      }
      

      【讨论】:

      • 此解决方案可能存在整数溢出,因此存在安全问题。您可能需要绑定年份并使用更大的类型(请参阅Mutt bug 3880 作为示例)。
      • 我使用了这种方法,由于某种原因,31-01-2018 23:59:58 的秒数比 01-02-2018 00:00:01 多。我最终在没有考虑闰年的情况下编写了我的变体。
      • 如果您只使用 tm->tm_yday 的值,而不是乱用 tm_mday 和 tm_mon 之前的 yday 数,这可以变得更加简单。
      【解决方案4】:

      timegm() 有效,但并非在所有系统上都存在。

      这是一个仅使用 ANSI C 的版本。(编辑:不是严格的 ANSI C!我正在对 time_t 进行数学计算,假设单位是自纪元以来的秒数。AFAIK,标准没有定义 time_t 的单位.) 请注意,它使用 hack,可以这么说,来确定机器的时区,然后相应地调整 mktime 的结果。

      /* returns the utc timezone offset (e.g. -8 hours for PST) */ int get_utc_offset() { time_t zero = 24*60*60L; struct tm * timeptr; int gmtime_hours; /* get the local time for Jan 2, 1900 00:00 UTC */ timeptr = localtime( &zero ); gmtime_hours = timeptr->tm_hour; /* if the local time is the "day before" the UTC, subtract 24 hours from the hours to get the UTC offset */ if( timeptr->tm_mday < 2 ) gmtime_hours -= 24; return gmtime_hours; } /* the utc analogue of mktime, (much like timegm on some systems) */ time_t tm_to_time_t_utc( struct tm * timeptr ) { /* gets the epoch time relative to the local time zone, and then adds the appropriate number of seconds to make it UTC */ return mktime( timeptr ) + get_utc_offset() * 3600; }

      【讨论】:

      【解决方案5】:

      timegm(1) 的以下实现可以在 Android 上流畅运行,并且可能也可以在其他 Unix 变体上运行:

      time_t timegm( struct tm *tm ) {
        time_t t = mktime( tm );
        return t + localtime( &t )->tm_gmtoff;
      }
      

      【讨论】:

      • 希望我能给这个不止一个赞成票。这允许您从 UTC 中提取 DST 增量。
      • @Dana:mktime()localtime() 都可能引入错误,因为使用了错误的 UTC 偏移量。
      • 请注意夏令时... tm->tm_isdst 必须为 0 才能正常工作!
      • 正如 Leo 所说,这不是可移植的,因为 tm_gmtoff 不是 POSIX。它在 BSD 和 GNU C 库中可用。
      • 让正确的返回值为time_t y。此答案在极端情况下失败,因为使用的tm_gmtoff 值基于t 而不是y。因此,如果 tm_gmtoffty 不同,则此代码会生成错误的答案。
      【解决方案6】:

      Loki Astari 的回答是一个好的开始,timegm 是可能的解决方案之一。但是,timegm 的手册页提供了它的可移植版本,因为timegm 不符合 POSIX。这里是:

      #include <time.h>
      #include <stdlib.h>
      
      time_t
      my_timegm(struct tm *tm)
      {
          time_t ret;
          char *tz;
      
          tz = getenv("TZ");
          if (tz)
              tz = strdup(tz);
          setenv("TZ", "", 1);
          tzset();
          ret = mktime(tm);
          if (tz) {
              setenv("TZ", tz, 1);
              free(tz);
          } else
              unsetenv("TZ");
          tzset();
          return ret;
      }
      

      【讨论】:

      • 它也不是线程安全的,但是 mktime() 也不是线程安全的。
      • 如果是c函数,最好有返回类型
      • 不错的收获!是的,这是一个复制/粘贴错误。现在已修复。
      • @T.E.D 这保证是线程不安全的,而 mktime() 通常是。
      • @liberforce 我注意到你的复制/粘贴缺少手册页中的一些代码,你能评论一下你为什么跳过它吗? if (tz) tz = strdup(tz);
      【解决方案7】:

      tzset 的 POSIX 页面描述了全局变量 extern long timezone,其中包含本地时区,作为与 UTC 的秒数偏移量。这将出现在所有符合 POSIX 的系统上。

      为了使时区包含正确的值,您可能需要在程序初始化期间调用tzset()

      然后,您可以从 mktime 的输出中减去 timezone 以获得 UTC 输出。

      #include <stdio.h>
      #include <stdlib.h>
      #include <time.h>
      
      time_t utc_mktime(struct tm *t)
      {
      
          return (mktime(t) - timezone) - ((t->tm_isdst > 0) * 3600);
      } 
      
      int main(int argc, char **argv)
      {
          struct tm t = { 0 };
      
          tzset();
          utc_mktime(&t);
      }
      

      注意:技术上是 tzset()mktime() aren't guaranteed to be threadsafe

      如果一个线程直接访问 tzname、[XSI] [Option Start] 日光或 timezone [Option End],而另一个线程正在调用 tzset() 或任何需要或允许设置时区信息的函数好像通过调用 tzset(),行为未定义。

      ...但大多数实现都是。 GNU C 在tzset() 中使用互斥锁来避免同时修改它设置的全局变量,mktime() 在没有同步的线程程序中看到了非常广泛的用途。我怀疑如果有人遇到副作用,那就是使用setenv() 来改变TZ 的值,就像@liberforce 的回答中所做的那样。

      【讨论】:

      • 这对我很有帮助,但我发现我需要在添加时区之前添加一个额外的检查 t.tm_isdst > 0。
      • 应该是mktime(t) - timezone。此外,mktimet 处转换为 DST,这根本不考虑。
      • 谢谢。修复了这两个问题。
      • 需要注意的是,这种方法假定时区是固定的。如果处于转换日期与今天之间的偏移量发生变化的时区,则结果将关闭。
      【解决方案8】:

      这是我的看法,它完全基于time_t/tm 转换函数,它对time_t 的唯一假设是它是线性的:

      1. 假装更好地了解tm 结构保持当地时间(如果有人问,非DST;没关系,但必须与步骤3 一致),将其转换为time_t
      2. 将日期转换回 tm 结构,但这次以 UTC 表示。
      3. 假装对tm 结构有更好的了解 也持有本地(如果有人要求,非 DST,但更重要的是与步骤 1 一致),并再次将其转换为 time_t
      4. 从两个time_t 结果中,我现在可以计算本地时间(如果有人问,非夏令时)和UTC 之间的时间差,单位为time_t
      5. 将这种差异添加到第一个 time_t 结果中可以让我获得正确的 UTC 时间。

      请注意,差异的计算可以想象一次,然后在以后应用到所需的任意多个日期;这可能是解决gmtime 中缺乏线程安全问题的一种方法。

      (编辑:再说一次,如果时区在用于计算偏移的日期和要转换的日期之间发生更改,这可能会导致问题。)

      tm tt;
      // populate tt here
      tt.tm_isdst = 0;
      time_t tLoc = mktime(&tt);
      tt = *gmtime(&tLoc);
      tt.tm_isdst = 0;
      time_t tRev = mktime(&tt);
      time_t tDiff = tLoc - tRev;
      time_t tUTC = tLoc + tDiff;
      

      警告:如果系统使用基于 TAI 的 time_t(或其他任何尊重闰秒的东西),如果应用于接近闰秒插入的时间点,则结果时间可能会延迟 1 秒。

      【讨论】:

        【解决方案9】:

        这确实是一条注释,其中包含解决 Leo Accend 答案的代码: 请尝试以下操作:

        #include <time.h>
        #include <stdio.h>
        #include <stdlib.h>    
        
        /*
         *  A bit of a hack that lets you pull DST from your Linux box
         */
        
        time_t timegm( struct tm *tm ) {           // From Leo's post, above
          time_t t = mktime( tm );
          return t + localtime( &t )->tm_gmtoff;
        }
        main()
        {
            struct timespec tspec = {0};
            struct tm tm_struct   = {0};
        
            if (gettimeofday(&tspec, NULL) == 0) // clock_gettime() is better but not always avail
            {
                tzset();    // Not guaranteed to be called during gmtime_r; acquire timezone info
                if (gmtime_r(&(tspec.tv_sec), &tm_struct) == &tm_struct)
                {
                    printf("time represented by original utc time_t: %s\n", asctime(&tm_struct));
                    // Go backwards from the tm_struct to a time, to pull DST offset. 
                    time_t newtime = timegm (&tm_struct);
                    if (newtime != tspec.tv_sec)        // DST offset detected
                    {
                        printf("time represented by new time_t: %s\n", asctime(&tm_struct));
        
                        double diff = difftime(newtime, tspec.tv_sec);  
                        printf("DST offset is %g (%f hours)\n", diff, diff / 3600);
                        time_t intdiff = (time_t) diff;
                        printf("This amounts to %s\n", asctime(gmtime(&intdiff)));
                    }
                }
            }
            exit(0);
        }
        

        【讨论】:

          【解决方案10】:

          我也被 mktime() 的问题困扰。我的解决方案如下

          time_t myTimegm(std::tm * utcTime)
          {
              static std::tm tmv0 = {0, 0, 0, 1, 0, 80, 0, 0, 0};    //1 Jan 1980
              static time_t utcDiff =  std::mktime(&tmv0) - 315532801;
          
              return std::mktime(utcTime) - utcDiff;
          }
          

          这个想法是通过使用已知时间(在本例中为 1980/01/01)调用 std::mktime() 并减去其时间戳 (315532801) 来获取时间差。希望对您有所帮助。

          【讨论】:

          • 这里假定time_t 代表秒,但不一定是这样(尽管通常是这样)
          • 这里的 time_t 值不是减 1 吗?好像应该是315532800。
          • 这不可靠,正如 Howard Hinnant 在这里的评论中指出的那样:stackoverflow.com/a/60954178/4083309 作为一个实际例子,俄罗斯在 2011 年至 2014 年之间试验了“永久 DST”。这意味着像 2012 年这样的日期- MSK(莫斯科标准时间)中的 01-01 将根据 UTC+04 进行转换,但 1.1.1980 的更正将根据 UTC+03 进行,假设历史上是准确的实施。
          【解决方案11】:

          对于所有时区和所有时间,如果不是不可能的话,这将是极其困难的。您需要准确记录所有各种任意时区和夏令时 (DST) 法令。有时,不清楚地方当局是谁,更不用说什么时候颁布的法令了。例如,如果跨越闰秒,大多数系统会在正常运行时间(系统已启动的时间)或启动时间(系统启动的时间戳)关闭一秒。一个好的测试将是一个曾经在 DST 但现在不在的日期(反之亦然)。 (不久前在美国发生了变化。)

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-05-06
            • 2018-06-04
            • 2021-07-21
            • 1970-01-01
            • 2012-09-13
            • 2014-02-02
            • 2015-09-09
            相关资源
            最近更新 更多