-
Book Overview & Buying
-
Table Of Contents
Advanced C++ [Instructor Edition]
By :
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:
int Day() const {return m_day;}
int Month() const {return m_month;}
int Year() const {return m_year;}
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.
Date dt;
VerifyDate(dt, 1970, 1, 1);
TEST_F(DateTest, Constructor1970Jan2)
{
Date dt(2, 1, 1970);
VerifyDate(dt, 1970, 1, 2);
}
Date() = default;
Date(int day, int month, int year) :
m_year{year}, m_month{month}, m_day{day}
{
}
using date_t=int64_t;
date_t ToDateT() const;
TEST_F(DateTest, ToDateTDefaultIsZero)
{
Date dt;
ASSERT_EQ(0, dt.ToDateT());
}
date_t Date::ToDateT() const
{
return 0;
}
TEST_F(DateTest, ToDateT1970Jan2Is1)
{
Date dt(2, 1, 1970);
ASSERT_EQ(1, dt.ToDateT());
}
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());
}
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;
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;
}
}
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);
}
public:
void FromDateT(date_t date);
private:
int CalcMonthDayOfYearIsIn(int dayOfYear, bool IsLeapYear) const;
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;
}
class Days
{
public:
Days() = default;
Days(int days) : m_days{days} { }
operator int() const
{
return m_days;
}
private:
int m_days{0};
};
Date& operator+=(const Days& day);
inline Date operator+(const Date& lhs, const Days& rhs )
{
Date tmp(lhs);
tmp += rhs;
return tmp;
}
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);
}
Date& Date::operator+=(const Days& day)
{
FromDateT(ToDateT()+day);
return *this;
}
TEST_F(DateTest, AddFourDaysAsInt)
{
Date dt(28, 8, 2019);
dt += 4;
VerifyDate(dt, 2019, 9, 1);
}
explicit Days(int days) : m_days{days} { }
dt += static_cast<Days>(4);
All tests should pass again.
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);
}
Days operator-(const Date& rhs) const;
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.
#define ACTIVITY2
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.
Change the font size
Change margin width
Change background colour