在C语言中获取时区GMT偏移量

18

我正在使用标准的mktime函数将struct tm转换为时期时间值。 tm字段本地填充,我需要将时期时间作为格林威治标准时间获得。 tm有一个gmtoff字段,可以让您设置本地GMT偏移量(以秒为单位)以实现此目的。

但是我无法找到如何获取此信息。一定有标准函数可以返回偏移量吧?localtime是如何做到的呢?


3
tm具有gmtoff字段”--> 这是一个非标准的C库扩展。 - chux - Reinstate Monica
12个回答

27

只需执行以下步骤:

#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。如果你能改变那个,我会点赞的。 - Serrano
4
我也想看看Windows的代码。非常感谢您的帮助! - Bhupesh Pant
13
tm_gmtofftm_zone是非C标准的扩展。 - chux - Reinstate Monica
1
我猜测 %lds 是什么意思。结果是 %lds :D - Zhang

7
如何使用localtime函数?
根据localtime手册页: localtime()函数的作用就像它调用了tzset(3)并设置了外部变量tzname,其中包含有关当前时区的信息,timezone则是协调世界时(UTC)和本地标准时间之间的差异(以秒为单位)。
因此,您可以调用localtime(),并将在timezone中得到差异,或者调用tzset()。
extern long timezone;
....
tzset();
printf("%ld\n", timezone);

注意:如果您选择使用 localtime_r(),请注意不需要设置那些变量,您需要先调用 tzset() 来设置 timezone
根据 POSIX.1-2004 规范,localtime() 必须表现得好像已经调用了 tzset(),而 localtime_r() 没有这个要求。对于可移植的代码,在调用 localtime_r() 之前应该先调用 tzset()

此方法不考虑夏令时。 - Ladislav

6
获取本地时间偏移量的通用版本在这里。
我从stackoverflow中这个答案借鉴了一些代码。
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);
}

要求mktime()确定DST是有问题的,因为它可能无法做到这一点,并且ptm不是本地时间。 - Ladislav

5
我想在提问之前应该多搜索一些。原来有一个鲜为人知的timegm函数,它可以做与gmtime相反的操作。它被GNU和BSD支持,这对我的需求来说足够好了。更具可移植性的解决方案是将TZ环境变量的值暂时设置为“UTC”,然后使用mktime,最后再设置TZ回来。
但对我来说,timegm很适合。

11
链接说:“这些函数是非标准的GNU扩展,但也存在于BSD上。避免使用它们。” - satur9nine

3

这是一种便携式解决方案,应该在所有标准的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”的原因是(考虑GMT),这是全球唯一具有相同日期的时间点。由于这个事实,我们不必考虑在计算中的日期变化的其他魔法。
警告:我的先前答案版本仅适用于Linux。
// DO NOT DO THAT!!
int timezonez_diff = localtime(&epoch_plus_11h)->tm_hour -
                     gmtime(&epoch_plus_11h)->tm_hour;

这可能不起作用,因为从localtimegmtime返回的指向 tm 对象的指针可能是共享的(在 Windows/msvc 上确实如此)。这就是我引入计算临时变量的原因。


3
简单明了。但并非所有时区都是整数小时。最好也要计算分钟。 - Tb.
那个如何处理夏令时? - bbonev
1
点赞这个好主意,但实际上没有一个时刻全球共享同一日期。时区的跨度是从“UTC-12”到“UTC+14”。 - aafulei
1
对这个好主意点赞,但实际上并没有一个时刻全球共享同一日期。时区的范围是从UTC-12UTC+14 - aafulei
这个程序没有正确处理夏令时。 - Ladislav

2

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

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

返回与UTC的偏移量(以秒为单位)。
不需要定义_GNU_SOURCE,但请注意,timegm不是POSIX标准,并且可能在GNU和BSD之外不可用。

1

我相信在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 编辑: man tzfile 描述了 zoneinfo 文件的格式。您应该能够简单地将其映射到适当类型的结构中。根据对 zdump 的 strace,它似乎就是这样做的。

0

这里是一个线程安全的方法,取自我对此帖子的回答:
什么是在UTC / GMT中获取一天的开始的正确方法?

::time_t GetTimeZoneOffset ()
{ // This method is to be called only once per execution
  static const seconds = 0; // any arbitrary value works!
  ::tm tmGMT = {}, tmLocal = {}; 
  ::gmtime_r(&seconds, &tmGMT); // ::gmtime_s() for WINDOWS
  ::localtime_r(&seconds, &tmLocal);  // ::localtime_s() for WINDOWS
  return ::mktime(&tmGMT) - ::mktime(&tmLocal);
};

0

这是我的方法:

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之间的偏差:

LocalST + tzlag = UTC

如果您还想考虑“夏令时”的影响,请从tzlag中减去tm_isdst,其中tm_isdst是特定本地时间struct tm的一部分,在应用mktime(或使用localtime)后获得。

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


0
最终得到了这个结果。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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接