## Activity 2: Implementing classes for Date Calculations

In this activity, we will implement two classes, **Date** and **Days** that will make it very easy for us to work with dates and the time differences between them. Let's get started:

- Load the prepared project from the
**Lesson3/Activity02**folder and configure the Current Builder for the project to be**CMake Build (Portable)**. Build and configure the launcher and run the unit tests. We recommend that the name that's used for the tests runner is**L3A2datetests**. The project has dummy files and one failing test. - Open the
**date.hpp**file in the editor and add the following lines inside the basic**Date**class to allow access to the stored values:int Day() const {return m_day;}

int Month() const {return m_month;}

int Year() const {return m_year;}

- Open the
**dateTests.cpp**file and add the following code to the**DateTest**class:void VerifyDate(const Date& dt, int yearExp, int monthExp, int dayExp) const

{

ASSERT_EQ(dayExp, dt.Day());

ASSERT_EQ(monthExp, dt.Month());

ASSERT_EQ(yearExp, dt.Year());

}

Normally, you would refactor this test as the tests develop, but we will pull it out up front.

- Replace
**ASSERT_FALSE()**in the existing test with the following test:Date dt;

VerifyDate(dt, 1970, 1, 1);

- Rebuild and run the tests – they should now all pass.
- Add the following test:
TEST_F(DateTest, Constructor1970Jan2)

{

Date dt(2, 1, 1970);

VerifyDate(dt, 1970, 1, 2);

}

- To make this test we need to add the following two constructors to the
**Date**class:Date() = default;

Date(int day, int month, int year) :

m_year{year}, m_month{month}, m_day{day}

{

}

- We now need to introduce the functions to convert to/from the
**date_t**type. Add the following alias to the**date.hpp**file inside our namespace:using date_t=int64_t;

- To the
**Date**class, add the declaration of the following method:date_t ToDateT() const;

- Then, add the following test:
TEST_F(DateTest, ToDateTDefaultIsZero)

{

Date dt;

ASSERT_EQ(0, dt.ToDateT());

}

- As we are doing (
**TDD**), we add the minimal implementation of the method to pass the test.date_t Date::ToDateT() const

{

return 0;

}

- Now, we add the next test:
TEST_F(DateTest, ToDateT1970Jan2Is1)

{

Date dt(2, 1, 1970);

ASSERT_EQ(1, dt.ToDateT());

}

- We continue to add one more test and then another, all the time refining the algorithm in
**ToDateT()**firstly to deal with dates in**1970**, then**1-Jan-1971**, and then a date in**1973**, which means we span one leap year, and so on. The full set of tests that are used to develop the**ToDateT()**method are as follows:TEST_F(DateTest, ToDateT1970Dec31Is364)

{

Date dt(31, 12, 1970);

ASSERT_EQ(364, dt.ToDateT());

}

TEST_F(DateTest, ToDateT1971Jan1Is365)

{

Date dt(1, 1, 1971);

ASSERT_EQ(365, dt.ToDateT());

}

TEST_F(DateTest, ToDateT1973Jan1Is1096)

{

Date dt(1, 1, 1973);

ASSERT_EQ(365*3+1, dt.ToDateT());

}

TEST_F(DateTest, ToDateT2019Aug28Is18136)

{

Date dt(28, 8, 2019);

ASSERT_EQ(18136, dt.ToDateT());

}

- To pass all of these tests, we add the following items to the declaration of the
**Date**class:public:

static constexpr int EpochYear = 1970;

static constexpr int DaysPerCommonYear = 365;

static constexpr int YearsBetweenLeapYears = 4;

private:

int GetDayOfYear(int day, int month, int year) const;

bool IsLeapYear(int year) const;

int CalcNumberLeapYearsFromEpoch(int year) const;

- The implementation of
**ToDateT()**and the supporting methods in**date.cpp**is as follows:namespace {

int daysBeforeMonth[2][12] =

{

{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 204, 334}, // Common Year

{ 0, 31, 50, 91, 121, 152, 182, 213, 244, 274, 205, 335} // Leap Year

};

}

namespace acpp::date

{

int Date::CalcNumberLeapYearsFromEpoch(int year) const

{

return (year-1)/YearsBetweenLeapYears

- (EpochYear-1)/YearsBetweenLeapYears;

}

int Date::GetDayOfYear(int day, int month, int year) const

{

return daysBeforeMonth[IsLeapYear(year)][month-1] + day;

}

bool Date::IsLeapYear(int year) const

{

return (year%4)==0; // Not full story, but good enough to 2100

}

date_t Date::ToDateT() const

{

date_t value = GetDayOfYear(m_day, m_month, m_year) - 1;

value += (m_year-EpochYear) * DaysPerCommonYear;

date_t numberLeapYears = CalcNumberLeapYearsFromEpoch(m_year);

value += numberLeapYears;

return value;

}

}

- Now that
**ToDateT()**is working, we turn to its inverse, that is,**FromDateT()**. Again, we build up the tests one at a time to develop the algorithm over a range of dates. The following tests were used:TEST_F(DateTest, FromDateT0Is1Jan1970)

{

Date dt;

dt.FromDateT(0);

ASSERT_EQ(0, dt.ToDateT());

VerifyDate(dt, 1970, 1, 1);

}

TEST_F(DateTest, FromDateT1Is2Jan1970)

{

Date dt;

dt.FromDateT(1);

ASSERT_EQ(1, dt.ToDateT());

VerifyDate(dt, 1970, 1, 2);

}

TEST_F(DateTest, FromDateT364Is31Dec1970)

{

Date dt;

dt.FromDateT(364);

ASSERT_EQ(364, dt.ToDateT());

VerifyDate(dt, 1970, 12, 31);

}

TEST_F(DateTest, FromDateT365Is1Jan1971)

{

Date dt;

dt.FromDateT(365);

ASSERT_EQ(365, dt.ToDateT());

VerifyDate(dt, 1971, 1, 1);

}

TEST_F(DateTest, FromDateT1096Is1Jan1973)

{

Date dt;

dt.FromDateT(1096);

ASSERT_EQ(1096, dt.ToDateT());

VerifyDate(dt, 1973, 1, 1);

}

TEST_F(DateTest, FromDateT18136Is28Aug2019)

{

Date dt;

dt.FromDateT(18136);

ASSERT_EQ(18136, dt.ToDateT());

VerifyDate(dt, 2019, 8, 28);

}

- Add the following declarations to the header file:
public:

void FromDateT(date_t date);

private:

int CalcMonthDayOfYearIsIn(int dayOfYear, bool IsLeapYear) const;

- Use the following implementation since the preceding tests are added one at a time:
void Date::FromDateT(date_t date)

{

int number_years = date / DaysPerCommonYear;

date = date - number_years * DaysPerCommonYear;

m_year = EpochYear + number_years;

date_t numberLeapYears = CalcNumberLeapYearsFromEpoch(m_year);

date -= numberLeapYears;

m_month = CalcMonthDayOfYearIsIn(date, IsLeapYear(m_year));

date -= daysBeforeMonth[IsLeapYear(m_year)][m_month-1];

m_day = date + 1;

}

int Date::CalcMonthDayOfYearIsIn(int dayOfYear, bool isLeapYear) const

{

for(int i = 1 ; i < 12; i++)

{

if ( daysBeforeMonth[isLeapYear][i] > dayOfYear)

return i;

}

return 12;

}

- Now that we have the supporting routines ready, we can implement the real feature of the
**Date**class difference between two dates and determine the new date by adding a number of days. Both of these operations need a new type (class)**Days**. - Add the following implementation of
**Days**to the header (above**Date**):class Days

{

public:

Days() = default;

Days(int days) : m_days{days} { }

operator int() const

{

return m_days;

}

private:

int m_days{0};

};

- The first operator will be an addition of
**Days**to**Date**. Add the following method declaration (inside the public section of the**Date**class) :Date& operator+=(const Days& day);

- Then, add the inline implementation (outside the
**Date**class) to the header file:inline Date operator+(const Date& lhs, const Days& rhs )

{

Date tmp(lhs);

tmp += rhs;

return tmp;

}

- Write the following tests to verify the
**sum**operation:TEST_F(DateTest, AddZeroDays)

{

Date dt(28, 8, 2019);

Days days;

dt += days;

VerifyDate(dt, 2019, 8, 28);

}

TEST_F(DateTest, AddFourDays)

{

Date dt(28, 8, 2019);

Days days(4);

dt += days;

VerifyDate(dt, 2019, 9, 1);

}

- The actual implementation of the
**sum**operation is simply based on the two support methodsDate& Date::operator+=(const Days& day)

{

FromDateT(ToDateT()+day);

return *this;

}

- Add the following test:
TEST_F(DateTest, AddFourDaysAsInt)

{

Date dt(28, 8, 2019);

dt += 4;

VerifyDate(dt, 2019, 9, 1);

}

- When we run the tests, they all build and this test passes. But this is not the desired outcome. We do not want them to be able to add naked integers to our dates. (A future version may add months and years, so what does adding an integer mean?). To make this fail by causing the build to fail, we change the Days constructor to be
**explicit**:explicit Days(int days) : m_days{days} { }

- Now the build fails, so we need to fix the test by changing the addition line to cast to
**Days**as follows:dt += static_cast<Days>(4);

All tests should pass again.

- The final functionality we want is the difference between two dates. Here are the tests that were used to verify the implementation:
TEST_F(DateTest, DateDifferences27days)

{

Date dt1(28, 8, 2019);

Date dt2(1, 8, 2019);

Days days = dt1 - dt2;

ASSERT_EQ(27, (int)days);

}

TEST_F(DateTest, DateDifferences365days)

{

Date dt1(28, 8, 2019);

Date dt2(28, 8, 2018);

Days days = dt1 - dt2;

ASSERT_EQ(365, (int)days);

}

- Add the following declaration of the function to the public section of the
**Date**class in the header file:Days operator-(const Date& rhs) const;

- Add the following code after the Date class in the header file:
inline Days Date::operator-(const Date& rhs) const

{

return Days(ToDateT() - rhs.ToDateT());

}

Because we made the

**Days**constructor explicit, we must call it in the return statement. With all these changes in place, all the tests should pass. - Configure
**L3A2date**as a**datetools**binary and open main.cpp in the editor. Remove the comment from the definition of**ACTIVITY2**:#define ACTIVITY2

- Build and then run the sample application. This will produce the following output:

###### Figure 3.53: Output of successful Date sample application

We have implemented all of the requirements of the Date and Days classes and delivered them all with unit tests. The unit tests allowed us to implement incremental functionality to build up the two complicated algorithms, **ToDateT** and **FromDateT** which form the underlying support for the functionality that we wanted to deliver.