Date Class Example
Fall 2001
Table of Contents
1. Introduction
2. Source Code
Instructor Comments
Section 1. Introduction

This is a simple class that represents a calendar date. Of particular interest is: 1) shows how to use static members in a class and 2) shows how to use string streams to handle multiple forms of user input gracefully.

Section 2. Source Code
date.h
   1// date.h
   2// Author: t a y l o r@msoe.edu and sections 2 and 6 of the
   3//         MSOE Spring 2001 CS183 course
   4// Date: 3-27-2001
   5// Purpose: Defines a Date class which represents calendar dates.
   6// Modifications:
   7//  3-29-2001, t a y l o r@msoe.edu Added postfix increment and decrement,
   8//                                insertion, and extraction operators
   9//                             Changed numDays from a vector to a static array
  10//                             Added monthNames static array
  11//                             Added lastDayOfMonth, niceRead, niceDisplay
  12//                                functions
  13//  9-26-2001, t a y l o r@msoe.edu Made leapYear() and valid() functions static
  14
  15#ifndef DATE_H
  16#define DATE_H
  17
  18#include <fstream>
  19using std::ostream;
  20using std::istream;
  21
  22class Date {
  23public:
  24  // Default constructor
  25  Date();
  26
  27  // Constructor that is passed the month, day, and year
  28  //  Assigns default date if passed an invalid date           [9-26-01-CCT]
  29  Date(unsigned int mn, unsigned int day, int yr);
  30
  31  // Copy constructor
  32  Date(const Date& rhs);
  33
  34  // Assignment operator
  35  Date& operator=(const Date& rhs);
  36
  37  // Destructor
  38  ~Date();
  39
  40  // Accessors to get the month, day, and year
  41  unsigned int getMonth() const;
  42  unsigned int getDay() const;
  43  int getYear() const;
  44
  45  // Mutator to set the month, day, and year of the call date object
  46  //  Does not modify the date if an invalid date is passed [9-26-01-CCT]
  47  bool setDate(unsigned int mn, unsigned int dy, int yr);
  48
  49  // Prefix increment operator that advances the date by one day
  50  Date& operator++();
  51
  52  // Postfix increment operator that advances the date by one day
  53  Date operator++(int);                            // [3-29-01-CCT]

These two functions define increment. As you know, it is possible to do a pre-increment, cout << ++i, which increments i before sending it to the output stream. It is also possible to do a post-increment, cout << i++, which increments i after sending it to the output stream. We need to have a way of distinguishing these two functions when implementing them. The way this is done in C++ is to indicate the post-increment operator by placing an int in the parameter list. In reality, an int is not passed to the function, it is just used to distinguish the two functions. The same is true for the decrement operators which follow.

  54
  55  // Prefix decrement operator that advances the date by one day
  56  Date& operator--();
  57
  58  // Postfix decrement operator that advances the date by one day
  59  Date operator--(int);                            // [3-29-01-CCT]
  60
  61  // Sends object data to the output stream in the following format:
  62  //  11-23-2000
  63  void display(ostream& os) const;
  64
  65  // Sends object data to the output stream in the following format:
  66  //  November 23, 2000
  67  void niceDisplay(ostream& os) const;             // [3-29-01-CCT]
  68
  69  // Reads a date from the input stream.  The date must be in the following
  70  //  form: mm?dd?yyyy where mm is the month, dd is the day, and yyyy is the
  71  //  year.  The ? can be any character.
  72  bool read(istream& is);

The read function is not really necessary as the niceRead function does everything the read function does and more.

  73
  74  // Reads a date from the input stream.  The date must be in either of
  75  //  the following forms: 11?23?2000
  76  //  or                   November 23? 2000 where ? can be any character
  77  bool niceRead(istream& is);                      // [3-29-01-CCT]
  78
  79private:
  80  unsigned int month;
  81  unsigned int day;
  82  int year;
  83  static const unsigned int numDays[];             // [3-29-01-CCT]

Originally, line 83 was:

       83  vector<unsigned int> numDays;

However, we replaced the vector data member because it was a waste of space to include a copy of numDays in every Date object. In its place you make use of a static array. The static keyword indicates that only one instance of this array will be created even if more than one Date object is created. (This is the way static data members work.) Please note the code at the beginning of the date.cpp file initializes the array with the appropriate values. I chose to use an array instead of a vector because it made the initialization somewhat easier.

The monthNames array (line 83) holds a character array that specifies the name of each month. This array is used by the niceRead and niceDisplay functions and is a little cleaner than the obvious alternative of a switch statement or series of if statements. Similarly, an array of character arrays was chosen here to simplify initialization (see the beginning of date.cpp).

  84
  85  static const char* monthNames[];                 // [3-29-01-CCT]
  86
  87  // Returns true if the date occurs in a leap year
  88  static bool leapYear(int yr);                  // [9-26-01-CCT]
  89
  90  // Returns true if the date is a valid date
  91  static bool valid(unsigned int mn, unsigned int dy, int yr); // [9-26-01-CCT]
  92
  93  // Returns true if the date is on the last day of the month
  94  bool lastDayOfMonth() const;                     // [3-29-01-CCT]
  95};
  96
  97// Stream insertion and extraction operators
  98ostream& operator<<(ostream& os, const Date& date);// [3-29-01-CCT]
  99istream& operator>>(istream& is, Date& date);      // [3-29-01-CCT]
 100
 101#endif

Some books/professors will make the extraction and insertion operators friends of the Date class. Doing this will allow them to access the private data members of the Date class. In that case, there is no need to create the display and read member functions. We could declare the insertion operator as a friend function by adding the following line anywhere in the class definition.

     20.5  friend ostream& operator<<(ostream& os, const Date& date);

I would encourage you to avoid this practice. Allowing functions outside of the class to modify private data members opens the opportunity for the data members to be given (perhaps accidentally) invalid values. Even if you are able to make sure that the friend functions are only written/modified by the same person who wrote the class, it is still safer to create member functions that do the work and call them with the auxillary functions (the functions that are not members of the class). The main reason for this is that the auxillary functions cannot be made const functions whereas the member functions can.

Date.cpp
   1// date.cpp
   2// Author: t a y l o r@msoe.edu and sections 2 and 6 of the
   3//         MSOE Spring 2001 CS183 course
   4// Date: 3-27-2001
   5// Purpose: Defines a Date class which represents calendar dates.
   6// Modifications:
   7//  3-29-2001, t a y l o r@msoe.edu Added postfix increment and decrement,
   8//                                insertion, and extraction operators
   9//                             Changed numDays from a vector to a static array
  10//                             Added monthNames static array
  11//                             Added lastDayOfMonth, niceRead, niceDisplay
  12//                                functions
  13//  9-26-2001, t a y l o r@msoe.edu Made leapYear() and valid() functions static
  14//                             Changed error handling for incorrect date
  15//                                assignment so that it retains its old date
  16//                                instead of "assert"ing
  17//                             Removed implemenation of decrement operators
  18// 10-15-2002, t a y l o r@msoe.edu Added self-assignment check to operator=
  19
  20#include "date.h"
  21#include <string>
  22#include <cassert>
  23#include <iostream>
  24using std::cout;
  25using std::endl;
  26using std::string;
  27
  28// Initialize and set value for the numDays and monthName arrays
  29// Notice that this is done in the date.cpp file outside of any
  30//  functions.
  31const unsigned int Date::numDays[] = {0, 31, 28, 31, 30, 31, 30,
  32                                         31, 31, 30, 31, 30, 31};
  33const char* Date::monthNames[] = {"", "January", "February", "March",
  34                                      "April", "May", "June", "July",
  35                                      "August", "September", "October",
  36                                      "November", "December"};
  37
  38
  39// t a y l o r@msoe.edu and CS183 class, 3-27-01
  40// Modified 3-29-01, t a y l o r@msoe.edu: Used initializer list, removed numDays
  41//                                     vector stuff
  42// Modified 9-26-01, t a y l o r@mose.edu: Now creates a default value when
  43//                                     an invalid date is entered
  44Date::Date(unsigned int mn, unsigned int dy, int yr) : month(mn), day(dy),
  45  year(yr)

Here we set the values of the data members in an initializer list. The initializer list allows us to assign values to the data members as they are instantiated (when memory is allocated for them). This is slightly more efficient than instantiating the objects before the constructor is called and then assigning values in the constructor.

  46{
  47  if(!valid(mn, dy, yr)) {
  48    Date temp;
  49    *this = temp;
  50  }
  51}

Another way of dealing with the error checking would have just been to use the assert function. We could replace the entire function with the following line:

       47  assert(valid(mn, dy, yr));

The assert function (it is really a macro, but we don't need to get into that here) is used when compiling in Debug mode. When the argument passed to assert is true, nothing happens. If the argument is false, an error message is display, and the program crashes. This can be handy when developing a program. We will make use of it periodically because it is an easy way to do some error checking. It is definitely not the best way to do error checking though. A more robust approach is to make use of exceptions to handle errors. We will discuss exceptions towards the end of the quarter. Perhaps we will come back to this example and deal with the errors "correctly" at that time.

  52
  53
  54// t a y l o r@msoe.edu and CS183 class, 3-27-01
  55// Modified 3-29-01, used initializer list, removed numDays vector stuff
  56Date::Date() : month(1), day(1), year(2001)
  57{
  58  // Nothing else to do
  59}

I have included the preferred default constructor implementation above. If we did not have the static array for the number of days in each month, I would need to do something like this:

      Date::Date(unsigned int mn, unsigned int dy, int yr) : month(mn), day(dy),
        year(yr), numDays(13)  // 13 tells it to create a vector with 13 elements
      {
        assert(valid());
        numDays[1]=31;
         // .
         // .
         // .
        numDays[12]=31;
      }

The 13 in the initializer list calls the vector class' one integer argument constructor. The constructor creates a vector with thirteen elements. Within the body of the function we must set the correct values for the vector.

Another, even less desirable version of the constructor is given below.

      Date::Date(unsigned int mn, unsigned int dy, int yr)
      {
        month=mn;
        day=dy;
        year=yr;
        assert(valid());
        numDays.push_back(31);
         // .
         // .
         // .
        numDays.push_back(31);
      }
  60
  61
  62// t a y l o r@msoe.edu and CS183 class, 3-27-01
  63// Modified 3-29-01, t a y l o r@msoe.edu: Used initializer list, removed
  64//                                     numDays vector stuff
  65Date::Date(const Date& rhs) : month(rhs.month), day(rhs.day),
  66  year(rhs.year)
  67{
  68  // Nothing else to do
  69}
  70
  71// t a y l o r@msoe.edu, 3-27-01
  72Date::~Date()
  73{
  74  // Nothing to do
  75}
  76
  77
  78// t a y l o r@msoe.edu and CS183 class, 3-27-01
  79// Modified 10-15-02, t a y l o r@msoe.edu: added self-assignment check
  80Date& Date::operator=(const Date& rhs)
  81{
  82  if(this!=&rhs) {
  83    month = rhs.month;
  84    day = rhs.day;
  85    year = rhs.year;
  86  }
  87  return *this;
  88}
  89
  90
  91// t a y l o r@msoe.edu and CS183 class, 3-27-01
  92unsigned int Date::getMonth() const
  93{
  94  return month;
  95}
  96
  97
  98// t a y l o r@msoe.edu and CS183 class, 3-27-01
  99unsigned int Date::getDay() const
 100{
 101  return day;
 102}
 103
 104
 105// t a y l o r@msoe.edu and CS183 class, 3-27-01
 106int Date::getYear() const
 107{
 108  return year;
 109}
 110
 111
 112// t a y l o r@msoe.edu, 3-27-01
 113// Modified: 9-26-01, t a y l o r@msoe.edu: Rewrote using new valid() function
 114bool Date::setDate(unsigned int mn, unsigned int dy, int yr)
 115{
 116  bool successful = valid(mn, dy, yr);
 117  if(successful) {
 118    month = mn;
 119    day = dy;
 120    year = yr;
 121  }
 122  return successful;
 123}
 124
 125
 126// t a y l o r@msoe.edu and CS183 class, 3-29-01
 127Date& Date::operator++()
 128{
 129  if(lastDayOfMonth()) {
 130    if(12==month) {
 131      ++year;
 132      month=1;
 133    } else {
 134      ++month;
 135    }
 136    day=1;
 137  } else {
 138    ++day;
 139  }
 140  return *this;
 141}

Suppose today is a Date object containing March 29, 2001. If I were to execute the following line of code:

        cout << ++today;

I would expect that March 30, 2001 would be sent to the console output stream and that today would now contain March 30, 2001. In order for March 30, 2001 to be sent to the console output stream, ++today must evaluate to (i.e., return) a Date object with the value of March 30, 2001. Basically, we want to return the new value of today. We do this by making use of the this pointer.

The this pointer is a pointer that points to the object that called the member function that we are in (in this case, operator++()). We want to return the object (not a pointer to the object) that called operator++(), so we need to dereference the pointer. Hence, we return *this.

 142
 143
 144
 145// t a y l o r@msoe.edu, 3-29-01
 146Date Date::operator++(int)
 147{
 148  Date temp=*this;
 149  ++(*this);
 150  return temp;
 151}

Suppose today is a Date object containing March 29, 2001. If I were to execute the following line of code:

        cout << today++;

I would expect that March 29, 2001 would be sent to the console output stream and that today would now contain March 30, 2001. In order for March 29, 2001 to be sent to the console output stream, ++today must evaluate to (i.e., return) a Date object with the value of March 29, 2001. We do this by making a copy of the original value of today (we call it temp in the above function). We then call the pre-increment operator to make today now contain March 30, 2001. Finally, we return the temp object which contains the original Date value.

The decrement operators are similar.

 152
 153
 154// t a y l o r@msoe.edu and CS183 class, 3-29-01
 155// Modified: 9-26-01, t a y l o r@msoe.edu: removed implementation
 156Date& Date::operator--()
 157{
 158  // This function doesn't work correctly
 159  std::cerr << "Decrement is broken" << std::endl;
 160  return *this;
 161}
 162
 163
 164// t a y l o r@msoe.edu, 3-29-01
 165// Modified: 9-26-01, t a y l o r@msoe.edu: removed implementation
 166Date Date::operator--(int)
 167{
 168  std::cerr << "Decrement is broken" << std::endl;
 169  return *this;
 170}
 171
 172
 173// t a y l o r@msoe.edu, 3-29-01
 174void Date::display(ostream& os) const
 175{
 176  os << month << '-' << day << '-' << year;
 177}
 178
 179
 180// t a y l o r@msoe.edu, 3-29-01
 181void Date::niceDisplay(ostream& os) const
 182{
 183  os << monthNames[month] << ' ' << day << ", " << year;
 184}

By making use of the monthNames array, this function is much less complicated than it would be if we had to write a switch statement with a case for each month. An alternative version of this function might look something like:

      void Date::niceDisplay(ostream& os) const;
      {
        string strmn;
        switch (month) {
          case 1:
            strmn = "January";
            break;
          case 2:
            strmn = "February";
            break;
          // .
          // .
          // .
          case 12:
            strmn = "December";
            break;
          default:
            strmn = "Unknown";
        }
        os << strmn << ' ' << day << ", " << year;
      }
 185
 186
 187// t a y l o r@msoe.edu, 3-29-01
 188bool Date::read(istream& is)
 189{
 190  char separator;
 191  unsigned int mn;
 192  unsigned int dy;
 193  int yr;
 194  bool successful = false;
 195  if(is>>mn>>separator>>dy>>separator>>yr) {
 196    successful = setDate(mn,dy,yr);
 197  }
 198  return successful;
 199}
 200
 201
 202// t a y l o r@msoe.edu, 3-29-01
 203bool Date::niceRead(istream& is)
 204{
 205  char separator;
 206  string strmn;
 207  unsigned int mn;
 208  unsigned int dy;
 209  int yr;
 210  bool successful = false;
 211  if(is>>mn) {
 212    if(is>>separator>>dy>>separator>>yr) {
 213      successful = setDate(mn,dy,yr);
 214    }
 215  } else {
 216    is.clear(); // Clear the error flag in the stream so that it can be read from
 217    if(is>>strmn>>dy>>separator>>yr) {
 218      // This for loop is a bit strange.  Notice that it ends with a semicolon.
 219      //  so there is no body to the loop.  It just keeps looking through the
 220      //  array of names to see if there is a match with strmn.  When it finds
 221      //  a match, it stops (and i is the correct month).  If no match is
 222      //  found, it ends when i=13 (an invalid value that will make the
 223      //  setDate function return false.
 224      unsigned int i=1;
 225      for(; i<=12 && monthNames[i]!=strmn; ++i);
 226      successful = setDate(i,dy,yr);
 227    }
 228  }
 229  return successful;
 230}

Notice how we made use of the monthNames array here. This function would be significantly more complicated if we didn't have the array. The alternative would be that we would have to have twelve if statements that would check to see if they matched strmn. Note: we could not use a switch statement here because the switch will not work on strings or character arrays. In place of the for loop (line 221) we would need to do something like this:

            i=13;  // Make setDate fail if we don't match a month name
            if(strmn="January") {
              i = 1;
            } else if(strmn="February") {
              i = 2;
            } // .
              // .
              // .
            } else if(strmn="December") {
              i = 12;
            }
 231
 232
 233// t a y l o r@msoe.edu, 3-29-01
 234bool Date::leapYear(int yr)
 235{
 236  bool isLeap=false;
 237  if(0==yr%400 || (0!=yr%100 && 0==yr%4) ) {
 238    isLeap = true;
 239  }
 240  return isLeap;
 241}

A year is a leap year if the year is either divisible by 400 (e.g., 2000) or not divisible by 100 but is divisible by 4 (e.g., 2004).

 242
 243
 244// t a y l o r@msoe.edu, 3-29-01
 245bool Date::lastDayOfMonth() const
 246{
 247  bool isLastDay = (numDays[month]==day);
 248  if(month==2 && leapYear(year)) {
 249    isLastDay = (29==day);
 250  }
 251  return isLastDay;
 252}
 253
 254
 255// t a y l o r@msoe.edu, 9-26-01
 256bool Date::valid(unsigned int mn, unsigned int dy, int yr)
 257{
 258  bool isValid = false;
 259  if(0!=dy && (1<=mn && mn<=12) &&
 260     (dy<=numDays[mn] || (2==mn && 29==dy && leapYear(yr)))) {
 261    isValid = true;
 262  }
 263  return isValid;
 264}
 265
 266
 267// t a y l o r@msoe.edu, 3-29-01
 268ostream& operator<<(ostream& os, const Date& date)
 269{
 270   date.niceDisplay(os);
 271   return os;
 272}
 273
 274
 275// t a y l o r@msoe.edu, 3-29-01
 276istream& operator>>(istream& is, Date& date)
 277{
 278   date.niceRead(is);
 279   return is;
 280}
Copyright   2001 Dr. Christopher C. Taylor t a y l o r@m s o e.e d u Last updated: Tue Oct 15 16:42:46 2002