我正在寻找一些我认为非常简单的东西 – 给定特定时区的本地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; }