使用霍华德·欣南特的日期库将双倍转换为zoned_time

Convert double to zoned_time using Howard Hinnant's date library

本文关键字:转换 time zoned 日期 霍华德      更新时间:2023-10-16

我有一个双精度表示自午夜(本地时区)1970 年 1 月 1 日以来的时间(以天为单位),还有一个字符串表示时区。我想将这些转换为 日期::zoned_time 使用霍华德·欣南特的日期和时间区库。

背景是我需要将日期时间与双精度值相互转换,以便在分析库中使用。我还将从本地或用户指定的时区的 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的结果具有错误的类型。此外,我不相信它正确地将输入时间(以天为单位)映射到输出日期时间,因为闰秒和夏令时发生变化的天数。

规格:

  • 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>的方法。 它只是一个方便的类型定义。

  • ddays是一个自定义duration单位,表示 1 天,double。 这便于将标量输入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表示美国/芝加哥当地时间的度量,而不是 UTC 的度量。 这会将纪元固定为 1899-12-30 00:00:00 在美国/芝加哥,而不是 1899-12-30 00:00:00 UTC。

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

  • 结果确实考虑了芝加哥的夏令时,包括多年来夏令时的变化,这是 IANA 数据库所能做到的(据我所知,这是确切的)。