mktime() 用于非本地时区

mktime() for non-local timezone

提问人:NorthStar 提问时间:4/22/2022 最后编辑:NorthStar 更新时间:4/23/2022 访问量:774

问:

在 C 语言中,函数 mktime() 根据本地时区返回纪元时间(输入结构是本地格式化的)。

函数 timegm() 根据 UTC 时间返回纪元时间(输入结构体的格式基于 UTC 时间)。

函数 localtime_r () 接受纪元时间并返回本地时区格式的结构。

函数 gmtime_r () 接受纪元时间并返回 UTC 格式的结构。

我需要找出非本地时区当前是否是夏令时,如果它是本地的,这将与 localtime_r() 函数一起使用,但如果它不是本地的呢?

gmtime_r() 函数始终将tm_isdst字段设置为零,这在这里不起作用。

也许还有其他一些我不知道的功能。不确定。

c mktime ctime

评论

0赞 Barmar 4/22/2022
设置环境变量,然后调用在本地时区运行的函数。TZ
0赞 nsilent22 4/22/2022
请看这个答案: stackoverflow.com/a/7107036/5128826
1赞 NorthStar 4/22/2022
“设置 TZ 环境变量,然后调用在本地时区运行的函数。”这不会在全系统范围内设置时区吗?还是仅适用于应用程序环境?
1赞 ikegami 4/22/2022
不可以,更改进程的 env var 只会更改进程的 env var 副本(它还会更改该进程的任何稍后创建的子级继承的值。但是,如果您有多线程应用程序,则可能会导致问题。在这种情况下,您需要采取措施保护自己。
1赞 chux - Reinstate Monica 4/23/2022
BostonBSD,知道为什么你想知道“找出非本地时区当前是否是夏令时”将是很有见地的,因为更高层次的问题可能比这更容易解决。

答:

2赞 ikegami 4/22/2022 #1

我需要确定非本地时区当前是否为夏令时

  1. 以 的形式获取当前时间。time_ttime
  2. 使用 将 env var 设置为目标时区。TZputenv
  3. 叫。(我不知道这是否是必需的,但调用它肯定没有坏处。tzset
  4. 用于根据(修改后的)本地时区将 转换为。localtimetime_tstruct tm
  5. 检查该字段。tm_isdststruct tm

评论

0赞 NorthStar 4/22/2022
可以在此处找到设置 TZ env 变量的示例
2赞 Jonathan Leffler 4/22/2022 #2

这是我十年前写的一些代码,它们符合 ikegami 在他们的回答中建议的内容:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

static void time_convert(time_t t0, char const *tz_value)
{
    char old_tz[64] = "-none-";
    char *tz = getenv("TZ");
    if (tz != 0)
        strcpy(old_tz, tz);
    setenv("TZ", tz_value, 1);
    tzset();
    char new_tz[64];
    strcpy(new_tz, getenv("TZ"));
    char buffer[64];
    struct tm *lt = localtime(&t0);
    strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", lt);
    if (strcmp(old_tz, "-none-") == 0)
        unsetenv("TZ");
    else
        setenv("TZ", old_tz, 1);
    tzset();
    printf("%lld = %s (TZ=%s, DST = %d)\n",
           (long long)t0, buffer, new_tz, lt->tm_isdst);
}

int main(void)
{
    time_t t0 = time(0);
    char *tz = getenv("TZ");
    if (tz != 0)
        time_convert(t0, tz);
    time_convert(t0, "UTC0");
    time_convert(t0, "IST-5:30");
    time_convert(t0, "EST5");
    time_convert(t0, "EST5EDT");
    time_convert(t0, "PST8");
    time_convert(t0, "PST8PDT");
}

我得到的输出是:

1650647033 = 2022-04-22 17:03:53 (TZ=UTC0, DST = 0)
1650647033 = 2022-04-22 22:33:53 (TZ=IST-5:30, DST = 0)
1650647033 = 2022-04-22 12:03:53 (TZ=EST5, DST = 0)
1650647033 = 2022-04-22 13:03:53 (TZ=EST5EDT, DST = 1)
1650647033 = 2022-04-22 09:03:53 (TZ=PST8, DST = 0)
1650647033 = 2022-04-22 10:03:53 (TZ=PST8PDT, DST = 1)

请注意,某些时区是在没有夏令时的情况下指定的,并且代码会报告这些区域。DST = 0

请注意在多线程应用程序中像这样更改时区。在重置环境时要小心,以防你和其他程序的 TZ 环境变量值意外。fork()exec()

注意:我已将代码修改为:

  • 用于打印值。令人讨厌的事情之一是没有标准格式的字符串用于将类型打印为整数(事实上,C 标准甚至不能保证类型是整数,但它实际上在大多数系统上都是,并且 POSIX 要求它是整数类型——参见 <sys/types.h>)。此更改应允许 32 位系统在 2038 年工作。这假定它是一个 64 位值,即使它是一个 32 位系统;如果系统仍然使用 32 位,当换行到 -231 时,它最终会损坏——但这不应该仍然是我的问题。long longlongtime_ttime_ttime_ttime_ttime_t
  • 打印问题中想要的信息。lt->tm_isdst

评论

0赞 NorthStar 4/22/2022
这与我在上一个回答后写的代码非常相似,谢谢你的这个例子。
1赞 Jonathan Leffler 4/23/2022
好点,@chux。我很高兴池上氏的回答被重新接受。我已经更新了我的代码。
0赞 NorthStar 4/23/2022
我不确定我是否可以接受两个答案,我恢复了它,因为我先选择了它,但选择是任意的。
0赞 Jonathan Leffler 4/23/2022
@BostonBSD,你只能接受一个答案。我很高兴你选择了池上氏的答案。史蒂夫·萨米特(Steve Summit)也有一些有趣的观点。C 库时间处理代码一团糟,但修复它更麻烦。必须弄乱环境变量才能得到一个理智的答案,这与现代编码风格背道而驰。
1赞 Steve Summit 4/23/2022
但是,是的,看可能是唯一理智的方法。(不要查看时区名称字符串并尝试自己解析它们 - 这样你肯定会遇到麻烦。tm_isdst
3赞 Steve Summit 4/22/2022 #3

如果您 (a) 不想使用全局环境变量,并且 (b) 可以使用“NetBSD inspired”时间函数,那么还有另一种可能性:和 ,它允许您明确指定要使用的区域。因此,您不仅限于默认本地区域或 UTC。mktime_z()localtime_rz()

下面是一个示例:

int main(int argc, char **argv)
{
    timezone_t tzp = tzalloc(argv[1]);
    if(tzp == NULL) return 1;
    time_t now = time(NULL);
    struct tm tm;
    struct tm *tmp = localtime_rz(tzp, &now, &tm);
    char tmpbuf[20];
    strftime(tmpbuf, sizeof(tmpbuf), "%H:%M:%S", tmp);
    printf("right now in zone %s is %s\n", argv[1], tmpbuf);

    tm.tm_year = 1976 - 1900;
    tm.tm_mon = 7 - 1;
    tm.tm_mday = 4;
    tm.tm_hour = 12;
    tm.tm_min = tm.tm_sec = 0;
    tm.tm_isdst = -1;

    time_t t = mktime_z(tzp, &tm);

    printf("in zone %s, %d-%02d-%02d %d:%02d was %ld\n", argv[1],
        1900+tm.tm_year, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, t);
}

当我调用时,我看到tzt America/New_York

right now in zone America/New_York is 11:58:23
in zone America/New_York, 1976-07-04 12:00 was 205344000

当我调用时,我看到tzt America/Los_Angeles

right now in zone America/Los_Angeles is 08:58:49
in zone America/Los_Angeles, 1976-07-04 12:00 was 205354800

现在,话虽如此,还有两条评论,与我的开场白“如果”有关:

一个。如果你不想在全局环境变量上混淆,我一点也不怪你。我非常讨厌使用全局变量(更不用说环境变量)来影响函数的喜欢或行为方式。然而,不幸的是,在这种情况下,这是 C 语言中推荐的方法——有关详细信息,请参阅此问题的其他答案 12mktimelocaltime

b.不幸的是,很有可能你没有,事实上,“有受 NetBSD 启发的时间函数可供你使用”。它们是非标准的,甚至不是很受欢迎。我之所以能够编译上面的测试程序,只是因为我手边有一份 IANA tz 数据库及其代码的副本,如果您还定义了 .(这就是为什么我打破了规则,没有展示一个完整的例子,所有的台词,因为我的台词很奇怪,很特质。NETBSD_INSPIRED#include