C++ timegm 将 DST 转换为将来给定时间的某个时区?

Posted

技术标签:

【中文标题】C++ timegm 将 DST 转换为将来给定时间的某个时区?【英文标题】:C++ timegm conversion DST to a certain timezone at a given time in the future? 【发布时间】:2014-09-29 23:21:12 【问题描述】:

我需要在课堂上将 UTC 时间准确转换为给定时区的本地时间,无论 DST 是否有效。我的问题是,当我使用 struct tm 时,我必须提供 tm_isdst 成员,或者将其保留为 -1 以自动确定。

from mktime(3) - linux man page:
"The value specified in the tm_isdst field informs mktime() whether or not daylight saving time (DST) is in effect for the time supplied in the tm structure:
a positive value means DST is in effect;
zero means that DST is not in effect;
and a negative value means that mktime() should (use timezone information and system databases to) attempt to determine whether DST is in effect at the specified time.

现在这是我的问题。我正在与来自世界各地的交易所合作(来自芝加哥、纽约、圣保罗、墨尔本、布宜诺斯艾利斯、约翰内斯堡、上海、首尔......)。我有一个表格,其中包含每个交易所的每个时区的名称:例如非洲/约翰内斯堡、美国/芝加哥、美国/温尼伯。

我正在处理的数据是某些给定期货和期权金融工具的到期日。我收到的数据始终是 UTC,我需要转换为交易所的本地时间。

长话短说,我目前的实施是在那些不应该使 DST 生效的资产的到期时间上增加 1 小时(例如,在 12 月到期且当地时间是美国/芝加哥的工具,应该添加 - 360 分钟偏移和 DST 0,而另一个在 6 月到期的同一时区应该有 -360 时区偏移 +60 DST 偏移,这将是 UTC 时间戳的 -300 偏移。我目前遇到的问题是,例如,对于 12 月份,我得到 9:30 AM 而不是 8:30 AM,因为 UTC 时间戳已经包含 dst 偏移量。

这是我进行转换的函数,它显然被破坏了:

#ifdef WIN32
#define timegm _mkgmtime
#endif

SimpleDateTime BusinessDateCalculator::UTC2TZ(const SimpleDateTime& utcDateTime, const int tz_utc_offset, const int tz_dst)

    struct tm stm;
    memset(&stm, 0, sizeof(stm));
    stm.tm_year = utcDateTime.getYear() - 1900;
    stm.tm_mon = utcDateTime.getMonth() - 1;
    stm.tm_mday = utcDateTime.getDay();
    stm.tm_hour = utcDateTime.getHour();
    stm.tm_min = utcDateTime.getMinute();
    stm.tm_sec = utcDateTime.getSecond();
    stm.tm_isdst = -1; //see note at the top of this file

    time_t tt = timegm( &stm );
    tt += (tz_utc_offset + tz_dst) * 60;

    struct tm ntm;
    memset(&ntm, 0, sizeof(ntm));
    gmtime_r( &tt, &ntm );
    return SimpleDateTime(ntm.tm_year + 1900, ntm.tm_mon + 1, ntm.tm_mday, ntm.tm_hour, ntm.tm_min, ntm.tm_sec, utcDateTime.getMillisecond());

其中 SimpleDateTime 是具有附加功能的日期时间类,例如从不同的日期/时间格式(SQL、FIX、Timeonly、DateOnly)进行转换。

我随时都有某个交易所的时区信息。我的问题可以以某种方式提供时区名称,并让timegm 执行数据库搜索并确定 DST 在美国/芝加哥时区是否在 2014 年 12 月 19 日生效,因此不添加额外的 60分钟到 UTC 时间,并且在同一日期,例如在南半球时区 America/Sao_Paulo,它一直有效到 2015 年 2 月 16 日,对于这个时间戳,它应该增加 60 分钟以获得给定日期的正确本地时间。

【问题讨论】:

我在这里没有看到明确的问题。你到底想知道什么? 我想知道如何将 2014-12-19 11:50:00.123 UTC 时间转换为特定时区的当地时间:例如美国/芝加哥时区,并获得正确的时间而不必担心 DST,因为当我转换到 America/Sao_Paulo 时,DST 将在那个时区生效。 我对任何解决方案都很满意(不一定是 timegm),因为日期和时间必须 100% 准确。例如,如果 boost 可以做到这一点,我也对该解决方案感到满意,因为我已经在我的程序中使用了一些 boost 共享指针和静态转换。我想说的是我没有可用的 DST 信息......但我必须记住,某些日期的某些时区有 DST 有效,而其他时区则没有。我总是有可用的时区偏移量和官方时区名称,但没有 DST 偏移量,对于给定的 tz,它可以是 0 或 60。 【参考方案1】:

当您通过其 IANA 标识符(例如 "America/Chicago")引用时区时,该标识符已包含所有 DST 信息和该时区的完整历史记录。至少,它在the IANA time zone database 中的原始源数据中确实如此。

您提到了Boost(在 cmets 中)。虽然 Boost 确实支持这些类型的标识符,但它错误地假设它们在时间上是永久固定的。这不是真的,因为世界上的时区一直在改变它们的偏移量和 DST 规则。考虑到美国在 2007 年更改了 DST 规则,而俄罗斯在今年晚些时候(2014 年 10 月)显着更改了时区。如果您查看Boost time zone data file,您会发现它的格式剥离了所有历史记录,只是将每个标识符映射到一组规则。因此,我建议您不要使用 Boost 进行本地时区转换。

请考虑使用ICU。除其他外,它包括时区转换功能,并使用 IANA 时区数据库的完整副本。 You can read more here, 并查看 sample code here. 我对 C++ 不是特别熟练,但您似乎可以使用 ICU 的 Calendar 类将 UTC 时间投影到特定时区的本地时间。

另一种选择是使用the GNU C library 内置的时区功能。它还使用完整的时区数据库。有一个使用 IANA/Olson 标识符 on this site 转换 UTC to 本地时间的简单示例,我也在下面发布:

/*
    C source code example: Convert UTC to local time zone, considering daylight
    savings. Uses mktime(), gmtime() and localtime(). Works for dates between
    years 1902 and 2037. Should compile and run with any recent GNU C if your
    tzdata is healthy. Written by Robert Larsson  http://rl.se

    Code put in public domain; do what you want with it.
*/

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

#define     DESTZONE    "TZ=Europe/Stockholm"       // Our destination time zone

int main(void)

    struct tm   i;
    time_t      stamp;                              // Can be negative, so works before 1970

    putenv("TZ=UTC");                               // Begin work in Greenwich …

    i.tm_year = 2009-1900;                          // Populate struct members with
    i.tm_mon  = 8-1;                                // the UTC time details, we use
    i.tm_mday = 29;                                 // 29th August, 2009 12:34:56
    i.tm_hour = 12;                                 // in this example
    i.tm_min  = 34;
    i.tm_sec  = 56;

    stamp = mktime(&i);                             // Convert the struct to a Unix timestamp

    putenv(DESTZONE);                               // Switch to destination time zone

    printf("UTC  : %s", asctime(gmtime(&stamp)));
    printf("Local: %s", asctime(localtime(&stamp)));

    return 0;                                       // That’s it, folks.

【讨论】:

谢谢马特,GNU C 并将环境变量设置为 IANA 代码对我来说非常有用。这很简单,但非常有效。我验证了。我对结果很满意。 好的,这个实现有一个严重的缺陷。它在单线程程序中运行良好,但在多线程程序中会导致随机段错误。如果 2 个并发线程正在修改变量,则可能导致不良行为。在您调用第二个 putenv 之后,另一个线程可能已经将您的变量修改为 putenv("TZ=UTC");当然,这意味着您的本地时间和格林威治时间不正确。昨天发生在我身上,我的应用程序因分段错误而崩溃。我调查了一下,这就是原因。所以当心!仅在单线程中使用此实现 互斥锁也无济于事,因为环境变量在线程之间共享,您只能为 putenv 放置一个互斥锁,默认情况下它是线程安全的(根据文档)。我正在寻找替代实现,因为这似乎对我不起作用。我读到我可以设置在进行转换时暂停其他线程的信号,并在转换完成后发出恢复信号。这当然会导致延误...... 我已经看到这可能会产生副作用(我知道它不是线程安全的),但在肺部运行时,我发现这可能会导致整个应用程序的时区发生变化(主要在日志文件,当设置 putenv 变量时另一个线程正在记录时,记录时间和日期将关闭 - 它设置为 destzone)。我现在正在使用谷歌的 CCTZ 库,它使用 std::chrono 进行转换。

以上是关于C++ timegm 将 DST 转换为将来给定时间的某个时区?的主要内容,如果未能解决你的问题,请参考以下文章

让 mktime() 在 C++ 中忽略 DST 和本地时区

Pytz 在没有 DST 的情况下将时间转换为 UTC

如何将 C++ 对象转换为 PyObject?

Python:如何在不知道 DST 是不是生效的情况下将时区感知时间戳转换为 UTC

timegm, mktime 改变 struct tm

如何将用户的本地时间转换为多伦多时区,然后在 Javascript 中根据多伦多时间检查 dst?