[英]Convert double to zoned_time using Howard Hinnant's date library
我有一个表示自 1970 年 1 月 1 日午夜(本地时区)以来的天数的双精度值和一个表示时区的字符串。 我想使用 Howard Hinnant 的日期和时区库将这些转换为 date::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>
完成。 这通常会简化您的代码,并且可能只是捕获转换错误。
<chrono>
知道如何在各种持续时间之间进行转换,但不知道 Excel epoch,因此这是我们无法避免的一种转换。 但是我们可以用比神秘常数 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_seconds
将duration
转换为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 数据库所能做到的(据我所知,这是准确的)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.