【问题标题】:Get the time zone GMT offset in C获取 C 中的时区 GMT 偏移量
【发布时间】:2012-11-28 01:14:52
【问题描述】:

我正在使用标准的mktime 函数将struct tm 转换为纪元时间值。 tm 字段在本地填充,我需要将纪元时间设为 GMT。 tm 有一个 gmtoff 字段,允许您为此目的以秒为单位设置本地 GMT 偏移量。

但我不知道如何获取该信息。当然一定有一个标准函数可以返回偏移量吗? localtime是怎么做到的?

【问题讨论】:

  • "tm 有一个 gmtoff 字段" --> 这是一个非标准 C 库扩展。

标签: c datetime timezone libc


【解决方案1】:

只需执行以下操作:

#define _GNU_SOURCE /* for tm_gmtoff and tm_zone */

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

/* Checking errors returned by system calls was omitted for the sake of readability. */
int main(void)
{
  time_t t = time(NULL);
  struct tm lt = {0};

  localtime_r(&t, &lt);

  printf("Offset to GMT is %lds.\n", lt.tm_gmtoff);
  printf("The time zone is '%s'.\n", lt.tm_zone);

  return 0;
}

注意:time() 返回的纪元以来的秒数按格林威治标准测量。

【讨论】:

  • 我真的很喜欢这个简单的解决方案,但我发现你的代码有一个问题。我认为struct tm = {0}; 应该是struct tm result = {0};,然后您可以在其余代码中使用result 而不是tm。如果您可以更改,请向我 +1。
  • 我也想看看 windows 的代码。任何帮助表示赞赏!
  • tm_gmtoff, tm_zone 是非 C 标准扩展。
【解决方案2】:

localtime是怎么做的?

根据localtime手册页

localtime() 函数就像调用 tzset(3) 并设置 带有当前时区信息的外部变量 tzname, 时区 与 Coordinated Universal 的区别 以秒为单位的时间 (UTC) 和当地标准时间

所以你可以打电话给localtime(),你会在timezone有区别,或者打电话给tzset()

extern long timezone;
....
tzset();
printf("%ld\n", timezone);

注意:如果您选择使用localtime_r(),请注意不需要设置这些变量,您需要先调用tzset() 来设置timezone

根据 POSIX.1-2004,localtime() 必须表现得像 tzset() 被调用,而 localtime_r() 没有这个 要求。对于可移植代码 tzset() 应该在之前调用 localtime_r()

【讨论】:

    【解决方案3】:

    我想我应该在问之前做更多的搜索。事实证明,有一个鲜为人知的timegm 函数与gmtime 正好相反。它在 GNU 和 BSD 上受支持,这对我的目的来说已经足够了。更便携的解决方案是将TZ环境变量的值临时设置为“UTC”,然后使用mktime,然后再设置TZ

    但是timegm 对我有用。

    【讨论】:

    • 链接说“这些函数是非标准的 GNU 扩展,也存在于 BSD 上。避免使用它们。”
    【解决方案4】:

    获取本地时间偏移功能的通用版本在这里。
    我在 stackoverflow 中从this answer 借用了一些代码。

    int time_offset()
    {
        time_t gmt, rawtime = time(NULL);
        struct tm *ptm;
    
    #if !defined(WIN32)
        struct tm gbuf;
        ptm = gmtime_r(&rawtime, &gbuf);
    #else
        ptm = gmtime(&rawtime);
    #endif
        // Request that mktime() looksup dst in timezone database
        ptm->tm_isdst = -1;
        gmt = mktime(ptm);
    
        return (int)difftime(rawtime, gmt);
    }
    

    【讨论】:

      【解决方案5】:

      这是适用于所有标准 C(和 C++)平台的可移植解决方案:

      const std::time_t epoch_plus_11h = 60 * 60 * 11;
      const int local_time = localtime(&epoch_plus_11h)->tm_hour;
      const int gm_time = gmtime(&epoch_plus_11h)->tm_hour;
      const int tz_diff = local_time - gm_time;
      

      使用 C++ 时添加 std:: 命名空间。结果以小时为单位,范围为 [-11, 12];

      解释: 我们只需将日期时间“1970-01-01 11:00:00”转换为 tm 结构两次 - 使用本地时区和 GMT。结果是小时部分之间的差异。

      之所以选择“11:00::00”,是因为这是我们在全球范围内拥有相同日期的唯一时间点(考虑到格林威治标准时间)。由于这个事实,我们不必在计算中考虑日期变化带来的额外魔力。

      警告

      我的答案的先前版本仅适用于 linux:

      // DO NOT DO THAT!!
      int timezonez_diff = localtime(&epoch_plus_11h)->tm_hour -
                           gmtime(&epoch_plus_11h)->tm_hour;
      

      这可能不起作用,因为作为来自localtimegmtime 的指针返回的结果tm 对象的存储可能是共享的(并且它位于windows/msvc 上)。这就是我引入临时计算的原因。

      【讨论】:

      • 漂亮而简单。但并非所有时区都是整数小时数。最好也计算小步舞曲
      • 如何处理夏令时?
      【解决方案6】:

      我相信至少在 linux 中是这样的:时区信息来自 /usr/share/zoneinfo/。 localtime 读取 /etc/localtime,它应该是 zoneinfo 中相应文件的副本。您可以通过在时区文件上执行zdump -v 来查看里面的内容(zdump 可能在 sbin 中,但您不需要提升权限来读取时区文件)。这是其中的一个片段:

      /usr/share/zoneinfo/EST5EDT 2033 年 11 月 6 日星期日 05:59:59 UTC = 2033 年 11 月 6 日星期日 01:59:59 EDT isdst=1 gmtoff=-14400 /usr/share/zoneinfo/EST5EDT 2033 年 11 月 6 日星期日 06:00:00 UTC = 2033 年 11 月 6 日星期日 01:00:00 EST isdst=0 gmtoff=-18000 /usr/share/zoneinfo/EST5EDT 2034 年 3 月 12 日星期日 06:59:59 UTC = 2034 年 3 月 12 日星期日 01:59:59 EST isdst=0 gmtoff=-18000 /usr/share/zoneinfo/EST5EDT 2034 年 3 月 12 日星期日 07:00:00 UTC = 2034 年 3 月 12 日星期日 03:00:00 EDT isdst=1 gmtoff=-14400 /usr/share/zoneinfo/EST5EDT 2034 年 11 月 5 日星期日 05:59:59 UTC = 2034 年 11 月 5 日星期日 01:59:59 EDT

      如果你愿意,我想你可以自己解析这个。我不确定是否有一个只返回 gmtoff 的 stdlib 函数(可能有,但我不知道......)

      编辑: man tzfile 描述了 zoneinfo 文件的格式。您应该能够简单地映射到适当类型的结构中。这似乎是 zdump 基于它所做的事情。

      【讨论】:

        【解决方案7】:

        这是受@Hill 和@friedo 答案启发的两行代码:

        #include <time.h>
        ...
        time_t rawtime = time(0);
        timeofs = timegm(localtime(&rawtime)) - rawtime;
        

        以秒为单位返回与 UTC 的偏移量。

        不需要定义 _GNU_SOURCE,但请注意 timegm 不是 POSIX 标准,可能在 GNU 和 BSD 之外不可用。

        【讨论】:

          【解决方案8】:

          就这样结束了。当然 tm_secs 是多余的,只是为了保持一致性。

          int timezone_offset() {
              time_t zero = 0;
              const tm* lt = localtime( &zero );
              int unaligned = lt->tm_sec + ( lt->tm_min + ( lt->tm_hour * 60 ) ) * 60;
              return lt->tm_mon ? unaligned - 24*60*60 : unaligned;
          }
          

          【讨论】:

            【解决方案9】:

            这是我的方式:

            time_t z = 0;
            struct tm * pdt = gmtime(&z);
            time_t tzlag = mktime(pdt);
            

            具有自动本地存储 struct tm 的替代方案:

            struct tm dt;
            memset(&dt, 0, sizeof(struct tm));
            dt.tm_mday=1; dt.tm_year=70;
            time_t tzlag = mktime(&dt);
            

            tzlag,以秒为单位,将是 UTC 偏移量的负数;与 UTC 相比,您的时区标准时间滞后:

            LocalST + tzlag = UTC

            如果您还想考虑“夏令时”,请从tzlag 中减去tm_isdst,其中tm_isdst 是特定当地时间struct tm,在应用mktime 之后(或在获得它之后) localtime)。

            为什么有效:
            集合 struct tm 用于“纪元”时刻,即 1970 年 1 月 1 日,对应于 0 的 time_t。 在该日期调用mktime() 会将其转换为time_t,就好像它是UTC(因此得到0),然后从中减去UTC 偏移量以产生输出time_t。因此它产生负的 UTC_offset。

            【讨论】:

              猜你喜欢
              • 2015-01-03
              • 2022-11-25
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-08-17
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多