Linux上的时区转换C API,任何人?

我正在寻找一些我认为非常简单的东西 – 给定特定时区的本地Unix时间(指定为string,例如“America / New_York” – 注意这不是我的本地时间),获取相应的时间值在GMT。 也就是说

time_t get_gmt_time(time_t local_time, const char* time_zone); 

就像听起来那么简单,我能find的最接近的是来自timegm手册页的以下代码片段:

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

有比这种好战的线程安全的可恶方法更好的方法,对吗? 对??

想在这里添加更多的细节。

如果您尝试以下操作:

 #include <stdio.h> #include <time.h> /* defines 'extern long timezone' */ int main(int argc, char **argv) { time_t t, lt, gt; struct tm tm; t = time(NULL); lt = mktime(localtime(&t)); gt = mktime(gmtime(&t)); printf( "(t = time(NULL)) == %x,\n" "mktime(localtime(&t)) == %x,\n" "mktime(gmtime(&t)) == %x\n" "difftime(...) == %f\n" "timezone == %d\n", t, lt, gt, difftime(gt, lt), timezone); return 0; } 

你会注意到时区转换确保:

  • mktime(localtime(t)) == t
  • mktime(gmtime(t)) == t + timezone
    因此:
  • difftime(mktime(gmtime(t)), mktime(localtime(t))) == timezone
    (后者是由tzset()或任何时区转换函数的调用初始化的全局变量)。

以上示例输出:

 $ TZ = GMT ./xx
 (t = time(NULL))== 4dd13bac,
 mktime(localtime(&t))== 4dd13bac,
 mktime(gmtime(&t))== 4dd13bac
 difftime(...)== 0.000000
时区== 0

 $ TZ = EST ./xx
 (t = time(NULL))== 4dd13baf,
 mktime(localtime(&t))== 4dd13baf,
 mktime(gmtime(&t))== 4dd181ff
 difftime(...)== 18000.000000
时区== 18000

 $ TZ = CET ./xx
 (t = time(NULL))== 4dd13bb2,
 mktime(localtime(&t))== 4dd13bb2,
 mktime(gmtime(&t))== 4dd12da2
 difftime(...)== -3600.000000
时区== -3600

从这个意义上说,你试图“向后做” – time_t在UN * X中被视为绝对的 ,即总是相对于“EPOCH”(1970年1月1日0:00 UTC)。

UTC和当前时区(最后一个tzset()调用)之间的区别总是在external long timezone全局中。

这并没有摆脱环境操纵的丑陋,但你可以节省自己通过mktime()的努力。

从tzfile(5),它将/ usr / share / zoneinfo(在我的系统上)中的文件记录在可怕的细节中:

看来,时区在内部使用tzfile,但glibc拒绝暴露给用户空间。 这很可能是因为标准化的功能更加有用和便携,而且实际上由glibc记录。

再一次,这可能不是你正在寻找的(即一个API),但信息是在那里,你可以解析它,而不是太痛苦。

我真的认为有些事情是滑稽的,但似乎被误解了。 我知道你可能正在寻找直接的C代码,但这里是我得到的最好的:

我知道Python通过tzinfo类有一些时区的概念 – 你可以在日期时间文档中阅读它。 你可以看一下模块的源代码(在压缩包里 ,它在modulees / datetime.c中) – 它看起来有一些文档,所以也许你可以从中得到一些东西。

与Python的答案类似,我可以告诉你R做了什么:

 R> now <- Sys.time() # get current time R> format(now) # format under local TZ [1] "2009-08-03 18:55:57" R> format(now,tz="Europe/London") # format under explicit TZ [1] "2009-08-04 00:55:57" R> format(now,tz="America/Chicago") # format under explicit TZ [1] "2009-08-03 18:55:57" R> 

但R使用扩展通常struct tm的内部表示法—请参阅R-2.9.1 / src / main / datetime.c。

不过,这是一个多毛的话题,如果它是标准库,那将会很好。 因为这不是最好的选择是使用Boost Date_Time(例子)

gmtime,localtime及其变体的问题是对TZ环境变量的依赖。 时间函数首先调用tzset(void),它读取TZ以确定偏移DST等。如果在用户环境中未设置TZ,(g)libc使用系统时区。 所以如果你在欧洲/巴黎有一个本地struct tm,并且你的机器或者环境被设置为'America / Denver',那么当你转换成GMT时将会应用错误的偏移量。 所有的时间函数调用tzset(void)读取TZ来设置char * tzname [2],long timezone(以GMT为单位的秒数)和int daylight(DST的布尔值)。 直接设置这些不会有什么影响,因为tzset()会在下一次调用localtime时覆盖它们。

在原来的问题中,我面临着与'igor'相同的问题,而setenv的工作似乎是有问题的(re-entran?)。 我决定进一步查看是否可以将tzset(void)修改为tzset(char *)来显式设置上述变量。 那当然,这只是一个坏主意……但是在探究glibc的来源和IANA TZ数据库来源时,我得出的结论是setenv方法并不是那么糟糕。

首先,setenv只修改进程全局'char ** environ'(而不是调用shell,所以'真实'TZ不受影响)。 其次,glibc实际上对setenv进行了锁定。 缺点是setenv / tzset调用不是原子的,所以在原始线程调用tzset之前,另一个线程可能会写入TZ。 但是一个使用线程的良好实现的应用程序应该注意这一点。

如果POSIX定义了tzset在广泛的IANA TZ数据库中查找字符*(并用NULL来表示'使用用户或系统TZ /'),那么这将是很酷的,但是如果失败了,setenv似乎是可以的。

为什么不能使用gmtime_r() ? 以下工作对我来说很好:

 int main() { time_t t_gmt, t_local=time(NULL); struct tm tm_gmt; gmtime_r(&t_local, &tm_gmt); t_gmt = mktime(&tm_gmt); printf("Time now is: %s", ctime(&t_local)); printf("Time in GMT is: %s", ctime(&t_gmt)); return 0; }