Find the exact date and time before n months?

543 views Asked by At

I am using HowardHinnant date library in my project. I want to get the exact datetime before n months.

For Example : "2016-12-17 18:21:26" is current datetime and, i want to know the datetime of 13 months before. It should output "2015-11-17 18:21:26".

/*
 * This function will return the current date in year_month_day format
 */

auto currentDate() {
    auto currentTime = std::chrono::system_clock::now();
    date::year_month_day currentDate = date::floor<date::days>(currentTime);
    return currentDate;
}



/*
 * This function will find the date before (n) months
 */

template <typename T>
auto oldDate(T monthsNum) {
    auto currentDate = currentDate();
    auto yearCurrentDate = (int)currentDate.year();
    auto monthCurrentDate = currentDate.month();
    auto dayCurrentDate = currentDate.day();
    auto yearNum = monthsNum/12;
    auto yearEndDate = yearCurrentDate - yearNum;
    auto monthNum = monthsNum%12;
    auto monthEndDate = monthCurrentDate;
    while(monthNum) {
        monthEndDate--;
    }
    if(monthEndDate > monthCurrentDate) {
        yearEndDate--;
    }
    date::year_month_day endDate = date::year{yearEndDate}/monthEndDate;
    return endDate;
}

My function oldDate() will return the date which was n months before. But i am not able to get the time.

1

There are 1 answers

1
Howard Hinnant On BEST ANSWER

Instead of currentDate(), create a currentTime which returns a sys_seconds (time with seconds precision):

auto
currentTime()
{
    using namespace std::chrono;
    return date::floor<seconds>(system_clock::now());
}

Now oldDate can call currentTime instead of currentDate and in that way know and preserve the time-of-day.

oldDate should take as a parameter date::months which is a std::chrono::duration with a precision of months. Here's what it could look like (description afterward):

auto
oldDate(date::months monthsNum)
{
    using namespace date;
    auto time = currentTime();
    auto sd = floor<days>(time);
    auto time_of_day = time - sd;
    auto ymd = year_month_day{sd} - monthsNum;
    if (!ymd.ok())
        ymd = ymd.year()/ymd.month()/last;
    return sys_days{ymd} + time_of_day;
}

The using namespace date is handy otherwise you're going to have date:: just all over the place.

  • First get the time from currentTime(). This is a std::chrono::time_point<system_clock, seconds>, or a count of seconds since 1970-01-01 UTC.

  • Then truncate this count of seconds into a count of days with floor<days>(). This is a std::chrono::time_point<system_clock, days>.

  • One can think of sd as a time point to the first instant of the day (in UTC). So if you subtract sd from time you get a std::chrono::duration representing the time-of-day. The precision is going to be the common_type of the two precisions you're subtracting (seconds).

  • To do the month arithmetic, you need to switch the type of sd from sys_days (a time_point) to year_month_day (a calendar type). Once you have type year_month_day, you can just subtract the monthsNum from it, yielding another year_month_day (stored in ymd above).

  • You can check if this resulted in a valid date or not with .ok(). If it is an invalid date, that means you overflowed the days field of the year_month_day. I see in the comments that if this happens, you want the last day of the month. So just extract the year and month and use last to reset ymd to the last day of the month for that year.

  • Finally convert the ymd back into a sys_days (time_point with days precision) and add the time_of_day back to it.

The result is sys_seconds (time_point with seconds precision).

I just ran this with this driver:

int
main()
{
    using namespace date;
    std::cout << currentTime() << '\n';
    std::cout << oldDate(months{13}) << '\n';
}

And the output was:

2016-12-17 15:57:52
2015-11-17 15:57:52

Convenience link to documentation.