处理c++ 11/14中的儒略历日
Handling Julian days in C++11/14
在c++中处理朱利安日最好/最简单的方法是什么?我希望能够在儒略历和公历日期之间进行转换。我有c++ 11和c++ 14。<chrono>
库可以帮助解决这个问题吗?
要在儒略历日和std::chrono::system_clock::time_point
之间进行转换,首先需要做的是找出两个历元之间的差异。
system_clock
没有正式的纪元,但事实上的标准纪元是1970-01-01 00:00:00 UTC(公历)。为了方便起见,用预言的公历来陈述儒略历日期纪元是很方便的。这个日历向后扩展了当前的规则,并包含了年份0。这使得算术更容易,但必须注意通过减去1和负(例如2BC是-1年)将年BC转换为负年。儒略历日期纪元是-4713-11-24 12:00:00 UTC(粗略地说)。
<chrono>
库可以方便地处理这个尺度上的时间单位。此外,这个日期库可以方便地在公历日期和system_clock::time_point
日期之间进行转换。要找出这两个时代的区别,只需:
constexpr
auto
jdiff()
{
using namespace date;
using namespace std::chrono_literals;
return sys_days{January/1/1970} - (sys_days{November/24/-4713} + 12h);
}
返回一个时间段为小时的std::chrono::duration
。在c++ 14中,它可以是constexpr
,我们可以使用计时持续时间文字12h
代替std::chrono::hours{12}
。
如果你不想使用日期库,这只是一个常数小时数,可以重写为更神秘的形式:
constexpr
auto
jdiff()
{
using namespace std::chrono_literals;
return 58574100h;
}
无论你怎么写,效率都是一样的。这只是一个返回常量58574100
的函数。这也可以是一个constexpr
全局变量,但是你必须泄漏你的using声明,或者决定不使用它们。
接下来,它是方便的创建一个儒略历日时钟(jdate_clock
)。由于我们需要处理至少像半天这样精确的单位,并且通常将儒略历日期表示为浮点日,因此我将jdate_clock::time_point
设置为从epoch开始的双基数天数计数:
struct jdate_clock
{
using rep = double;
using period = std::ratio<86400>;
using duration = std::chrono::duration<rep, period>;
using time_point = std::chrono::time_point<jdate_clock>;
static constexpr bool is_steady = false;
static time_point now() noexcept
{
using namespace std::chrono;
return time_point{duration{system_clock::now().time_since_epoch()} + jdiff()};
}
};
实现注:
我立即将返回值从
system_clock::now()
转换为duration
,以避免system_clock::duration
为纳秒的系统溢出。
jdate_clock
现在是一个完全符合和完全功能的<chrono>
时钟。例如,我可以找到现在的时间:
std::cout << std::fixed;
std::cout << jdate_clock::now().time_since_epoch().count() << 'n';
只输出:
2457354.310832
这是一个类型安全的系统,因为jdate_clock::time_point
和system_clock::time_point
是两个不同的类型,人们不会意外地在其中执行混合算术。然而,您仍然可以从<chrono>
库中获得所有丰富的好处,例如在jdate_clock::time_point
中添加和减去持续时间。
using namespace std::chrono_literals;
auto jnow = jdate_clock::now();
auto jpm = jnow + 1min;
auto jph = jnow + 1h;
auto tomorrow = jnow + 24h;
auto diff = tomorrow - jnow;
assert(diff == 24h);
但是如果我不小心说:
auto tomorrow = system_clock::now() + 24h;
auto diff = tomorrow - jnow;
我将得到如下错误:
error: invalid operands to binary expression
('std::chrono::time_point<std::chrono::system_clock, std::chrono::duration<long long,
std::ratio<1, 1000000> > >' and 'std::chrono::time_point<jdate_clock, std::chrono::duration<double,
std::ratio<86400, 1> > >')
auto diff = tomorrow - jnow;
~~~~~~~~ ^ ~~~~
中文:你不能从std::chrono::system_clock::time_point
中减去jdate_clock::time_point
。
但有时我做想要将jdate_clock::time_point
转换为system_clock::time_point
,反之亦然。为此,可以轻松编写几个辅助函数:
template <class Duration>
constexpr
auto
sys_to_jdate(std::chrono::time_point<std::chrono::system_clock, Duration> tp) noexcept
{
using namespace std::chrono;
static_assert(jdate_clock::duration{jdiff()} < Duration::max(),
"Overflow in sys_to_jdate");
const auto d = tp.time_since_epoch() + jdiff();
return time_point<jdate_clock, std::remove_cv_t<decltype(d)>>{d};
}
template <class Duration>
constexpr
auto
jdate_to_sys(std::chrono::time_point<jdate_clock, Duration> tp) noexcept
{
using namespace std::chrono;
static_assert(jdate_clock::duration{-jdiff()} > Duration::min(),
"Overflow in jdate_to_sys");
const auto d = tp.time_since_epoch() - jdiff();
return time_point<system_clock, std::remove_cv_t<decltype(d)>>{d};
}
实现注:
我已经添加了静态范围检查,如果您使用纳秒或基于32位的分钟作为源
time_point
的持续时间,则可能会触发。
一般的方法是获得自历元以来的duration
(duration
s是"时钟中立的"),添加或减去历元之间的偏移量,然后将duration
转换为所需的time_point
。
将使用任意精度在两个时钟的time_point
s之间进行转换,所有这些都以类型安全的方式进行。如果它能编译,它就能工作。如果您犯了一个编程错误,它会在编译时显示出来。有效的使用示例包括:
auto tp = sys_to_jdate(system_clock::now());
tp
是jdate::time_point
,除了它具有与system_clock::duration
的精度(对我来说是微秒)相同的积分表示。需要预先警告的是,如果对您来说是纳秒(gcc),则会溢出,因为纳秒只有+/- 292年的范围。
你可以像这样强制精度:
auto tp = sys_to_jdate(time_point_cast<hours>(system_clock::now()));
现在tp
是自jdate
纪元以来的整数小时数。
如果您愿意使用这个日期库,可以很容易地使用上面的实用程序将浮点儒略历日期转换为公历日期,并具有您想要的任何精度。例如:
using namespace std::chrono;
using namespace date;
std::cout << std::fixed;
auto jtp = jdate_clock::time_point{jdate_clock::duration{2457354.310832}};
auto tp = floor<seconds>(jdate_to_sys(jtp));
std::cout << "Julian day " << jtp.time_since_epoch().count()
<< " is " << tp << " UTCn";
我们使用jdate_clock
来创建jdate_clock::time_point
。然后使用jdate_to_sys
转换函数将jtp
转换为system_clock::time_point
。这将有一个表示double和一段时间。不过这并不重要。重要的是将其转换为您想要的任何表示和精度。我已经用floor<seconds>
完成了上面的操作。我也可以使用time_point_cast<seconds>
,它会做同样的事情。floor
来自日期库,总是向负无穷大方向截断,并且更容易拼写。
这将输出:
Julian day 2457354.310832 is 2015-11-27 19:27:35 UTC
如果我想四舍五入到最接近的秒,而不是地板,这将是简单的:
auto tp = round<seconds>(jdate_to_sys(jtp));
Julian date 2457354.310832 is 2015-11-27 19:27:36 UTC
或者如果我想要精确到毫秒:
auto tp = round<milliseconds>(jdate_to_sys(jtp));
Julian day 2457354.310832 is 2015-11-27 19:27:35.885 UTC
c++ 17的更新
作为Howard Hinnant的日期库的一部分,上面提到的floor
和round
函数现在也可以在命名空间std::chrono
下作为c++ 17的一部分使用。
c++ 20的更新
Howard Hinnant的日期库在很大程度上被投进了c++ 20,所以jdate_clock
现在可以完全用std::chrono
来编写了。
另外还有一个方便的std::chrono::clock_cast
功能,jdate_clock
可以参与。这有助于不同时钟的time_point
s之间的转换,甚至可以帮助实现jdate_clock
:
#include <chrono>
struct jdate_clock;
template <class Duration>
using jdate_time = std::chrono::time_point<jdate_clock, Duration>;
struct jdate_clock
{
using rep = double;
using period = std::chrono::days::period;
using duration = std::chrono::duration<rep, period>;
using time_point = std::chrono::time_point<jdate_clock>;
static constexpr bool is_steady = false;
static time_point now() noexcept;
template <class Duration>
static
auto
from_sys(std::chrono::sys_time<Duration> const& tp) noexcept;
template <class Duration>
static
auto
to_sys(jdate_time<Duration> const& tp) noexcept;
};
template <class Duration>
auto
jdate_clock::from_sys(std::chrono::sys_time<Duration> const& tp) noexcept
{
using namespace std::chrono;
return jdate_time{tp - (sys_days{November/24/-4713}+12h)};
}
template <class Duration>
auto
jdate_clock::to_sys(jdate_time<Duration> const& tp) noexcept
{
using namespace std::chrono;
return sys_time{tp - clock_cast<jdate_clock>(sys_days{})};
}
jdate_clock::time_point
jdate_clock::now() noexcept
{
using namespace std::chrono;
return clock_cast<jdate_clock>(system_clock::now());
}
jdate_time
只是按照std::chrono
提供的新的方便类型别名的样式编写的方便类型别名。它缩短了jdate_clock
实现中的一些签名,使客户端更容易用任意duration
s生成jdate_clock
的time_point
s。
jdate_clock
有两个新的static
成员函数:from_sys
和to_sys
。这些函数代替了前面的名称空间作用域函数sys_to_jdate
和jdate_to_sys
。from_sys
和to_sys
使jdate_clock
能够参与std::chrono::clock_cast
设施。
clock_cast
查找这些静态成员函数,并使用它们在jdate_clock
和之间转换每个参与clock_cast
设施的其他时钟,无论是否为时间定义的。
now()
可以简单地从system_clock::now()
返回clock_cast
的当前时间。
from_sys
简单地减去给定的基于system_clock
的time_point
和儒略历:-4713-11-24 12:00:00 UTC。返回类型必须至少精确到小时,因为epoch的精度为小时。
to_sys
可以重用from_sys
中的历元,通过使用clock_cast
查找system_clock历元处的儒略历日期:clock_cast<jdate_clock>(sys_days{})
。从儒略历日期中减去这个值,就可以找到自system_clock
纪元以来的时间。
客户端代码现在可以使用通用的clock_cast
来代替不太通用的jdate_to_sys
API:
using namespace std::chrono;
auto jtp = jdate_clock::time_point{jdate_clock::duration{2457354.310832}};
auto tp = round<milliseconds>(clock_cast<system_clock>(jtp));
std::cout << "Julian day " << jtp.time_since_epoch()
<< " is " << tp << " UTCn";
输出:
Julian date 2457354.310832d is 2015-11-27 19:27:35.885 UTC
最后请注意,尽管jdate_clock
对std::chrono::tai_clock
一无所知,但clock_cast
仍然可以与进行转换。
auto jtp = jdate_clock::time_point{jdate_clock::duration{2457354.310832}};
auto tp = round<milliseconds>(clock_cast<tai_clock>(jtp));
std::cout << "Julian day " << jtp.time_since_epoch()
<< " is " << tp << " TAIn";
输出:
Julian day 2457354.310832d is 2015-11-27 19:28:11.885 TAI
谢谢Howard提供这些有用的例子。为了使用MSVC/c++ 17成功编译,我最终使用了稍微修改过的sys_to_jdate和jdate_to_sys函数。原来的表单是使用clang 12.0.0.12000032和gcc 8.3.1编译的。
template <class Duration>
constexpr
auto
sys_to_jdate_v2(std::chrono::time_point<std::chrono::system_clock, Duration> tp) noexcept
{
static_assert(jdate_clock::duration{jdiff()} < Duration::max(),
"Overflow in sys_to_jdate");
const auto d = jdate_clock::duration{tp.time_since_epoch() + jdiff()};
return jdate_clock::time_point{d};
}
template <class Duration>
constexpr
auto
jdate_to_sys_v2(std::chrono::time_point<jdate_clock, Duration> tp) noexcept
{
static_assert(jdate_clock::duration{-jdiff()} > Duration::min(),
"Overflow in jdate_to_sys");
const auto d = std::chrono::duration_cast<std::chrono::system_clock::duration>(tp.time_since_epoch() - jdiff());
return std::chrono::system_clock::time_point{d};
}
以上修改使以下编译错误消失:
C:Program Files (x86)Microsoft Visual Studio2019BuildToolsVCToolsMSVC14.28.29333includechrono(182,23): error C2338: duration must be an instance of std::duration
(显然是由jdate_to_sys的最后一行引起的)
我对时间和日期api非常陌生,因此欢迎输入关于我修复的正确性。
- 警告处理为错误这里有什么问题
- 在C#中处理C++指针而不使用unsafe的最佳方法
- 处理多个异常集合的C++方法
- 找不到成员对象:没有名为get_event()的成员,也处理多态性和向量
- 使用流处理接收到的数据
- 获取日期异步信号安全吗?如果在信号处理程序中使用,它会导致死锁吗
- 处理小于cpu数据总线的数据类型.(c++转换为机器代码)
- 基于多个条件处理地图中的所有元素
- 如何用数字处理log(0)
- SSL上的`curl_easy_send`和`curl_asy_recv`:如何处理`CURLE_AGAIN`
- 错误处理.将系统错误代码映射到泛型
- 从文本文件中读取时钟时间和事件时间并进行处理
- 在运行时处理类型擦除的数据-如何不重新发明轮子
- 在for循环中使用auto vs decltype(vec.size())来处理字符串的向量
- 用于矢量处理的多个线程
- 对字符串进行排序时,在c++中处理sort()
- 如何处理linux终端中带有负号(-)的C++中的命令行参数
- 处理除以零会导致<csignal>意外行为
- 是否可以在c++中处理字符串流中的各个元素
- 在多个核心中处理一个HTTP请求