【问题标题】:Convert double to zoned_time using Howard Hinnant's date library使用 Howard Hinnant 的日期库将 double 转换为 zoned_time
【发布时间】:2019-07-16 10:22:54
【问题描述】:

我有一个 double 表示自 1970 年 1 月 1 日午夜(本地时区)以来的天数和一个表示时区的字符串。我想将这些转换为 日期::分区时间 使用 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 的结果类型错误,它无法编译。另外,由于闰秒和夏令时更改的日子,我不相信它将以天为单位的输入时间正确映射到输出日期时间。

【问题讨论】:

    标签: c++ date datetime


    【解决方案1】:

    规格:

    • 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&lt;seconds&gt;(t)。但是还有更多的乐趣......

    &lt;chrono&gt; 为您处理转换而生死存亡。任何时候您自己进行转换时,您都应该选择删除这些转换,而让&lt;chrono&gt; 进行这些转换。这通常会简化您的代码,并且它可能只是捕获转换错误。

    &lt;chrono&gt; 知道如何在各种持续时间之间转换,但不知道 Excel 时期,所以这是我们无法避免的一种转换。但是我们可以用比神秘常数 25569.0 更高级的语言来表达那个时代。

    所以,从顶部开始:

    • date::zoned_seconds 是写date::zoned_time&lt;std::chrono::seconds&gt; 的更简单的方式。它只是一个方便的 typedef。

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

    • 历元差是 1970-01-01 和 1899-12-30 之间的时间量。单位将是我编码的天数,但这是一个不重要的细节。 &lt;chrono&gt; 会为您处理单位。

    • 我使用 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 代表美国/芝加哥本地 时间的度量,而不是以 UTC 衡量。这将美国/芝加哥的纪元固定为 1899-12-30 00:00:00,而不是 1899-12-30 00:00:00 UTC。

    • 结果不考虑闰秒。这是正确的做法,因为 Excel 和 system_clock 都没有。几乎所有基于计算机的计时协议都不计算闰秒。这是Unix Time的一个很好的描述。如果你想转换成一个计算闰秒的系统,这个库也可以做到。它被称为utc_clock/utc_time

    • 结果确实将芝加哥的夏令时考虑在内,包括多年来对夏令时规则的更改,尽最大努力 IANA database 可以做到(据我所知,这是准确的)。

    【讨论】:

    • 好的,我已将时代更改为在美国/芝加哥指定。我不知道 Excel 的那个细节,谢谢。
    • Excel 将 1900 年视为闰年这一事实由 1899-12-31 纪元处理。 Excel 声称纪元是 1900-01-01,而 1900 年是闰年。相当于说纪元是1899-12-31,1900不是闰年。
    • 只需更改返回类型的精度(例如zoned_time&lt;milliseconds&gt;)和round 表达式中的精度(例如round&lt;milliseconds&gt;(...)),即可轻松更改结果的精度。在 C++14 中,可以使用 auto 计算返回类型,因此您只需在返回表达式的两个位置指定精度。在 C++17 中,CTAD 将把它降低到返回表达式中的一个位置。而在 C++20 中,命名空间 date 中的所有内容都移动到命名空间 std::chrono
    • 再次感谢您提供如此有用的答案。很高兴知道如何更改精度 - 我可能很快就会需要这个
    • 你又教我了! (谢谢!)。我将更新我的答案以包含您的修复。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-21
    • 1970-01-01
    • 2020-10-06
    • 2013-12-15
    • 2017-07-28
    • 2013-10-14
    • 1970-01-01
    相关资源
    最近更新 更多