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