【问题标题】:Performing date time arithmetic in custom date time class在自定义日期时间类中执行日期时间算术
【发布时间】:2021-06-22 19:28:09
【问题描述】:

我有一个非常简单的结构表示日期时间,我想对其进行算术运算:

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 Time:

https://www.boost.org/doc/libs/1_55_0/doc/html/date_time/examples.html#date_time.examples.time_math

但这似乎只是在做时间数学(不考虑日期部分)。

我查看了 Boost Gregorian Date,但在构造函数中看不到任何时间参数。

使用 Boost 的最简单方法是什么,以便我可以执行日期时间算术?

【问题讨论】:

标签: c++ boost


【解决方案1】:

您现在可能已经意识到,无法添加日期。

日期和时间戳在数学上类似于tensors,因为它们的不同类型在不同的域中。

当您评论 time_duration 不包含日期时,您仍然有道理。

因为time_duration 可能是时域差异类型(差异类型ptime),但我们需要ptime 的日期部分的模拟,即boost::gregorian::date

Boost Gregorian 日期基本上是 (yyyy,mm,dd) 的祝福元组。所以自然差异类型只是 有符号整数天数。这就是完全正确*boost::gregorian::date_duration 是什么:

boost::gregorian::date_duration  x = date{} - date{};
boost::posix_time::time_duration y = ptime{} - ptime{};

因为该类型是在 Gregorian 模块中实现的,所以即使在闰日和其他异常等特殊情况下,您也会得到正确的差异:https://www.calendar.com/blog/gregorian-calendar-facts/

因此,您实际上可以将该类型用作差异类型,仅用于 ymd 部分。

简化

好消息是,您不必费心:boost::posix_time::ptime 封装了一个完整的 boost::gregorian::date,因此当您从 ptimes 中减去 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;
};

现在,我会质疑保留 MyDateTime 类型的用处,但我意识到存在遗留代码,有时您需要更长的时间才能摆脱它。

纳秒

默认情况下不启用纳秒精度。你需要[选择使用它](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&lt;&lt; 为好:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-02
    相关资源
    最近更新 更多