在自定义日期时间中执行日期时间算法 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<<

Live On Coliru

#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