使用Howard Hinnant的日期库将double转换为zoned_time

时间:2019-07-16 10:22:54

标签: c++ date datetime

我有一个表示从1970年1月1日午夜(本地时区)开始的天数的双精度数,和一个表示时区的字符串。我想将它们转换为     日期:: zoned_time 使用Howard Hinnant的日期和时区库。

背景是我需要将日期时间与双精度日期进行转换,以便在分析库中使用。我还将在本地或用户指定的时区中从excel中获得两倍的日期时间。

这是我尝试过的

using namespace date;
using namespace std::chrono;
typedef date::zoned_time<std::chrono::seconds> datetime;
const double UNIX_MINUS_EXCEL_EPOCH = 25569.0;
const double SECONDS_PER_DAY = 24.0 * 60.0 * 60.0;
datetime timePointFromDouble(double x)
{
    double seconds = (x - UNIX_MINUS_EXCEL_EPOCH) * SECONDS_PER_DAY;
    system_clock::duration d = duration_cast<system_clock::duration>(duration<double>(seconds));
    system_clock::time_point t = system_clock::time_point(d);
    auto xx = make_zoned("America/Chicago", t);

    return xx;
}

它无法编译,因为make_zoned的结果类型错误。另外,由于leap秒和更改夏令时的天数,我不能说它正确地将以天为单位的输入时间映射到输出日期时间。

1 个答案:

答案 0 :(得分:2)

规格:

  • x是美国/芝加哥自1899-12-30 00:00:00以来的天数。

解决方案:

using datetime = date::zoned_seconds;

datetime
timePointFromDouble(double x)
{
    using namespace date;
    using namespace std::chrono;
    using ddays = duration<double, days::period>;
    constexpr auto excel_epoch = local_days{1_d/January/1970} -
                                 local_days{30_d/December/1899};
    return datetime{"America/Chicago",
             local_seconds{round<seconds>(ddays{x} - excel_epoch)}};
}

说明:

您的版本无法编译的原因是由于转换为system_clock::time_point,实际上它的精度为微秒或更好。但是您的结果类型具有几秒钟的精度,因此该库拒绝将您的高精度t隐式截断为您的低精度xx

解决此问题的最简单方法是time_point_cast<seconds>(t)。但是还有更多的乐趣...

<chrono>通过为您处理转化而生死。任何时候您自己进行转换时,都应该选择删除这些转换,而选择让<chrono>进行转换。通常,这将简化您的代码,并且可能仅捕获转换错误。

<chrono>知道如何在各种持续时间之间进行转换,但不了解Excel时期,因此这是我们无法避免的转换。但是我们可以用比神秘的常数25569.0更高级的语言来表达那个时代。

所以,从顶部开始:

  • date::zoned_seconds是一种写date::zoned_time<std::chrono::seconds>的简单方法。这只是一个方便的typedef。

  • ddays是一个自定义的duration单位,用double代表1天。这对于将标量输入x直接转换为<chrono>持续时间很方便。最好尽快进入<chrono>类型的系统。

  • 时代差异是1970-01-01和1899-12-30之间的时间量。按照我的编码,单位将是几天,但这不是一个重要的细节。 <chrono>为您照顾单位。

  • 我正在使用local_days而不是sys_days来计算历元差。这是一个很大的象征性手势,用于传达该纪元是本地时间,而不是UTC。它不会影响所计算常数的实际值。

  • 由于您使用问题的措辞方式,我假设您希望在代码中按日,月,年排序。这是一个纯粹的风格选择。

  • 如果使用C ++ 11编写,则必须将excel_epoch设置为const而不是constexpr。区别在于,C ++ 11必须在运行时计算该常数,而C ++ 14必须稍后在编译时计算该常数。

  • 从基于双精度的单位转换为基于整数的单位时,我喜欢使用round而不是duration_cast。区别在于round选择了最接近的可表示值,而duration_cast则从零开始截断为最接近的可表示值。 round策略更可能导致双精度表示形式和整数表示形式之间的往返转换稳定,而由于双精度表示形式中的舍入误差,截断更有可能暴露一次性的差异。

  • 最后一行必须明确地使我们从基于双精度的单位转换为基于整数的单位,并且必须指定seconds以与返回类型匹配,但不必担心将天数转换为秒。

  • 最后一行使用local_secondsduration转换为time_point,因为此duration表示 local 中的一个量度美国/芝加哥时间,而不是UTC时间。相对于1899-12-30 00:00:00 UTC,这将美国/芝加哥的时代固定为1899-12-30 00:00:00。

  • 结果不考虑leap秒。这是正确的做法,因为Excel和system_clock都没有。几乎所有基于计算机的计时协议都不算leap秒。这是Unix Time的很好描述。如果您要转换为计数leap秒的系统,则该库也可以执行此操作。它称为utc_clock/utc_time

  • 该结果确实考虑了芝加哥的夏令时,包括多年来对夏令时规则的更改,尽其所能IANA database(据我所知这是确切的) )。