1

I have this code:

COleDateTime datStart = COleDateTime::GetCurrentTime(), datEnd;

// Update end date (one year later)
datEnd.SetDateTime(datStart.GetYear() + 1,
    datStart.GetMonth(),
    datStart.GetDay(),
    datStart.GetHour(),
    datStart.GetMinute(),
    datStart.GetSecond());

The above code failed yesterday (Feb 29, 2020) because datEnd resulted in Feb 29, 2021 which is not valid.

What is the right way to safely add a year to datStart taking into account leap years?

C# has:

DateTime theDate = DateTime.Now;
DateTime yearInTheFuture = theDate.AddYears(1);

What is the equivalent for MFC/C++?

One possibility is:

COleDateTimeSpan spnYear;
spnYear.SetDateTimeSpan(365, 0, 0, 0);

datEnd = datStart + spnYear;

But it is still potentially flawed due to leap years having 366 days. So what is the right way?

I see this similar question:

C++ add 1 year to date

It implies using boost. I do have boost in my project although never used it for date manipulation. Wonder if this can be used with a COleDateTime object?

Using boost:

boost::gregorian::date dStart{ datStart.GetYear(), datStart.GetMonth(), datStart.GetDay() };
boost::gregorian::date dEnd = dStart + boost::gregorian::years(1);

datEnd.SetDateTime(
    dEnd.year(),
    dEnd.month(),
    dEnd.day(),
    datStart.GetMonth(),
    datStart.GetMinute(),
    datStart.GetSecond());

Doesn't compile.

4>D:\My Programs\2019\MeetSchedAssist\Meeting Schedule Assistant\PublishersDatabaseDlg.cpp(354,49): error C2398: Element '1': conversion from 'int' to 'boost::gregorian::date::year_type' requires a narrowing conversion
4>D:\My Programs\2019\MeetSchedAssist\Meeting Schedule Assistant\PublishersDatabaseDlg.cpp(354,70): error C2398: Element '2': conversion from 'int' to 'boost::gregorian::date::month_type' requires a narrowing conversion
4>D:\My Programs\2019\MeetSchedAssist\Meeting Schedule Assistant\PublishersDatabaseDlg.cpp(354,89): error C2398: Element '3': conversion from 'int' to 'boost::gregorian::date::day_type' requires a narrowing conversion
Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
  • Does this answer your question? [C++ add 1 year to date](https://stackoverflow.com/questions/8450569/c-add-1-year-to-date) In particular, pay attention to the answer that starts with "There is no such thing as "adding a year".". – Ulrich Eckhardt Mar 01 '20 at 10:03
  • 1
    What do you want the result to be? As for your errors you get when using boost: The compiler is complaining about a narrowing conversion. This is due to the use of brace initialization. Either explicitly cast the year/month/day values to an appropriate type, or use direct initialization with parentheses (`dStart( datStart.GetYear(), ... );`). – IInspectable Mar 01 '20 at 10:55
  • @IInspectable I have fixed that compilation error and now I have another: `LINK : fatal error LNK1104: cannot open file 'libboost_date_time-vc142-mt-x64-1_71.lib'`. I can't see any lib files in the boost folder structure. – Andrew Truckle Mar 01 '20 at 14:54
  • 1
    I'm not very familiar with boost, I'm afraid. The more important question still is: What is the result you expect when adding 1 year to any given time point? The implementation is (once that question has been answered), very likely trivial. And you don't need boost either, with C++' [std::chrono](https://en.cppreference.com/w/cpp/chrono) library. – IInspectable Mar 01 '20 at 14:57
  • @IInspectable I just want to add a year to the starting date. That is all. Yesterday I had introduced a leap year because because tweaking the literal year resulted in a bogus date 29-2-2021. I am happy to try `std::chrono`. Not used it before. – Andrew Truckle Mar 01 '20 at 14:59
  • 2
    That's understood, but what is a year to you? Is it 365 days, always? 365 days by default, with 366 days when the range includes 29th of February? 365.25 days? 365.2425 days? Something else? – IInspectable Mar 01 '20 at 17:04
  • Fair point. I just wanted to avoid the crash. I have kept it simple and used the 365 day span. – Andrew Truckle Mar 01 '20 at 17:20
  • 1
    See my [comment here](https://stackoverflow.com/questions/60479748/boost-error-libboost-system-vc141-mt-gd-x64-1-66-lib-c) about the boost lib. I had long ago moved to boost for dates. That @IInspectable says DATE is hideous is mild. :) – lakeweb Mar 02 '20 at 16:56
  • @lakeweb Thanks for the link. I am afraid that a lot of that is over my head. I am used to just downloading compiled libraries, ready to use with appropriate include headers. I will stick with my two lines of code. It does what I need. :) – Andrew Truckle Mar 02 '20 at 18:06
  • 1
    @lak: You don't need boost. [std::chrono](https://en.cppreference.com/w/cpp/chrono) has been part of C++ since C++11. With C++20, the library is finally completed, adding the missing pieces: calendars, timezones, parsing and formatting. The code in question would be as simple as this: `auto const datStart{chrono::system_clock::now()}; auto const datEnd{datStart + years{1}};` (in C++20), or `... +24h * 365` in C++11. – IInspectable Mar 03 '20 at 18:02

1 Answers1

3

COleDateTime is a wrapper around the (hideously surprising) DATE type. Internally it stores a 64-bit floating point value, where whole numbers represent the days since 30th of December, 1899 (midnight). The fractional part represents the time of day (e.g. 0.25 means 6 A.M., and 0.5 is noon). Every floating point value is a legal value and represents a unique1 point in time.

Adding a year to a COleDateTime value amounts to adding the value 365.0 (or whichever value suits your needs) to its internal representation. This can be done by accessing the m_dt member directly

COleDateTime datEnd{ datStart.m_dt + 365.0 };

or by using a COleDateTimeSpan

datEnd = datStart + COleDateTimeSpan{ 365.0 };
// or
datEnd = datStart + COleDateTimeSpan{ 365, 0, 0, 0 };

This operation is always well defined, and will produce a valid time point regardless of leap years. Under the hood, it's just adding floating point values, that have no notion of calendrical properties. The following code

COleDateTime datStart{ 2020, 2, 29, 0, 0, 0 };
COleDateTime datEnd{ datStart + COleDateTimeSpan{ 365.0 } };

will produce a time point that represents February 28th, 2021 (note that if you add 366.0 days, this will return March 1st, 2021).


The initial solution that attempts to construct a COleDateTime by doing arithmetic on the individual date components fails during construction. The constructor validates its input. When passed (2021, 2, 29, 0, 0, 0) it determines an invalid date, and sets m_status to invalid.


1 That's not entirely correct. Values in the range (-1 .. 0] map to the same time point as their absolute values. Note, that e.g. COleDateTime{-0.25} == COleDateTime{0.25} evaluates to false, even though they represent the same point in time.

IInspectable
  • 46,945
  • 8
  • 85
  • 181