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 和本地时区