Saturday, 16 June 2018

Chapter 9 // Exercise 11 - Principles & Practice Using C++

In this exercise I am using Visual Studio Community 2017 and the header file "std_lib_facilities.h" which can be found here:

http://www.stroustrup.com/Programming/PPP2code/std_lib_facilities.h


Chapter 9 // Exercise 11



Design and implement a set of useful helper functions for the Date class with functions such as next_workday() (assume that any day that is not a Saturday or Sunday is a workday) and week_of_year() (assume that week 1 is the week with January 1 in it and that the first day of a week is Sunday).

(the above code on github is different to below as I re-did the book)

dateclass.h
//dateclass.h
#ifndef _DATECLASS_H_
#define _DATECLASS_H_

#include "std_lib_facilities.h"

enum class Month
{
 jan = 1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec
};

enum class Day
{
 monday = 1, tuesday, wednesday, thursday, friday, saturday, sunday
};

//forward declaration
Month returnMonth(int month);
Day returnDay(int day);

//9.8 The Date Class
class Date
{
public:
 Date();
 ~Date();
 Date(int y, Month m, int d);  //check for valid date & initialise

 //non-modifying operations:
 int day() const { return d; };
 Month month() const { return m; }
 int year() const { return y; }

 //modifying operations:
 void add_day(int n);
 void add_month(int n);
 void add_year(int n);

private:
 int y;
 Month m;
 int d;

 bool lastDay = false;
 bool endYear = false;
};

//helper functions
bool is_date(int y, Month m, int d);   //true for valid date
bool leapyear(int y);     //true if y is leap year

Day findDayOfWeek(const Date& d);   //returns the day for the given date
Day next_workday(const Date& d);   //retrieves the next work day
int week_of_the_year(const Date& d);   //returns the number of the week in the year
int howManyDaysSince(const Date& d);   //returns how many days since the start of the year

void printDay(const Day& day);    //prints out day in written form

ostream& operator<<(ostream& os, Date& d);
istream& operator>>(istream& is, Date& dd);

bool operator==(const Date& a, const Date& b);
bool operator!=(const Date& a, const Date& b);

//to help with finding day for any given date
struct stringInt
{
 int monthDay;
 int key;
};

#endif // !_DATECLASS_H_


dateclass.cpp
//dateclass.cpp

#include "dateclass.h"

vector<stringInt> monthKey
{
 { 3, 1 },{ 4, 2 },{ 5, 3 },{ 6, 4 },{ 7, 5 },{ 8, 6 },{ 9, 7 },{ 10, 8 },{ 11, 9 },{ 12, 10 },{ 1, 11 },{ 2, 12 }
};
vector<stringInt> dayKey
{
 { 7, 0 },{ 1, 1 },{ 2, 2 },{ 3, 3 },{ 4, 4 },{ 5, 5 },{ 6, 6 }
};

//default Date Constructor
Date::Date() 
{
 y = 2001;
 m = Month::jan;
 d = 1;
}

//deconstructor
Date::~Date() {}

//Date Constructor - initialise the day
Date::Date(int y, Month m, int d)
{
 //check that y m d is a valid date
 if (!is_date(y, m, d))
  cout << "Error, invalid date." << endl;
 else
 {
  //if date is valid, initalise the date
  Date::y = y;
  Date::m = m;
  Date::d = d;
 }

 return;
}

//increment date by n days
void Date::add_day(int n)
{
 //if day goes above 31, increase month, set day to 1
 //if month goes above 12, increase year, set month to 1
 for (int i = 0; i < n; ++i)
 {
  //wrap days
  if (Date::d == 31)
   lastDay = true;
  Date::d = (Date::d == 31) ? 1 : ++Date::d;  //if day is equal to 31, make it 1, otherwise ++

  if (lastDay)
  {
   //wrap month
   lastDay = false;
   int mon = (static_cast<int>(Date::m) == 12) ? 1 : (static_cast<int>(Date::m) + 1); //if month is equal to 12, make it 1, otherwise ++
   Date::m = returnMonth(mon);
   if (static_cast<int>(Date::m) == 12)
    endYear = true;

   if (endYear)
   {
    //just increase year by one
    endYear = false;
    ++Date::y;
   }

  }

 }
}

void Date::add_month(int n)
{
 for (int i = 0; i < n; ++i)
 {
  //if month is equal to 12, make it 1, otherwise ++
  int mon = (static_cast<int>(Date::m) == 12) ? 1 : (static_cast<int>(Date::m) + 1);
  Date::m = returnMonth(mon);
  if (static_cast<int>(Date::m) == 12)
   endYear = true;

  if (endYear)
  {
   //just increase year by one
   endYear = false;
   ++Date::y;
  }
 }
}

void Date::add_year(int n)
{
 for (int i = 0; i < n; ++i)
  ++Date::y;
}

//switch to return correct type of Month
Month returnMonth(int month)
{
 switch (month)
 {
 case 1:
  return Month::jan;
  break;
 case 2:
  return Month::feb;
  break;
 case 3:
  return Month::mar;
  break;
 case 4:
  return Month::apr;
  break;
 case 5:
  return Month::may;
  break;
 case 6:
  return Month::jun;
  break;
 case 7:
  return Month::jul;
  break;
 case 8:
  return Month::aug;
  break;
 case 9:
  return Month::sep;
  break;
 case 10:
  return Month::oct;
  break;
 case 11:
  return Month::nov;
  break;
 case 12:
  return Month::dec;
  break;
 default:
  cout << "Bad month" << endl;
 }
}

//switch to return correct day
Day returnDay(int day)
{
 switch (day)
 {
 case 7:
  return Day::sunday;
  break;
 case 1:
  return Day::monday;
  break;
 case 2:
  return Day::tuesday;
  break;
 case 3:
  return Day::wednesday;
  break;
 case 4:
  return Day::thursday;
  break;
 case 5:
  return Day::friday;
  break;
 case 6:
  return Day::saturday;
  break;
 default:
  cout << "Bad day" << endl;
 }
}

void printDay(const Day& day)
{
 switch (day)
 {
 case Day::sunday:
  cout << "Sunday\n";
  break;
 case Day::monday:
  cout << "Monday\n";
  break;
 case Day::tuesday:
  cout << "Tuesday\n";
  break;
 case Day::wednesday:
  cout << "Wednesday\n";
  break;
 case Day::thursday:
  cout << "Thursday\n";
  break;
 case Day::friday:
  cout << "Friday\n";
  break;
 case Day::saturday:
  cout << "Saturday\n";
  break;
 default:
  cout << "Bad day" << endl;
 }
}

//true for valid date
bool is_date(int y, Month m, int d)
{
 //check that y is valid
 if (y < 1900 || y > 2018)
 {
  cout << "Error, invalid year." << endl;
  return false;
 }

 //check that m is valid
 if (m < Month::jan || m > Month::dec)
  return false;

 //check that d is valid
 if (d <= 0)
  return false; //d must be positive

 int daysInMonth = 31;  //most months have 31 days

 switch (m)
 {
  case Month::feb:
   //if leapyear, make it 29, if not make it 28
   daysInMonth = (leapyear(y)) ? 29 : 28;
   break;
  case Month::apr: case Month::jun: case Month::sep: case Month::nov:
   daysInMonth = 30; //the rest have 30 days
   break;
 }

 if (daysInMonth < d)
  return false;

 return true;
}

//checks for leap year (gregorian calender)
bool leapyear(int y)
{
 //if divisible by 4 but not 100 - then leap year
 if (y % 4 == 0 && y % 100 != 0)
  return true;
 //if divisible by 4, 100 and 400 - then leap year
 if (y % 4 == 0 && y % 100 == 0 && y % 400 == 0)
  return true;

 else
  return false;
}

//find day of the week for any year using Zeller's Rule
Day findDayOfWeek(const Date& d)
{
 int day, month, year, century;
 
 //get day
 day = d.day();

 //get key for month
 for (int i = 0; i < monthKey.size(); ++i)
 {
  if (monthKey[i].monthDay == static_cast<int>(d.month()))
   month = monthKey[i].key;
 }

 //get last two digits of year
 year = d.year() % 100;
 if (month == 11 || month == 12)
  year -= 1;

 //get the century - first 2 digits of year
 string y = to_string(d.year());
 char y1 = y.at(0);
 char y2 = y.at(1);
 string y3;
 y3 += y1;
 y3 += y2;
 century = stoi(y3);

 int f = d.day() + ((13 * month - 1) / 5) + year + (year / 4) + (century / 4) - (2 * century);
 day = f % 7;

 //get which day it is
 for (int i = 0; i < dayKey.size(); ++i)
 {
  if (day == dayKey[i].key)
   day = dayKey[i].monthDay;
 }

 return returnDay(day);
}

//retrieves the next work day
Day next_workday(const Date& d)
{
 //find the current day
 Day current = findDayOfWeek(d);

 //find next day
 Date nextDay(d);
 nextDay.add_day(1);
 Day next = findDayOfWeek(nextDay);
 while (next == Day::saturday || next == Day::sunday)
 {
  nextDay.add_day(1);
  next = findDayOfWeek(nextDay);
 }

 return next;
}

//returns how many days since the start of the year
int howManyDaysSince(const Date& d)
{
 int day = d.day();
 int month = static_cast<int>(d.month());
 cout << "Month: " << month << endl;
 int year = d.year();
 vector<int> daysInMonth = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
 vector<int> daysInMonthLeap = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

 int result = 0;
 if (leapyear(year))
 {
  for (int i = 0; i < month - 1; ++i)
   result += daysInMonthLeap[i];
  result += day;
 }
 else
 {
  for (int i = 0; i < month - 1; ++i)
   result += daysInMonth[i];
  result += day;
 }

 return result;
}

//finds out what number week of the year it is
int week_of_the_year(const Date& d)
{
 int daysSince1st = howManyDaysSince(d); //how many days have passed since jan 1st of given year?
 cout << "Days since 1st: " << daysSince1st << endl;
 int dow;     //day of week. sun = 0, mon = 1 etc..
 int dowJan1;     //day of week on jan 1st

 //find out day of week for current day
 Day day = findDayOfWeek(d);
 if (day == Day::sunday)
  dow = 0;
 else
  dow = static_cast<int>(day);

 //find out day of week for jan 1st of given year
 day = findDayOfWeek(Date(d.year(), Month::jan, 1));
 if (day == Day::sunday)
  dowJan1 = 0;
 else
  dowJan1 = static_cast<int>(day);

 //find week number
 int weekNum = ((daysSince1st + 6) / 7);
 if (dow < dowJan1)
  ++weekNum;
 return weekNum;
}

//compares dates
bool operator==(const Date& a, const Date& b)
{
 return a.year() == b.year()
  && a.month() == b.month()
  && a.day() == b.day();
}

bool operator!=(const Date& a, const Date& b)
{
 return !(a == b);
}

//print to screen
ostream& operator<<(ostream& os, Date& d)
{
 return os << d.day() << ", " << static_cast<int>(d.month()) << ", " << d.year() << endl;
}

//write into Date
istream& operator>>(istream& is, Date& dd)
{
 int y, m, d;
 char ch1, ch2, ch3, ch4;
 is >> ch1 >> y >> ch2 >> m >> ch3 >> d >> ch4;
 if (!is)
  return is;
 if (ch1 != '(' || ch2 != ',' || ch3 != ',' || ch4 != ')')
 {
  //format error
  is.clear(ios_base::failbit);  //set the failbit
  return is;
 }

 dd = Date(y, Month(m), d);  //update dd
 return is;
}

main.cpp
//main.cpp

#include "std_lib_facilities.h"
#include "dateclass.h"

int main()
{
 //intialise dates
 Date today(2018, Month::jun, 14);
 Date tomorrow(today);

 //increase day by one
 tomorrow.add_day(1);
 tomorrow.add_month(2);
 tomorrow.add_year(2);

 //print out results
 cout << "Today: " << today << endl;
 cout << "Tomorrow: " << tomorrow << endl;

 //prints todays day
 cout << "Today is a: ";
 printDay(findDayOfWeek(today));

 //print next working day
 cout << "The next working day is: ";
 printDay(next_workday(today));

 //print week number
 cout << "The week number is: " << week_of_the_year(today) << endl;

 keep_window_open();

 return 0;
}


This took much longer than I expected, mainly because I honestly (and naively) expected to be implementing just two functions. I ended up creating quite a few more than that. 

First up, in order to find the next work day, the day of the week had to be figured out. We enter a number as the day, how does the date class know what day it is? 

I went on a rabbit hole trying to figure this out. I eventually ended up using a formula called Zeller's Rule:
http://mathforum.org/dr.math/faq/faq.calendar.html
This had a few problems though as I needed to separate the year into the first and last two digits as well as assign keys to months and days. The keys were assigned by creating a new struct which allowed for two ints. Then the month-key pairs and day-key pairs were inputted manually. The function findDayOfWeek() returns an int, however I created another function which converts the day int into the Day enum value and returns that.

Just when I thought the hard part was over, he then wanted us to find what week number of the year it was. There are many different ways of doing this online however I eventually went with a function that figures out how many days have passed since the 1st of Jan of the given year and then a formula adapted from here:
https://stackoverflow.com/questions/274861/how-do-i-calculate-the-week-number-given-a-date

Getting the howManyDaysSince() to work properly was a bit of headache as there isn't really much out there from a C++ perspective. There was an excellent yet crazy answer from a commenter called Mmars from this site:
https://www.epochconverter.com/daynumbers
however I found this to be ridiculous to look at or even try and read. I prefer my brute force solution. I did go a bit further though than Bjarne's suggestion of assuming that the 1st of jan was a sunday. 

I've purposefully left that function to take any date in as I may change it in the future to calculate how many days since any given date. However, it will need some tweaking.

1 comment:

  1. i used these two functions to determine the day on given date after 1970. https://pastebin.com/LPiQvs5Q

    now fed up with this date thing , moving up to next chapter. lol

    ReplyDelete