【问题标题】:Converting timezone offset from timezone name string in C/C++ in a thread-safe way以线程安全的方式从 C/C++ 中的时区名称字符串转换时区偏移量
【发布时间】:2020-05-17 19:45:53
【问题描述】:

我正在尝试实现一个可以转换给定时区名称字符串的函数:

  1. 接受输入时区字符串,例如“Australia/Melbourne”;
  2. 检查主机操作系统(假设它是 POSIX 环境)时区数据库;
  3. 以秒为单位返回一个整数时区偏移量(对于 DST 中的墨尔本,它是 36000)。

可以通过调用putenv("TZ=Australia/Melbourne") 来完成,但这是may not be thread-safe

有没有办法在没有putenv() 的情况下做到这一点,或者以某种方式让它成为线程安全的?提前致谢!

【问题讨论】:

    标签: c++ c time


    【解决方案1】:

    在 C++20 中,您将能够使用 std::chrono::tzdb::locate_zone(),它将给定的时区名称转换为您可以查询以获取偏移量的 std::chrono::time_zone 对象。

    没有处理时区的标准 C 函数。有符合 POSIX.1 的函数,但它们确实不是线程安全的。但是,根据您的操作系统,您可能能够打开时区数据库文件并手动解析它们。例如,在 Linux 上,您可以打开 /usr/share/zoneinfo/Australia/Melbourne 并根据 tzfile(5) 手册页中的规范对其进行解析。

    【讨论】:

    • 谢谢。但稍后我将在 Espressif ESP32 上运行我的代码。所以解析 tz 文件可能不是一个选项。我什至不确定是否有用于加载文件的 API...
    • tz文件很简单,ESP32功能也很强大,快速解析时区文件应该没问题。我认为问题在于你是否会在你的 ESP32 上拥有一个时区数据库。如果不存在,则必须添加自己的时区数据库。
    • 谢谢。可能在某处嵌入了“TZ 文件”,因为像 tzset() 这样的 POSIX API 确实适用于 ESP32。但是如何直接访问和解析就成问题了。
    【解决方案2】:

    由于编译器还不支持 C++20 std::chrono 库的所有功能,我将编写一个使用 Howard Hinnant 的 datehttps://github.com/HowardHinnant/date 库的解决方案。 C++20 引入了std::chrono::zoned_time,你可以使用它来实现你想要的。

    #include "date/tz.h"
    #include <iostream>
    
    int main()
    {
        auto current_time = std::chrono::system_clock::now();
        auto la = date::zoned_time{"America/Los_Angeles", current_time};
        auto sy = date::zoned_time{"Australia/Sydney", current_time};
        std::cout << date::format("%T\n", sy.get_local_time() - la.get_local_time());
    }
    

    以上示例将为您提供两个时区之间的差异。此外,如果您想获得当前操作系统的时区,您可以使用我希望将来编译器支持的东西 - std::chrono::time_zone::name

    【讨论】:

    • 这种方法通常可以正常工作,但本地时间与 UTC(此处为第三时区)的偏移量在 lasy 之间变化,减法会产生不正确的差异。
    • 对于用于构造两个zoned_times 的system_clock::time_point 的任何值,差异都是正确的。
    • 这些zoned_time 中的任何一个都可以用"Etc/UTC" 构造,以在system_clock::time_point 指示的时间点获取另一个zoned_time 的UTC 偏移量。而这个 C++20 chrono 库的预览版是 100% 线程安全的。它确实 在后台操纵诸如TZ之类的全局变量。
    • @HowardHinnant 感谢您对这一点的解释比我做得更好。顺便说一句,我钦佩和尊重你的工作
    【解决方案3】:

    除了 OP 的线程问题,还有一个考虑:

    “时区名称字符串的时区偏移量”通常是不够的,因为偏移量因全年的数据/时间而异。

    即使所选区域没有发生年度日光调整,偏移量也会因区域的历史而有所不同。

    long offset(tz_name) 不够,long offset(tz_name, time_t t) 是必需的。

    【讨论】:

    • 此答案与您在@NutCracker 答案下的评论相结合,表明您了解问题,但忽略了 NutCracker 的答案没有您声称的缺陷。它使用current_time 计算UTC 偏移量。更一般地说,可以在该计算中使用system_clock::time_point 的任何值。
    • @HowardHinnant 我想我们将不得不不同意,因为当本地时间的偏移量在 lasy 之间变化时,我仍然断言 NutCracker 答案失败。
    • 你能举个具体的例子吗?例如,NutCracker 的程序当前指示相差 19 小时。如果将current_time 更改为sys_days{March/9/2020},则差值为18h,如果将current_time 更改为sys_days{April/5/2020},则差值为17h。正确答案是什么?或者您选择的时间的正确答案是什么?
    • @HowardHinnant sy.get_local_time()la.get_local_time() 的结果表示时间,就好像它们是其追溯时区“America/Los_Angeles”和“Australia/Sydney”的当地时间。到目前为止没有问题。两个当地时间的减法是“好像”这些当地时间是一个共同的时区——当前的当地时区。如果那个本地时区在这两个本地时间之间是连续的,瞧,我们就有了悉尼和洛杉矶的追捧差异。根据您的方法……。
    • ... 如果本地时区在这两个本地时间之间是不连续的(例如由于时区更改其 UTC 偏移量而移动一个小时并且与洛杉矶的悉尼无关),则差异是不正确的结果.
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-20
    • 2021-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多