C++ 将 time_t 读入不同的时区然后提取 year/month..纳秒

C++ Read time_t in to different timezone then extract year/month....nanoseconds

我正在使用以下问题的答案从包含自 Epoch 以来纳秒的源中提取 year/month/day/hours/min/sec/nanoseconds:

Extract year/month/day etc. from std::chrono::time_point in C++

但是,我输入的是不同的时区。以下是我目前的代码。

  1. 如何将以下内容转换为从不同的时区读取?
  2. 我需要在执行 duration_cast 之前进行转换吗?否则hours/mins/secs个数会不会错?

我正在使用 C++17、Clang、Linux 并且更喜欢标准库。几个月后将转向 C++20,我怀疑这会简化答案。

using namespace std;
using namespace std::chrono;
using Clock = high_resolution_clock;
using TimePoint = time_point<Clock>;

const nanoseconds nanosecondsSinceEpoch(nanosecondsSinceEpochTS);
const Clock::duration since_epoch = nanosecondsSinceEpoch;
const TimePoint time_point_sinc_epoch(since_epoch);

using days = duration<int, ratio_multiply<hours::period, ratio<24> >::type>;

system_clock::time_point now = time_point_sinc_epoch;  // Do I need to handle timezone here before duration_cast?
system_clock::duration tp = now.time_since_epoch();
days d = duration_cast<days>(tp);
tp -= d;
hours h = duration_cast<hours>(tp);
tp -= h;
minutes m = duration_cast<minutes>(tp);
tp -= m;
seconds s = duration_cast<seconds>(tp);
tp -= s;

const uint64_t nanosSinceMidnight = tp.count();

time_t tt = system_clock::to_time_t(now);
tm utc_tm = *gmtime(&tt);                    // Presumably this needs to change

std::cout << utc_tm.tm_year + 1900 << '-';
std::cout << utc_tm.tm_mon + 1 << '-';
std::cout << utc_tm.tm_mday << ' ';
std::cout << utc_tm.tm_hour << ':';
std::cout << utc_tm.tm_min << ':';
std::cout << utc_tm.tm_sec << '\n';

由于您的输入和输出处于同一时区,因此时区本身变得无关紧要。这随后使这个问题变得非常容易。只需将纳秒数转换为所需的字段即可。我建议 one short public domain helper function 将天数转换为 {y, m, d} 数据结构。

#include <chrono>
#include <iostream>
#include <tuple>

// Returns year/month/day triple in civil calendar
// Preconditions:  z is number of days since 1970-01-01 and is in the range:
//                   [numeric_limits<Int>::min(), numeric_limits<Int>::max()-719468].
template <class Int>
constexpr
std::tuple<Int, unsigned, unsigned>
civil_from_days(Int z) noexcept
{
    static_assert(std::numeric_limits<unsigned>::digits >= 18,
             "This algorithm has not been ported to a 16 bit unsigned integer");
    static_assert(std::numeric_limits<Int>::digits >= 20,
             "This algorithm has not been ported to a 16 bit signed integer");
    z += 719468;
    const Int era = (z >= 0 ? z : z - 146096) / 146097;
    const unsigned doe = static_cast<unsigned>(z - era * 146097);          // [0, 146096]
    const unsigned yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365;  // [0, 399]
    const Int y = static_cast<Int>(yoe) + era * 400;
    const unsigned doy = doe - (365*yoe + yoe/4 - yoe/100);                // [0, 365]
    const unsigned mp = (5*doy + 2)/153;                                   // [0, 11]
    const unsigned d = doy - (153*mp+2)/5 + 1;                             // [1, 31]
    const unsigned m = mp + (mp < 10 ? 3 : -9);                            // [1, 12]
    return std::tuple<Int, unsigned, unsigned>(y + (m <= 2), m, d);
}

int
main()
{
    using namespace std;
    using namespace std::chrono;

    auto nanosecondsSinceEpochTS = 1592130258959736008;
    using days = duration<int, ratio_multiply<hours::period, ratio<24> >>;

    nanoseconds ns(nanosecondsSinceEpochTS);
    auto D = floor<days>(ns);
    ns -= D;
    auto H = duration_cast<hours>(ns);
    ns -= H;
    auto M = duration_cast<minutes>(ns);
    ns -= M;
    auto S = duration_cast<seconds>(ns);
    ns -= S;
    auto [y, m, d] = civil_from_days(D.count());
    cout << "y = " << y << '\n';
    cout << "m = " << m << '\n';
    cout << "d = " << d << '\n';
    cout << "H = " << H.count() << '\n';
    cout << "M = " << M.count() << '\n';
    cout << "S = " << S.count() << '\n';
    cout << "NS = " << ns.count() << '\n';
}

输出:

y = 2020
m = 6
d = 14
H = 10
M = 24
S = 18
NS = 959736008

更新

经过下面评论的讨论,发现nanosecondsSinceEpochTS是UTC,而不是我推测的America/Chicago。这意味着作为时区和纳秒计数的函数的 UTC 偏移量必须作为第一步添加到计数中。然后按照上面的指示进行操作以获取每个字段。

找到正确的偏移量是一个非常重要的过程,我不会尝试显示其代码。一种技术是为所有有问题的输入年份预先计算 {utc_timestamp, utc_offset} 的 table,然后使用输入 utc_timestamp 查找正确的偏移量。

在 C++20 中,可以简单地:

zoned_time zt{"America/Chicago", sys_time{nanoseconds{nanosecondsSinceEpochTS}}};
cout << zt << '\n';

并得到输出:

2020-06-14 05:24:18.959736008 CDT

如果需要整数字段:

auto lt = zt.get_local_time();  // look up utc offset and add it to sys_time
year_month_day ymd{floor<days>(lt)};  // run civil_from_days
hh_mm_ss tod{lt - floor<days>(lt)};  // {H, M, S, NS} since local midnight

// copy each underlying integral value
auto y = int{ymd.year()};
auto m = unsigned{ymd.month()};
auto d = unsigned{ymd.day()};
auto H = tod.hours().count();
auto M = tod.minutes().count();
auto S = tod.seconds().count();
auto NS = tod.subseconds().count();

免责声明:在我撰写本文时,还没有供应商发布 C++20 的这一部分。

POSIX 个时区的更新

如果您愿意使用这个 free, open-source, header-only library,您可以使用 POSIX 时区来避免 IANA 数据库安装问题。

看起来像:

#include "date/ptz.h"
#include <iostream>

int
main()
{
    using namespace date;
    using namespace std;
    using namespace std::chrono;

    auto nanosecondsSinceEpochTS = 1592130258959736008;
    zoned_time zt{Posix::time_zone{"CST6CDT,M3.2.0,M11.1.0"},
                  sys_time<nanoseconds>{nanoseconds{nanosecondsSinceEpochTS}}};
    cout << zt << '\n';
}

输出:

2020-06-14 05:24:18.959736008 CDT

请注意,这仅模型 America/Chicago 回到 2007 年。2007 年之前 America/Chicago 有不同的夏令时规则。