std::chrono::system_clock::now()考虑到操作系统配置的时区

std::chrono::system_clock::now() considering the OS configured time zone

本文关键字:操作 考虑到 系统配置 时区 now system chrono clock std      更新时间:2023-10-16

我正在编写运行在BusyBox嵌入式linux上的c++代码。我的代码和它的库有几个调用std::chrono::system_clock::now()来获得当前时间。

因为现在我的机器被配置为默认时区(UTC),一切都很好,进程运行和结果ok。

现在我必须把我的linux设置在不同的时区。然后我在/etc/profile框中配置:

export TZ=UTC+3

当我发出date命令和控制台时,我得到了正确的时间,但是我对std::chrono::system_clock::now()的调用仍然得到UTC时间,而不是date命令中显示的时间(正确的时间)。

我不想改变我所有的now()调用-有数百个…这导致我的进程工作的时间与控制台上设置的正确时间不同。

有没有办法解决,而不改变我的代码?有什么我遗漏的吗?

虽然标准没有指定,但std::chrono::system_clock::now()的每个实现都跟踪Unix时间,这与UTC非常接近。

如果你想将std::chrono::system_clock::now()转换为本地时间,你可以通过system_clock::to_time_tsystem_clock::time_point转换为time_t,然后通过C API(例如localtime)工作,或者你可以尝试构建在<chrono>之上的现代时区库:

https://howardhinnant.github.io/date/tz.html

你可以用它来获取当前的本地时间,像这样:

#include "tz.h"
#include <iostream>
int
main()
{
    using namespace date;
    using namespace std::chrono;
    auto t = make_zoned(current_zone(), system_clock::now());
    std::cout << t << 'n';
}

make_zoned是一个工厂函数,它返回zoned_time类型,具有system_clock支持的任何精度(例如纳秒)。是一个time_zone和一个system_clock::time_point的配对。

你可以得到一个local_time<Duration>,它是一个std::chrono::time_point,像这样:

    auto t = make_zoned(current_zone(), system_clock::now());
    auto lt = t.get_local_time();
    std::cout << lt.time_since_epoch().count() << 'n';

虽然库有一个远程API来自动下载IANA时区数据库,但可以通过使用-DHAS_REMOTE_API=0编译来禁用该API。这些都在安装说明中有详细说明。禁用远程API后,您必须手动从IANA时区数据库下载数据库(这只是一个tar.gz)。

如果你需要当前的UTC偏移量,可以这样获取:

    auto t = make_zoned(current_zone(), system_clock::now());
    auto offset = t.get_info().offset;
    std::cout << offset << 'n';

在上面的代码片段中,我利用在同一个github存储库中找到的"chrono_io.h"来打印offsetoffset的类型为std::chrono::seconds。这只是我的输出:

-14400s

(-0400)

最后,如果您想查找当前时区的IANA名称,只需输入:

std::cout << current_zone()->name() << 'n';

对我来说只是输出:

America/New_York

name()返回的类型是std::string。如果需要,可以记录该字符串,然后使用带有该名称的time_zone,即使这不是计算机当前的时区:

auto tz_name = current_zone()->name();
// ...
auto t = make_zoned(tz_name, system_clock::now()); // current local time in tz_name

4年后的更新

该库现在是c++ 20的一部分,修改如下:

  • "tz.h"的内容在<chrono>中。
  • namespace date中的名称内容在namespace std::chrono中。
  • make_zoned不是c++ 20的一部分,因为CTAD使它没有必要。您可以使用zoned_time来代替它,并推导出模板参数。

另外,一个自定义的(用户编写的)time_zone现在可以与https://howardhinnant.github.io/date/tz.html或新的c++ 20 <chrono>一起使用。这里已经编写了一个自定义time_zone的好例子来模拟POSIX时区。这可以用来更精确地回答原来的问题:

#include "date/ptz.h"
#include <cstdlib>
#include <iostream>
int
main()
{
    using namespace date;
    using namespace std;
    using namespace std::chrono;
    const char* tz = getenv("TZ");
    if (tz == nullptr)
        tz = "UTC0";
    zoned_time now{Posix::time_zone{tz}, system_clock::now()};
    cout << format("%F %T%z", now) << 'n';
}

我的TZ环境变量设置为UTC+3,这只是我的输出:

2020-09-17 10:28:06.343050-0300

指出:

  • 尽管依赖于tz.h, ptz.h是一个头文件库。不需要安装IANA tz数据库,也不需要编译tz.cpp

  • POSIX指定UTC偏移量的符号与其他所有使用的符号相反(包括POSIX的其他部分)。正偏移量是本初子午线西而不是。这反映在输出中,在本例中将UTC偏移量格式化为负值,这与POSIX strftime规范一致。

  • 如果用c++ 11或c++ 14编译,你的工具箱中没有CTAD。在本例中:

zoned_time now{Posix::time_zone{tz}, system_clock::now()};

就变成:

zoned_time<system_clock::duration, Posix::time_zone> now{Posix::time_zone{tz}, system_clock::now()};