在自定义日期时间中执行日期时间算法 class
Performing date time arithmetic in custom date time class
我有一个非常天真的结构,表示我想对其执行算术的日期时间:
struct MyDateTime
{
MyDateTime(int year, int month, int day, uint64_t nanos);
int year;
int month;
int day;
uint64_t nanosSinceMidnight;
};
我希望能够从另一个 MyDateTime
.
add/subtract MyDateTime
我的想法是让我的结构成为一个包装器并在内部使用 Boost。
我看了Boost Posix时间:
https://www.boost.org/doc/libs/1_55_0/doc/html/date_time/examples.html#date_time.examples.time_math
但这似乎只是在计算时间(不考虑日期部分)。
我查看了 Boost Gregorian Date,但在构造函数中看不到任何时间参数。
使用 Boost 的最简单方法是什么,以便我可以执行日期时间算术?
您现在可能已经意识到,无法添加日期。
日期和时间戳在数学上类似于 tensors,因为它们的不同类型在不同的域中。
当您评论说 time_duration
不包括日期时,您仍然说得有道理。
因为time_duration
可能是时域差值类型(差值类型ptime
)但是我们需要一个类比ptime
的日期部分,即boost::gregorian::date
.
Boost 公历日期基本上是 (yyyy,mm,dd) 的元组。因此自然差异类型只是 带符号的整数天数 。这就是 * boost::gregorian::date_duration
是什么:
boost::gregorian::date_duration x = date{} - date{};
boost::posix_time::time_duration y = ptime{} - ptime{};
Because that type is implemented in the Gregorian module you will get correct differences, even with special cases like leap days and other anomalies: https://www.calendar.com/blog/gregorian-calendar-facts/
因此,您实际上可以将该类型用作差异类型,仅用于 ymd 部分。
简化
好消息是,您不必费心:boost::posix_time::ptime
封装了完整的 boost::gregorian::date
,因此当您从 ptime
中减去 boost::posix_time::time_duration
时,您将已经得到加密的天数:
#include <boost/date_time.hpp>
int main() {
auto now = boost::posix_time::microsec_clock::local_time();
auto later = now + boost::posix_time::hours(3);
auto tomorrow = later + boost::gregorian::days(1);
auto ereweek = later - boost::gregorian::weeks(1);
std::cout << later << " is " << (later - now) << " later than " << now
<< std::endl;
std::cout << tomorrow << " is " << (tomorrow - later) << " later than " << later
<< std::endl;
std::cout << ereweek << " is " << (ereweek - now) << " later than " << now
<< std::endl;
}
从当前时间开始,我们加 3 小时 1 天,然后减去一周。它打印:Live On Coliru:
2021-Mar-28 01:50:45.095670 is 03:00:00 later than 2021-Mar-27 22:50:45.095670
2021-Mar-29 01:50:45.095670 is 24:00:00 later than 2021-Mar-28 01:50:45.095670
2021-Mar-21 01:50:45.095670 is -165:00:00 later than 2021-Mar-27 22:50:45.095670
注意24h
是1天,-165h是(7*24 - 3)小时前。
公历模块中有很多智能:
std::cout << date{2021, 2, 1} - date{2020, 2, 1} << std::endl; // 366
std::cout << date{2020, 2, 1} - date{2019, 2, 1} << std::endl; // 365
考虑到闰日。但也知道上下文中日历月的不同长度:
auto term = boost::gregorian::months(1);
for (date origin : {date{2021, 2, 17}, date{2021, 3, 17}}) {
std::cout << ((origin + term) - origin) << std::endl;
};
分别打印 28 和 31。
将其应用于您的类型
我建议保持库差异类型,因为 很明显您之前没有考虑过您需要一个。通过简单地创建一些相互转换,您可以吃蛋糕也可以吃:
struct MyDateTime {
MyDateTime(int year = 1970, int month = 1, int day = 1, uint64_t nanos = 0)
: year(year),
month(month),
day(day),
nanosSinceMidnight(nanos) {}
operator ptime() const {
return {date(year, month, day),
microseconds(nanosSinceMidnight / 1'000)};
}
explicit MyDateTime(ptime const& from)
: year(from.date().year()),
month(from.date().month()),
day(from.date().day()),
nanosSinceMidnight(from.time_of_day().total_milliseconds() * 1'000) {}
private:
int year;
int month;
int day;
uint64_t nanosSinceMidnight;
};
Now, I would question the usefulness of keeping your MyDateTime
type, but I realize legacy code exists, and sometimes you require a longer time period while moving away from it.
纳秒
默认情况下不启用纳秒精度。您需要[选择使用它](https://www.boost.org/doc/libs/1_58_0/doc/html/date_time/details.html#boost-common-heading-doc-spacer:~:text=To%20use%20the%20alternate%20resolution%20(96,the%20variable%20BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG%20must%20be%20defined)。
在下面的示例中,我这样做了。
注意项目中的所有翻译单元都使用定义,否则会导致 ODR violations。
现场演示
也增加了一些便利 operator<<
:
#define BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG
#include <boost/date_time.hpp>
#include <vector>
using boost::posix_time::ptime;
using boost::gregorian::date;
using boost::posix_time::nanoseconds;
struct MyDateTime {
MyDateTime(MyDateTime const&) = default;
MyDateTime& operator=(MyDateTime const&) = default;
MyDateTime(int year = 1970, int month = 1, int day = 1, uint64_t nanos = 0)
: year(year),
month(month),
day(day),
nanosSinceMidnight(nanos) {}
operator ptime() const {
return {date(year, month, day), nanoseconds(nanosSinceMidnight)};
}
/*explicit*/ MyDateTime(ptime const& from)
: year(from.date().year()),
month(from.date().month()),
day(from.date().day()),
nanosSinceMidnight(from.time_of_day().total_nanoseconds()) {}
private:
friend std::ostream& operator<<(std::ostream& os, MyDateTime const& dt) {
auto save = os.rdstate();
os << std::dec << std::setfill('0') << std::setw(4) << dt.year << "/"
<< std::setw(2) << dt.month << "/" << std::setw(2) << dt.day << " +"
<< dt.nanosSinceMidnight;
os.setstate(save);
return os;
}
int year;
int month;
int day;
uint64_t nanosSinceMidnight;
};
int main() {
namespace g = boost::gregorian;
namespace p = boost::posix_time;
using p::time_duration;
std::vector<time_duration> terms{p::seconds(30), p::hours(-168),
p::minutes(-15),
p::nanoseconds(60'000'000'000 * 60 * 24)};
for (auto mydt : {MyDateTime{2021, 2, 17}, MyDateTime{2021, 3, 17}}) {
std::cout << "---- Origin: " << mydt << "\n";
for (time_duration term : terms) {
mydt = ptime(mydt) + term;
std::cout << "Result: " << mydt << "\n";
}
};
}
版画
---- Origin: 2021/02/17 +0
Result: 2021/02/17 +30000000000
Result: 2021/02/10 +30000000000
Result: 2021/02/09 +85530000000000
Result: 2021/02/10 +85530000000000
---- Origin: 2021/03/17 +0
Result: 2021/03/17 +30000000000
Result: 2021/03/10 +30000000000
Result: 2021/03/09 +85530000000000
Result: 2021/03/10 +85530000000000
我有一个非常天真的结构,表示我想对其执行算术的日期时间:
struct MyDateTime
{
MyDateTime(int year, int month, int day, uint64_t nanos);
int year;
int month;
int day;
uint64_t nanosSinceMidnight;
};
我希望能够从另一个 MyDateTime
.
MyDateTime
我的想法是让我的结构成为一个包装器并在内部使用 Boost。
我看了Boost Posix时间:
https://www.boost.org/doc/libs/1_55_0/doc/html/date_time/examples.html#date_time.examples.time_math
但这似乎只是在计算时间(不考虑日期部分)。
我查看了 Boost Gregorian Date,但在构造函数中看不到任何时间参数。
使用 Boost 的最简单方法是什么,以便我可以执行日期时间算术?
您现在可能已经意识到,无法添加日期。
日期和时间戳在数学上类似于 tensors,因为它们的不同类型在不同的域中。
当您评论说 time_duration
不包括日期时,您仍然说得有道理。
因为time_duration
可能是时域差值类型(差值类型ptime
)但是我们需要一个类比ptime
的日期部分,即boost::gregorian::date
.
Boost 公历日期基本上是 (yyyy,mm,dd) 的元组。因此自然差异类型只是 带符号的整数天数 。这就是 * boost::gregorian::date_duration
是什么:
boost::gregorian::date_duration x = date{} - date{};
boost::posix_time::time_duration y = ptime{} - ptime{};
Because that type is implemented in the Gregorian module you will get correct differences, even with special cases like leap days and other anomalies: https://www.calendar.com/blog/gregorian-calendar-facts/
因此,您实际上可以将该类型用作差异类型,仅用于 ymd 部分。
简化
好消息是,您不必费心:boost::posix_time::ptime
封装了完整的 boost::gregorian::date
,因此当您从 ptime
中减去 boost::posix_time::time_duration
时,您将已经得到加密的天数:
#include <boost/date_time.hpp>
int main() {
auto now = boost::posix_time::microsec_clock::local_time();
auto later = now + boost::posix_time::hours(3);
auto tomorrow = later + boost::gregorian::days(1);
auto ereweek = later - boost::gregorian::weeks(1);
std::cout << later << " is " << (later - now) << " later than " << now
<< std::endl;
std::cout << tomorrow << " is " << (tomorrow - later) << " later than " << later
<< std::endl;
std::cout << ereweek << " is " << (ereweek - now) << " later than " << now
<< std::endl;
}
从当前时间开始,我们加 3 小时 1 天,然后减去一周。它打印:Live On Coliru:
2021-Mar-28 01:50:45.095670 is 03:00:00 later than 2021-Mar-27 22:50:45.095670
2021-Mar-29 01:50:45.095670 is 24:00:00 later than 2021-Mar-28 01:50:45.095670
2021-Mar-21 01:50:45.095670 is -165:00:00 later than 2021-Mar-27 22:50:45.095670
注意24h
是1天,-165h是(7*24 - 3)小时前。
公历模块中有很多智能:
std::cout << date{2021, 2, 1} - date{2020, 2, 1} << std::endl; // 366
std::cout << date{2020, 2, 1} - date{2019, 2, 1} << std::endl; // 365
考虑到闰日。但也知道上下文中日历月的不同长度:
auto term = boost::gregorian::months(1);
for (date origin : {date{2021, 2, 17}, date{2021, 3, 17}}) {
std::cout << ((origin + term) - origin) << std::endl;
};
分别打印 28 和 31。
将其应用于您的类型
我建议保持库差异类型,因为 很明显您之前没有考虑过您需要一个。通过简单地创建一些相互转换,您可以吃蛋糕也可以吃:
struct MyDateTime {
MyDateTime(int year = 1970, int month = 1, int day = 1, uint64_t nanos = 0)
: year(year),
month(month),
day(day),
nanosSinceMidnight(nanos) {}
operator ptime() const {
return {date(year, month, day),
microseconds(nanosSinceMidnight / 1'000)};
}
explicit MyDateTime(ptime const& from)
: year(from.date().year()),
month(from.date().month()),
day(from.date().day()),
nanosSinceMidnight(from.time_of_day().total_milliseconds() * 1'000) {}
private:
int year;
int month;
int day;
uint64_t nanosSinceMidnight;
};
Now, I would question the usefulness of keeping your
MyDateTime
type, but I realize legacy code exists, and sometimes you require a longer time period while moving away from it.
纳秒
默认情况下不启用纳秒精度。您需要[选择使用它](https://www.boost.org/doc/libs/1_58_0/doc/html/date_time/details.html#boost-common-heading-doc-spacer:~:text=To%20use%20the%20alternate%20resolution%20(96,the%20variable%20BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG%20must%20be%20defined)。 在下面的示例中,我这样做了。
注意项目中的所有翻译单元都使用定义,否则会导致 ODR violations。
现场演示
也增加了一些便利 operator<<
:
#define BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG
#include <boost/date_time.hpp>
#include <vector>
using boost::posix_time::ptime;
using boost::gregorian::date;
using boost::posix_time::nanoseconds;
struct MyDateTime {
MyDateTime(MyDateTime const&) = default;
MyDateTime& operator=(MyDateTime const&) = default;
MyDateTime(int year = 1970, int month = 1, int day = 1, uint64_t nanos = 0)
: year(year),
month(month),
day(day),
nanosSinceMidnight(nanos) {}
operator ptime() const {
return {date(year, month, day), nanoseconds(nanosSinceMidnight)};
}
/*explicit*/ MyDateTime(ptime const& from)
: year(from.date().year()),
month(from.date().month()),
day(from.date().day()),
nanosSinceMidnight(from.time_of_day().total_nanoseconds()) {}
private:
friend std::ostream& operator<<(std::ostream& os, MyDateTime const& dt) {
auto save = os.rdstate();
os << std::dec << std::setfill('0') << std::setw(4) << dt.year << "/"
<< std::setw(2) << dt.month << "/" << std::setw(2) << dt.day << " +"
<< dt.nanosSinceMidnight;
os.setstate(save);
return os;
}
int year;
int month;
int day;
uint64_t nanosSinceMidnight;
};
int main() {
namespace g = boost::gregorian;
namespace p = boost::posix_time;
using p::time_duration;
std::vector<time_duration> terms{p::seconds(30), p::hours(-168),
p::minutes(-15),
p::nanoseconds(60'000'000'000 * 60 * 24)};
for (auto mydt : {MyDateTime{2021, 2, 17}, MyDateTime{2021, 3, 17}}) {
std::cout << "---- Origin: " << mydt << "\n";
for (time_duration term : terms) {
mydt = ptime(mydt) + term;
std::cout << "Result: " << mydt << "\n";
}
};
}
版画
---- Origin: 2021/02/17 +0
Result: 2021/02/17 +30000000000
Result: 2021/02/10 +30000000000
Result: 2021/02/09 +85530000000000
Result: 2021/02/10 +85530000000000
---- Origin: 2021/03/17 +0
Result: 2021/03/17 +30000000000
Result: 2021/03/10 +30000000000
Result: 2021/03/09 +85530000000000
Result: 2021/03/10 +85530000000000