【问题标题】:Time zone conversion C API on Linux, anyone?Linux上的时区转换C API,有人吗?
【发布时间】:2010-11-16 11:22:18
【问题描述】:

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

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

虽然听起来很简单,但我能找到的最接近的是 timegm 手册页中的以下代码 sn-p:

       #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;
       } 

肯定有比这种好战而不是线程安全的憎恶更好的方法,对吧?对吧??

【问题讨论】:

  • 好问题。事情真的几乎那么糟糕。全世界似乎都认为你永远不会有兴趣假装在不同的时区,或者需要转换到除 UTC 之外的任何时区。这是个问题。
  • 这里对于time_t 和struct tm 的含义有些混淆。在所有类似 UN*X 的系统上,time_t / time() 是“自 'epoch' 以来的秒数”(UTC 0:00 01/01/1970),可以通过 localtime() 转换为 struct tm (给出时区校正的结果)或gmtime()(给出UTC的结果),分别是它们的可重入版本。mktime() 是相反的,将struct tm 转换为time_t。因此,您编码的只是mktime()变相?
  • 我也对线程安全问题感兴趣。如何以线程安全的方式转换到/从任意时区,不一定是“当前”时区。不仅转换,还使用strftime打印出区域。
  • 现在是 2015 年,我仍在寻找线程安全的解决方案

标签: c linux timezone


【解决方案1】:

想在这里添加更多细节。

如果您尝试以下操作:

#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 = 时间(NULL)) == 4dd13bac, mktime(本地时间(&t)) == 4dd13bac, mktime(gmtime(&t)) == 4dd13bac 差异时间(...)== 0.000000 时区 == 0 $ TZ=EST ./xx (t = time(NULL)) == 4dd13baf, mktime(本地时间(&t)) == 4dd13baf, mktime(gmtime(&t)) == 4dd181ff difftime(...) == 18000.000000 时区 == 18000 $ TZ=CET ./xx (t = 时间(NULL)) == 4dd13bb2, mktime(本地时间(&t)) == 4dd13bb2, mktime(gmtime(&t)) == 4dd12da2 difftime(...) == -3600.000000 时区 == -3600

从这个意义上说,您正在尝试“倒退” - time_t 在 UN*X 中被视为 absolute,即始终相对于“EPOCH”(0:00 UTC 1970 年 1 月 1 日)。

UTC 和当前时区之间的差异(最后一次tzset() 调用)始终在external long timezone 全局范围内。

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

【讨论】:

  • 我不会这样做,因为时区之间的偏移量不是恒定的(想想 DST 和历史偏移量的变化)。
  • 请澄清一下 - 你会“不做”什么确切地?我同意 UN*X 时区机制不应该是历史数据库。尽管如此,你能举一个上面给出错误输出的例子吗?
  • 这个答案其实是最好的!非常感谢! :)
【解决方案2】:

来自 tzfile(5),它详细记录了 /usr/share/zoneinfo(在我的系统上)中的文件:

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

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

【讨论】:

  • 是的,这听起来像是完成这项工作的唯一方法,尽管它很丑。当然,一旦实现,我将首先得到一个我一直在寻找的通用时区转换 API :o 由于我目前正在处理一个单线程分叉进程,我可能没有足够的动力去做这个并且可能只会选择 timegm 手册页中提到的 TZ“hack”。
【解决方案3】:

gmtime、localtime 及其变体的问题在于对 TZ 环境变量的依赖。时间函数首先调用 tzset(void),它读取 TZ 以确定偏移 DST 等。如果在用户环境中未设置 TZ,则 (g)libc 使用系统时区。因此,如果您在“Europe/Paris”中有一个本地结构 tm,并且您的机器或环境设置为“America/Denver”,则在转换为 GMT 时将应用错误的偏移量。所有时间函数调用 tzset(void) 读取 TZ 以设置 char *tzname[2]、长时区(差异,以秒为单位,从 GMT 开始)和 int 日光(DST 的布尔值)。直接设置这些没有影响,因为 tzset() 会在你下次调用 localtime 时覆盖它们。

我在原始问题中遇到了与“igor”相同的问题,而 setenv 工作它似乎有问题(重新进入?)。我决定进一步看看是否可以将 tzset (void) 修改为 tzset(char*) 以显式设置上述变量。好吧,当然,这只是个坏主意……但是在探索 glibc 源和 IANA TZ 数据库源时,我得出的结论是 setenv 方法还不错。

首先,setenv 只修改进程全局 'char **environ'(不是调用 shell,因此不影响“真实”TZ)。其次,glibc 实际上在 setenv 中加了一个锁。缺点是 setenv/tzset 调用不是原子的,因此可以想象另一个线程可以在原始线程调用 tzset 之前写入 TZ。但无论如何,一个使用线程的良好实现的应用程序都应该注意这一点。

如果 POSIX 定义 tzset 使用 char* 在广泛的 IANA TZ 数据库中查找(并使用 NULL 表示“使用用户或系统 TZ/”),那就太酷了,但如果不这样做,setenv 似乎是好的。

【讨论】:

    【解决方案4】:

    我真的以为 glib 中有什么东西,但似乎记错了。我知道您可能正在寻找直接的 C 代码,但这是我所拥有的最好的:

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

    【讨论】:

    • 是的,我的意思当然是直接的 C API - 主题现已更正,谢谢!至于查看 Python 实现,这个想法闪过我的脑海,但我真的希望避免走这条路。在查看了源代码和 API 文档后,我发现 Python 的 datetime 完全不知道时区 - 它所提供的只是一个抽象的 tzinfo 类,如果你要在时间之间进行时间转换,你应该实现它区。有一些库实现了这一点,但是,真的 - 事情真的那么糟糕吗?
    【解决方案5】:

    类似于 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)

    【讨论】:

    • 据我了解,Boost 的 Date_time 没有使用系统的 zoneinfo 文件,而是依赖于自己的时区数据库——这使得它无法启动,恕我直言。如果我错了,请纠正我...
    【解决方案6】:

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

    【讨论】:

    • 一直在想同样的事情,特别是因为原始海报说“GMT”。
    • 这很好,但是假设计算机时区是+1,而您要转换的时间是+2。这还不是最坏的情况。想象一下,在我的情况下,计算机时区是 Europe/Amsterdam,而您要在 tm 结构中转换的时间是 Europe/Athens 时区。它不仅是加/减一小时,而且是全球不同的夏令时。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-17
    • 1970-01-01
    • 1970-01-01
    • 2022-01-19
    • 2011-05-06
    相关资源
    最近更新 更多