将uint64时间转换为从1月1日1601到日期时间字符串的100纳秒间隔的数量

时间:2013-12-04 19:40:59

标签: c++ datetime

我有一个时间值,开始存储在uint64中。该值是自160年1月1日以来100纳秒间隔的数量。我了解Windows FILETIME类型使用此格式。我需要将这个uint64转换为一些对象,我可以用字符串格式读取年份,日期,小时,分钟等,这样我就可以构建一个自定义日期时间字符串。

如何将uint64转换为有用的东西。我尝试将uint64转换为文件时的所有方法都会出现编译错误,例如

uint64 big_int;  // this will end up containing the nanosecond interval time
.
.
.

FILETIME t = static_cast<FILETIME>(big_int);

1 个答案:

答案 0 :(得分:8)

我认为使用chrono-Compatible Low-Level Date Algorithms<chrono> Utilities和C ++ 11 <chrono>设施来执行此计算是一项挑战。

chrono-Compatible Low-Level Date Algorithms我们需要:

template <class Int>
constexpr
Int
days_from_civil(Int y, unsigned m, unsigned d) noexcept;

将y / m / d三元组转换为1970-01-01之前/之后的天数,并且:

template <class Int>
constexpr
std::tuple<Int, unsigned, unsigned>
civil_from_days(Int z) noexcept;

将1970-01-01之前/之后的天数转换为y / m / d三联。

<chrono> Utilities我们需要:

template <class To, class Rep, class Period>
To
floor(const std::chrono::duration<Rep, Period>& d);

std::chrono::duration_cast非常相似,只是它向负无穷大舍入而不是向零(对于舍入负值有所不同)。

首先,我们需要一个datetime对象来保存我们想要的所有信息:

struct datetime
{
    int year;
    int month;
    int day;
    int hour;
    int minute;
    int second;
    int nanoseconds;
};

打印设施便于演示(应根据需要定制):

std::ostream&
operator<<(std::ostream& os, const datetime& dt)
{
    char fill = os.fill();
    os.fill('0');
    os << dt.year << '-';
    os << std::setw(2) << dt.month << '-';
    os << std::setw(2) << dt.day << " T ";
    os << std::setw(2) << dt.hour << ':';
    os << std::setw(2) << dt.minute << ':';
    os << std::setw(2) << dt.second << '.';
    os << std::setw(9) << dt.nanoseconds;
    os.fill(fill);
    return os;
}

我选择了nanoseconds的单位作为datetime的精度,这有点矫枉过正。您可以轻松地将其设置为所需的任何内容。

接下来,可以方便地声明两个自定义std::chrono::duration

代表天数(恰好24小时):

typedef std::chrono::duration
        <
            int,
            std::ratio_multiply<std::ratio<24>, std::chrono::hours::period>
        > days;

一个代表问题陈述的100纳秒间隔:

typedef std::chrono::duration
        <
            std::int64_t, std::ratio_multiply<std::ratio<100>, std::nano>
        > wtick;

现在,我们拥有了将std::uint64_t转换为datetime所需的所有工具,(此问题的主题):

datetime
datetime_from_wtick(wtick t)
{
    // Get the number of days between 1601-01-01 and 1970-01-01
    constexpr days epoch{days_from_civil(1601, 1, 1)};
    // eppoch is a negative number of days, so add it to get time since 1970-01-01
    wtick utc_time = t + epoch;
    days d = floor<days>(utc_time);  // Get #days before/since 1970-01-01
    datetime r;
    // Split #days into year/month/day
    std::tie(r.year, r.month, r.day) = civil_from_days(d.count());
    utc_time -= d;  // Subtract off #days to leave hours:minutes:seconds.fractional
    auto h = floor<std::chrono::hours>(utc_time);  // Get hours
    r.hour = h.count();
    utc_time -= h;  // Subtract off hours to leave minutes:seconds.fractional
    auto m = floor<std::chrono::minutes>(utc_time);  // Get minutes
    r.minute = m.count();
    utc_time -= m;  // Subtract off minutes to leave seconds.fractional
    auto s = floor<std::chrono::seconds>(utc_time);  // Get seconds
    r.second = s.count();
    utc_time -= s;  // Subtract off seconds to leave fractional seconds
    std::chrono::nanoseconds ns = utc_time;  // Get nanoseconds
    r.nanoseconds = ns.count();
    return r;
}

这个解决方案的工作原理是简单地将纪元转移到1970-01-01,然后截断天数,然后截断小时数,然后是分钟数等,直到我们下降到几分之一秒。天数进一步分为三个:y / m / d。

改变时代的唯一原因是利用chrono-Compatible Low-Level Date Algorithms中完全调试,高性能和极其强大的公式。

此解决方案忽略了leap seconds的存在。我的猜测是Windows FILETIME也是如此。但是,如果没有,您可以通过构建一个将datetime映射到#seconds以添加的表来考虑leap seconds。例如,如果datetime超出当前leap seconds表,则添加25秒以获得now和1601-01-01之间的“真实”差异。我的经验是,计算机正在使用Unix Time,这只是leap seconds

用以下方式测试全部:

int
main()
{
    std::cout << datetime_from_wtick(wtick(130330211760000005)) << '\n';
}

应该给出:

2014-01-01 T 03:39:36.000000500

用于表示UTC时区中的datetime

这篇文章有两个主要观点:

  1. 使用正确,<chrono>可用于虚拟消除所有转换常量。此示例仅介绍一些用于定义自定义std::chrono:duration的转换常量和一个纪元转换。在此之后,机器正常工作,消除了常见的错误。

  2. chrono-Compatible Low-Level Date Algorithms有一些非常有用和有效的算法。

  3. <强>更新

    使用无符号持续时间确实容易出错,并且wtick的63位有足够的范围。已将wtick::rep更改为int64_t。这会产生更好的结果:

    int
    main()
    {
        std::cout << datetime_from_wtick(wtick(0)) << '\n';
        std::cout << datetime_from_wtick(wtick(864000000000)) << '\n';
        std::cout << datetime_from_wtick(wtick(130330211760000005)) << '\n';
        std::cout << datetime_from_wtick(wtick(0x7FFFFFFFFFFFFFFF)) << '\n';
    }
    
    1601-01-01 T 00:00:00.000000000
    1601-01-02 T 00:00:00.000000000
    2014-01-01 T 03:39:36.000000500
    30828-09-14 T 02:48:05.477580700