Linux上的时区转换C API,有人吗?

时间:2009-08-03 18:42:23

标签: c linux timezone

我正在寻找一些我认为非常简单的东西 - 考虑到特定时区的本地Unix时间(指定为字符串,例如“America / New_York” - 请注意不是我的当地时间),在GMT中获得相应的时间值。即,

的内容
time_t get_gmt_time(time_t local_time,
                    const char* time_zone);

听起来很简单,我能找到的最接近的是来自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;
       } 

必须有一种比这更好的方式,而不是线程安全的憎恶,对吧?右??

6 个答案:

答案 0 :(得分:6)

想在这里添加更多细节。

如果您尝试以下操作:

#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
timezone == 0

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

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

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

UTC与当前时区(上次tzset()来电)之间的差异始终位于external long timezone全局。

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

答案 1 :(得分:4)

来自tzfile(5),它以令人毛骨悚然的细节记录/ usr / share / zoneinfo(在我的系统上)中的文件:

  

似乎时区使用tzfile   内部,但glibc拒绝   将其暴露给用户空间。这是最多的   可能因为标准化   函数更有用   便携式的,实际记录的   glibc的。

同样,这可能不是您正在寻找的(即API),但信息就在那里,您可以轻松解析它。

答案 2 :(得分:1)

我真的觉得有些东西在滑稽,但似乎有错误的记忆。我知道你可能正在寻找直接的C代码,但这是我得到的最好的代码:

我知道Python在tzinfo类中有一些时区概念 - 你可以在datetime documentation中阅读它。您可以查看模块的源代码(在tarball中,它位于Modules / datetime.c中) - 它似乎有一些文档,所以也许你可以从中获得一些东西。

答案 3 :(得分:1)

与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 (example)

答案 4 :(得分:1)

gmtime,localtime及其变体的问题在于依赖于TZ环境变量。时间函数首先调用tzset(void),它读取TZ以确定偏移量DST等。如果在用户环境中未设置TZ,则(g)libc使用系统时区。因此,如果您在“欧洲/巴黎”中有本地结构,并且您的机器或环境设置为“美国/丹佛”,则转换为GMT时将应用错误的偏移。所有时间函数都调用tzset(void),它读取TZ以设置char * tzname [2],长时区(diff,以秒为单位,从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数据库中查找char *(并使用NULL表示'使用用户或系统TZ /),那将会很酷,但如果失败,setenv似乎是好。

答案 5 :(得分:0)

为什么不能使用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;
}