// Bjarne Stroustrup 1/16/2010 // Chapter 9 Exercise 11 /* Provide helper functions for the Date class, such as next_workay() and week_of_year(). Apologies: I had assumed the the formulas and explanations needed to make date calculations simple were more easily available on the web and clearer than I found. The mathematical explanations seem to have been drowned in little widgets that will calculate for you, but don't explain how. The next_workday() and week_of_year() are not very hard, but just about anything further gets you into trouble. I did a bit of that below. To keep the code simple and comprehensible, the code below has been restricted in places (e.g. handling only dates after 1 Jan 1601). Warning: Do not try anything non-trivial with date unless you are experienced with either data calculations, algorithms in general, or have a good source of algorithms (in some language) to guide your work. This exercise was meant partly as a motivation for the next exercise: changing the representation of a date to "number of days after a designated first date" rather than (day,month,year). I think it succeeded all too well at that. To avoid really messy code, I first convert the "days after first_date". */ #include "std_lib_facilities.h" /* If you know the day of the week of *any* day (e.g. Jan 16, 2010 is a Saturday) and how to calculate leapyear() (exercise 9-10), you can find the day of the week of every day (of the current calendar). For example, 7 days after 1/16/2010 is another Saturday, 10 days after 1/16/2010 is a "Saturday+3"; that is, a Tuesday. 10 days before 1/16/2010 is a "Saturday-3"; that is, a Wednesday. Primary school children can do that by counting on their fingers; Our job is to translate what those children do into code (formulas) so that we can deal with larger numbers. So as not to try to invent everything from scratch, try typing "day of the week" into google (or equivalent). For more than you ever wanted to know about calendars, see http://www.tondering.dk/claus/calendar.html (e.g. where and when did they have a February 30th?) */ //_____________________________________ /* for simplicity of distribution on the web, I have copied the code of a Date class into this file. Had my aim been realism and proper code organization, I would have used a Date.h and a Date.cpp. */ // // This is example code from Chapter 9.8 "The Date class" of // "Programming -- Principles and Practice Using C++" by Bjarne Stroustrup // #include using namespace std; // file Chrono.h namespace Chrono { //------------------------------------------------------------------------------ class Date { public: enum Month { jan=1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec }; class Invalid { }; // to throw as exception Date(int y, Month m, int d); // check for valid date and initialize Date(); // default constructor // the default copy operations are fine // 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 is_date(int y, Date::Month m, int d); // true for valid date bool leapyear(int y); // true if y is a leap year bool operator==(const Date& a, const Date& b); bool operator!=(const Date& a, const Date& b); ostream& operator<<(ostream& os, const Date& d); istream& operator>>(istream& is, Date& dd); const Date& default_date(); } // Chrono //------------------------------------------------------------------------------ //The definitions go into Chrono.cpp: // Chrono.cpp namespace Chrono { // member function definitions: Date::Date(int yy, Month mm, int dd) : y(yy), m(mm), d(dd) { if (!is_date(yy,mm,dd)) throw Invalid(); } const Date& default_date(); Date::Date() :y(default_date().year()), m(default_date().month()), d(default_date().day()) { } int days_in_month(int y, Date::Month m); void Date::add_day(int n) { // use linear_day() below or things get really messy if (n<0) error("add_day(): can't handle negative n"); // not yet while (days_in_month(y,m)>(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!=')') { // oops: format error is.clear(ios_base::failbit); // set the fail bit return is; } dd = Date(y,Date::Month(m),d); // update dd return is; } ostream& operator<<(ostream& os, Day d) // sloppy: I should have used a table { switch (d) { case sunday: os << "Sunday"; break; case monday: os << "Monday"; break; case tuesday: os << "Tuesday"; break; case wednesday: os << "Wednesday"; break; case thursday: os << "Thursday"; break; case friday: os << "Friday"; break; case saturday: os << "Saturday"; break; } return os; } int nmonth(int m) // number of days before the first day of month #m (january is #1) ignoring leap days { switch(m) { case Date::jan: return 0; case Date::feb: return 31; case Date::mar: return 31+28; case Date::apr: return 31+28+31; case Date::may: return 31+28+31+30; case Date::jun: return 31+28+31+30+31; case Date::jul: return 31+28+31+30+31+30; case Date::aug: return 31+28+31+30+31+30+31; case Date::sep: return 31+28+31+30+31+30+31+31; case Date::oct: return 31+28+31+30+31+30+31+31+30; case Date::nov: return 31+28+31+30+31+30+31+31+30+31; case Date::dec: return 31+28+31+30+31+30+31+31+30+31+30; } } int day_in_year(Date a) // e.g. Jan 1 is day #1, Jan 2 is day #1 and feb 3 is day #34 { int m = nmonth(a.month()-1); if (Date::feb>d) { write(d); } keep_window_open("~"); // For some Windows(tm) setups } catch (Chrono::Date::Invalid&) { cerr << "error: Invalid date\n"; keep_window_open("~"); // For some Windows(tm) setup return 1; } catch (runtime_error& e) { // this code is to produce error messages cout << e.what() << '\n'; keep_window_open("~"); // For some Windows(tm) setups } /* This has not been SYSTEMATICALLY tested, so you should be able to find bugs; I'd like to hear about those. */