在 C++11/14 中处理 Julian 日期
Handling Julian dates in C++11/14
在 C++ 中 best/easiest 处理 Julian dates 的方法是什么?我希望能够在 Julian 日期和 Gregorian 日期之间进行转换。我有 C++11 和 C++14。 <chrono>
库可以帮助解决这个问题吗?
要在 Julian date 和 std::chrono::system_clock::time_point
之间转换,首先需要做的是找出纪元之间的差异。
system_clock
没有官方纪元,但事实上的标准纪元是 1970-01-01 00:00:00 UTC(公历)。为方便起见,根据 proleptic Gregorian calendar 来表述 Julian date 纪元很方便。此日历向后扩展了当前规则,并包括了第 0 年。这使得算术更容易,但必须注意通过减去 1 和取反(例如 2BC 是第 -1 年)将 BC 年转换为负数年。 Julian date 纪元是 -4713-11-24 12:00:00 UTC(粗略地说)。
<chrono>
库可以方便地处理这种规模的时间单位。此外,this date library 可以方便地在公历日期和 system_clock::time_point
之间进行转换。要找到这两个时期之间的差异很简单:
constexpr
auto
jdiff()
{
using namespace date;
using namespace std::chrono_literals;
return sys_days{jan/1/1970} - (sys_days{nov/24/-4713} + 12h);
}
这个 return 是一个 std::chrono::duration
的时间段。在 C++14 中,这可以是 constexpr
并且我们可以使用计时持续时间文字 12h
而不是 std::chrono::hours{12}
.
如果您不想使用 date library,这只是一个固定的小时数,可以重写为这种更神秘的形式:
constexpr
auto
jdiff()
{
using namespace std::chrono_literals;
return 58574100h;
}
不管怎么写,效率都是一样的。这只是一个 return 常量 58574100
的函数。这也可以是一个 constexpr
全局的,但是你必须泄露你的 using 声明,或者决定不使用它们。
接下来创建 Julian 日期时钟很方便 (jdate_clock
)。由于我们需要处理至少半天这样精细的单位,并且通常将儒略日期表示为浮点天数,因此我将 jdate_clock::time_point
设为从纪元开始的双倍天数:
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()};
}
};
执行说明:
I converted the return from system_clock::now()
to duration
immediately to avoid overflow for those systems where system_clock::duration
is nanoseconds.
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>
库,例如添加和减去持续时间 to/from 您的 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, 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, decltype(d)>{d};
}
执行说明:
I've added static range checking which is likely to fire if you use nanoseconds or a 32bit-based minute as a duration in your source time_point
.
一般的方法是得到duration
自epoch(duration
s是“clock neutral”),加上或减去epoch之间的偏移量,然后转换duration
到想要的 time_point
.
这些将使用 any 精度在两个时钟的 time_point
之间转换,全部以类型安全的方式进行。如果它编译,它就可以工作。如果你犯了一个编程错误,它会在编译时出现。有效示例使用包括:
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
纪元以来的小时数。
如果您愿意使用 this date library,可以很容易地使用上面的实用程序将浮点儒略日期转换为公历日期,并具有您想要的任何精度。例如:
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 date " << jtp.time_since_epoch().count()
<< " is " << tp << " UTC\n";
我们使用我们的 jdate_clock
创建一个 jdate_clock::time_point
。然后我们使用我们的 jdate_to_sys
转换函数将 jtp
转换为 system_clock::time_point
。这将有一个双倍的表示和一个小时的时间段。但这并不重要。 重要的是将其转换为您想要 的任何表示形式和精度。我已经用 floor<seconds>
完成了上面的操作。我也可以使用 time_point_cast<seconds>
并且它会做同样的事情。 floor
来自 the date library,总是向负无穷截断,并且更容易拼写。
这将输出:
Julian date 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 date 2457354.310832 is 2015-11-27 19:27:35.885 UTC
C++17 更新
上面提到的作为 Howard Hinnant's date library 一部分的 floor
和 round
函数现在也作为 C++17 的一部分在命名空间 std::chrono
下可用。
C++20 更新
Howard Hinnant's date library 在很大程度上被投票加入了 C++20,因此 jdate_clock
现在可以完全按照 std::chrono
.
来编写
另外还有一个方便的std::chrono::clock_cast
功能,jdate_clock
可以参与。这方便了time_point
不同时钟之间的转换,甚至可以帮助实现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
制作 jdate_clock
的 time_point
。
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()
可以简单地clock_cast
从system_clock::now()
到return当前时间。
from_sys
简单地减去给定的基于 system_clock
的 time_point
和 Julian 纪元:-4713-11-24 12:00:00 UTC。 return 类型必须至少与小时一样精细,因为纪元的精度为小时。
to_sys
可以重用 from_sys
中的纪元,方法是使用 clock_cast
找到 system_clock 纪元的儒略日期:clock_cast<jdate_clock>(sys_days{})
。这是从 Julian 日期中减去以找到自 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 date " << jtp.time_since_epoch()
<< " is " << tp << " UTC\n";
输出:
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 date " << jtp.time_since_epoch()
<< " is " << tp << " TAI\n";
输出:
Julian date 2457354.310832d is 2015-11-27 19:28:11.885 TAI
在 C++ 中 best/easiest 处理 Julian dates 的方法是什么?我希望能够在 Julian 日期和 Gregorian 日期之间进行转换。我有 C++11 和 C++14。 <chrono>
库可以帮助解决这个问题吗?
要在 Julian date 和 std::chrono::system_clock::time_point
之间转换,首先需要做的是找出纪元之间的差异。
system_clock
没有官方纪元,但事实上的标准纪元是 1970-01-01 00:00:00 UTC(公历)。为方便起见,根据 proleptic Gregorian calendar 来表述 Julian date 纪元很方便。此日历向后扩展了当前规则,并包括了第 0 年。这使得算术更容易,但必须注意通过减去 1 和取反(例如 2BC 是第 -1 年)将 BC 年转换为负数年。 Julian date 纪元是 -4713-11-24 12:00:00 UTC(粗略地说)。
<chrono>
库可以方便地处理这种规模的时间单位。此外,this date library 可以方便地在公历日期和 system_clock::time_point
之间进行转换。要找到这两个时期之间的差异很简单:
constexpr
auto
jdiff()
{
using namespace date;
using namespace std::chrono_literals;
return sys_days{jan/1/1970} - (sys_days{nov/24/-4713} + 12h);
}
这个 return 是一个 std::chrono::duration
的时间段。在 C++14 中,这可以是 constexpr
并且我们可以使用计时持续时间文字 12h
而不是 std::chrono::hours{12}
.
如果您不想使用 date library,这只是一个固定的小时数,可以重写为这种更神秘的形式:
constexpr
auto
jdiff()
{
using namespace std::chrono_literals;
return 58574100h;
}
不管怎么写,效率都是一样的。这只是一个 return 常量 58574100
的函数。这也可以是一个 constexpr
全局的,但是你必须泄露你的 using 声明,或者决定不使用它们。
接下来创建 Julian 日期时钟很方便 (jdate_clock
)。由于我们需要处理至少半天这样精细的单位,并且通常将儒略日期表示为浮点天数,因此我将 jdate_clock::time_point
设为从纪元开始的双倍天数:
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()};
}
};
执行说明:
I converted the return from
system_clock::now()
toduration
immediately to avoid overflow for those systems wheresystem_clock::duration
is nanoseconds.
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>
库,例如添加和减去持续时间 to/from 您的 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, 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, decltype(d)>{d};
}
执行说明:
I've added static range checking which is likely to fire if you use nanoseconds or a 32bit-based minute as a duration in your source
time_point
.
一般的方法是得到duration
自epoch(duration
s是“clock neutral”),加上或减去epoch之间的偏移量,然后转换duration
到想要的 time_point
.
这些将使用 any 精度在两个时钟的 time_point
之间转换,全部以类型安全的方式进行。如果它编译,它就可以工作。如果你犯了一个编程错误,它会在编译时出现。有效示例使用包括:
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
纪元以来的小时数。
如果您愿意使用 this date library,可以很容易地使用上面的实用程序将浮点儒略日期转换为公历日期,并具有您想要的任何精度。例如:
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 date " << jtp.time_since_epoch().count()
<< " is " << tp << " UTC\n";
我们使用我们的 jdate_clock
创建一个 jdate_clock::time_point
。然后我们使用我们的 jdate_to_sys
转换函数将 jtp
转换为 system_clock::time_point
。这将有一个双倍的表示和一个小时的时间段。但这并不重要。 重要的是将其转换为您想要 的任何表示形式和精度。我已经用 floor<seconds>
完成了上面的操作。我也可以使用 time_point_cast<seconds>
并且它会做同样的事情。 floor
来自 the date library,总是向负无穷截断,并且更容易拼写。
这将输出:
Julian date 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 date 2457354.310832 is 2015-11-27 19:27:35.885 UTC
C++17 更新
上面提到的作为 Howard Hinnant's date library 一部分的 floor
和 round
函数现在也作为 C++17 的一部分在命名空间 std::chrono
下可用。
C++20 更新
Howard Hinnant's date library 在很大程度上被投票加入了 C++20,因此 jdate_clock
现在可以完全按照 std::chrono
.
另外还有一个方便的std::chrono::clock_cast
功能,jdate_clock
可以参与。这方便了time_point
不同时钟之间的转换,甚至可以帮助实现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
制作 jdate_clock
的 time_point
。
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()
可以简单地clock_cast
从system_clock::now()
到return当前时间。
from_sys
简单地减去给定的基于 system_clock
的 time_point
和 Julian 纪元:-4713-11-24 12:00:00 UTC。 return 类型必须至少与小时一样精细,因为纪元的精度为小时。
to_sys
可以重用 from_sys
中的纪元,方法是使用 clock_cast
找到 system_clock 纪元的儒略日期:clock_cast<jdate_clock>(sys_days{})
。这是从 Julian 日期中减去以找到自 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 date " << jtp.time_since_epoch()
<< " is " << tp << " UTC\n";
输出:
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 date " << jtp.time_since_epoch()
<< " is " << tp << " TAI\n";
输出:
Julian date 2457354.310832d is 2015-11-27 19:28:11.885 TAI