Comp 272 Jan 31, 2005 Program 1: MyDate Due: Jan 42, 2005 (better known as Feb 11) You are to implement a Date class, which we will call MyDate. I have provided some starting elements in mydate.cpp. The general interface is to be as in Horstmann, but basically you are NOT allowed to implement advance(), etc by converting to the Julian Day format; you will calculate these by counting. The MyDate class should also take care to maintain only valid days (see the section below on constructors); that is, you should never have a Jan 32 or a Feb 30 or an illegal month. We will assume that January is month 1 (not 0). Your program will be graded as much on READABILITY as on correctness. A few guidelines are below. * Your indentation should be consistent * Your indentation should reflect program logic * Use appropriate whitespace (eg blank lines between definitions) * Use sensible variable names * program logic should be clean * a brief comment summarizing each function is in order IF you wish to implement something different from the way it is described here, I will consider your innovation to be an error UNLESS you clearly document it in a comment at an appropriate point! I like to encourage such innovation, generally, but you have to fess up when you depart from the spec. CONSTRUCTORS The constructor MyDate(int month, int day, int year) should be straightforward; all you need to do is add a check for validity. It is recommended that if the date is invalid you call assert() to signal this; eg assert(isvalid(day, month, year)); To call assert(), you will need #include at the beginning of your program. Note that my ctor has the parameters in American order month,day,year, rather than European (and logical!) order day,month,year. You will also need a "default" constructor, ie a zero-parameter ctor. You can take one of two approaches. Perhaps the simplest is to set the default date to, say, Jan 27, 1957 (my birthday; you may want to pick your own birthday). The Unix clock is zero on 1/1/1970; that is another common choice. Note that it makes *sense* here not to provide a default ctor, in that you can argue that there is no clear natural default value (there is no analogue of "zero"). However, that means all sorts of minor but annoying problems for programmers using your class (eg they can't create a nonempty vector). Give 'em a break. BASIC ACCESSORS Your class will provide day(), month(), and year() functions to return the current day, month, and year. Actually, these are provided already. BASIC MUTATORS Note that you can *not* provide mutators such as setday() and setmonth(); there is no practical way to do that while maintaining the invariant that valid MyDates stay valid. That is, if d is Jan 31, 2005, then d.setmonth(2) is invalid, even if the programmer intends to follow it with d.setday(28). Similarly, if d is Feb 28, then d.setday(31) is invalid even if the intent is to follow it with d.setmonth(1). A full-featured Date class might instead provide setdate(int month, int day, int year). You can do this if you wish, advance(ndays) This advances the day by ndays; that is, if the MyDate d is Jan 26 then d.advance(1) sets the date to Jan 27 and d.advance(31) sets the date to Feb 26. Note that if d is Dec 25, then d.advance(10) should set the date to Jan 4; that is, we wrap around. (An alternative is to have the date become "invalid"). When pushed to extremes this gets into awkward problems about leap years etc (if this is a leap year, what is d.advance(366)?); you can either disallow wrapping past Feb 28 or you can just ignore this. Here is a strategy for ndays>0: if ndays+_day <= daysinmonth(_month), just update _day otherwise: go to beginning of following month: _day = 1; _month ++; (if month == 13, then go to next year) ndays -= days left in original month Now, do the following until ndays < length of month: ndays -= daysinmonth(month); _month++; if month == 13 then go to next year advance needs to work for ndays<0 too. One approach is to revise the algorithm above to work for ndays < 0. Another is to note that advancing -500 days is the same as advancing -730 days followed by +230 days; note that -730 days is exactly two years. (Ok, that assumed no leap years; you'll have to make the negative advance be -731 if a leapyear were involved.) daysbetween(MyDate d2) This returns the number of days between the current MyDate and d1; that is, if d is May 5 and d1 is Jun 5 then d.daysbetween(d1) should return 31 and d1.daysbetween(d) should return -31. To implement this you will again count by days and months, as in advance(): if (years are equal and months are equal) just do subtraction on days advance first date to start of following month let daysbetween = days left in orig month now repeat until you get to the month/year of the second date: daysbetween += daysinmonth(month) increment month (and year if necessary) Or you can make a copy of the current day and advance it by 1 until it is equal to d2 and count how many times you did this. In computing d1.daysbetween(d2), if d2 comes after d1 then your answer should be positive. If d2 comes before, you might wish to return -d2.daysbetween(d1). Note that you have a minor problem: inside daysbetween, how do you refer to d1? There is a standard way: *this. (note the *; "this" is really a pointer.) Thus, you would return -d2.daysbetween(*this). dayofweek(); Return the day (sun=1, mon=2, ..., sat=7) of the current date. The easiest way to do this is to note that 1/1/2005 occurred on day 7. Now suppose that the daysbetween the current date and 1/1/2005 is 87. Divide this by 7 and take the remainder; this is 3. That means that the current date is 3 days beyond Saturday, that is, the day of the current date is 3. This works for negative daysbetween too, but be sure you get a positive remainder. Add 7 if necessary. WHAT YOU SHOULD TURN IN Your MyDate class, with nontrivial member function implementations *outside* the class declaration. You should also, in the same file, extend my main() so that illustrates use of each of the member functions. All the basic accessors and mutators should be tested, as well as: * advance(n), with various n between 0 and 366, and n<0, and n>366, and wraparound at yearend * daysbetween(), for various near, far, and negative combinations * dayofweek, or equiv HELPER FUNCTIONS The following are provided to illustrate certain parts of C++ and to help simplify life a little. Note that they are written to handle leapyears as well as regular years, but that the *default value* of the second parameter is "false". That is, if the second parameter is omitted the call is still legal, and a call to daysinmonth(2) returns 28. int daysinmonth1(int month, bool leapyear=false) { int days; if (month<1 || month>12) return -1; switch (month) { case 1: days = 31; break; case 2: if (leapyear) days = 29; else days=28; break; case 3: days = 31; break; case 4: days = 30; break; case 5: days = 31; break; case 6: days = 30; break; case 7: days = 31; break; case 8: days = 31; break; case 9: days = 30; break; case 10: days = 31; break; case 11: days = 30; break; case 12: days = 31; break; } /* end switch */ return days; } int daysinmonth2(int month, int leapyear=true) { // the following is a "native" array, days, initialized // statically (ie once, at compile time, not run time) // so that days[month-1] = # of days in given month. // Native arrays, like vectors, always begin at 0, // so days[] goes from 0 to 11, hence the need for month-1. static int days[] = {31,28,31,30,31,30,31,31,30,31,30,31}; if (month<1 || month>12) return -1; int d = days[month-1]; if (month==2 && leapyear) d++; return d; } // returns -1 if d1 < d2 (d1 comes before d2), 0 if d1==d2, +1 if d1>d2 static int compare(MyDate d1, MyDate d2) { if (d1.year() < d2.year()) return -1; if (d1.year() > d2.year()) return +1; // now years are equal if (d1.month() < d2.month()) return -1; if (d1.month() > d2.month()) return +1; // now months are equal if (d1.day() < d2.day()) return -1; if (d1.day() > d2.day()) return +1; // now days are equal return 0; } // the following assumes the Gregorian calendar always: static bool isleapyear(int year) { if (year % 4 != 0) return false; if (year % 400 != 0 && year % 100 == 0) return false; return true; }