#include "stdstuff.h" #include "Date.h" // ------------------------ Conventions ------------------------------------ // This class ensures that dates are always valid and always lie between // Jan 1, 1900 and Dec 31, 2099. // Attempts to move a date out of this range produce the appropriate // limiting value. // Invalid constructor arguments, read errors, and so result in a // date of Jan 1, 1900. // dayNumber: 1 = Jan 1, 1900 // 2 = Jan 2, 1900 // etc. // dayInYear: 1 = Jan 1 // 2 = Jan 2 // 365 = Dec 31 or Dec 30 (depending upon whether year is leap) // dayInWeek: 1 = Monday, etc. // ------------------------ Utility Functions ------------------------------ // returns true if the year is a leap year static bool yearIsLeap (int year) { return (bool) ( ((year % 4) == 0) && (((year % 100) != 0) || ((year % 400) == 0)) ); } // returns the number of days in a month static int daysInMonth (int year, int month) { const int table[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; if (yearIsLeap(year) && month == 2) { return 29; } return table[month - 1]; } // returns whether or not a date is valid static bool DDMMYYYYIsValid (int day, int month, int year) { return (year >= 1900) && (year < 2100) && (month >= 1) && (month <= 12) && (day >= 1) && (day <= daysInMonth (year, month)); } // converts a day in year to a day and a month static void dayInYearToDDMM (int year, int dayInYear, int &day, int &month) { // the number of days before each month in a "normal" year static int lastDayInMonth [12] = { 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }; int i, adjustment;; if (dayInYear <= lastDayInMonth[0]) { // january day = dayInYear; month = 1; return; } if (yearIsLeap(year)) { adjustment = 1; } else { adjustment = 0; } if (dayInYear <= lastDayInMonth[1] + adjustment) { // februrary day = dayInYear - lastDayInMonth[0]; month = 2; return; } for (i = 2; i < 12; i++) { if (dayInYear <= lastDayInMonth[i] + adjustment) { day = dayInYear - (lastDayInMonth [i - 1] + adjustment); month = i + 1; return; } } // we should never get here } // converts a day number to a day, month, and year static void dayNumberToDDMMYYYY (long dayNumber, int &day, int &month, int &year) { const int daysInNormalYear = 365, daysInLeapYear = 366, cycleLength = daysInLeapYear + 3* daysInNormalYear; long days = dayNumber - 1; int cycles; // work out how many complete four year cycles we've got. // the +1 and -1 are because the first cycle is one day shorter than // all subsequent cycles (because 1900 was not a leap year). cycles = (days + 1) / cycleLength; if (cycles != 0) { days -= (cycles * cycleLength) - 1; } year = 1900 + (cycles * 4); // see how many years into the next cycle we are. in we're in the first // cycle, things must be done a bit differently (because 1900 was not // a leap year) if (year == 1900) { year += (days / daysInNormalYear); days = days % daysInNormalYear; } else { if (days >= daysInLeapYear) { year++; days -= daysInLeapYear; // now that the leap year has been dealt with, see how // many additional normal years we have. year += (days / daysInNormalYear); days = days % daysInNormalYear; } } // we know know which year we're in, and "days + 1" is our day in year dayInYearToDDMM (year, days + 1, day, month); } // converta a day and a month to a day in year static int DDMMToDayOfYear (int year, int day, int month) { // the number of days before each month in a "normal" year static int DaysBeforeMonth [12] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; int dayOfYear; // the "-1" in the following is because month numbers start at 1 dayOfYear = DaysBeforeMonth [month - 1] + day; if ((month > 2) && yearIsLeap(year)) { dayOfYear++; // throw in the extra day } return dayOfYear; } // converts a day, month, and year into a day number static long DDMMYYYYToDayNumber (int day, int month, int year) { long days; // work out total number of days in preceding years days = ((year - 1900) * 365) + ((year - 1901) / 4); return days + DDMMToDayOfYear (year, day, month); } //---------------------------- Constants ---------------------------------- static const long highestDayNumber = DDMMYYYYToDayNumber (31, 12, 2099); //--------------------------- Member Methods ------------------------------ Date::Date () { dayNumber = 1; } Date::Date (int day, int month, int year) { if (DDMMYYYYIsValid (day, month, year)) { dayNumber = DDMMYYYYToDayNumber (day, month, year); } else { dayNumber = 1; // leave object properly initialized throw invalid_argument ("Date::Date - invalid date specified"); } } Date::Date (long dayNumber) { // within this function, "dayNumber" by itself refers to the parameter // (as the parameter declaration is "closer", it masks out the declaration // in the class definition). to get at the "dayNumber" component of the // object being created, we must use "this ->" (or "Date::"). if ( (dayNumber < 1)||(dayNumber > highestDayNumber) ) { this -> dayNumber = 1; // leave object containing a valid value throw invalid_argument ("Date::date - invalid day number"); } this -> dayNumber = dayNumber; } void Date::read (istream &is) { int day, month, year; is >> day >> month >> year; if (is.fail() || !DDMMYYYYIsValid (day, month, year)) { // we've a problem - either the read didn't work at all // or the number we got aren't valid is.clear (is.rdstate() | ios::failbit); // make sure the fail bit is set dayNumber = 1; return; } dayNumber = DDMMYYYYToDayNumber (day, month, year); } /* original implementation void Date::write (ostream &os) const { int day, month, year; dayNumberToDDMMYYYY (dayNumber, day, month, year); os << day << '/' << month << '/' << year; } */ // modified implementation (makes use of member function getDDMMYYY) void Date::write (ostream &os) const { int day, month, year; // get the day, month, and year for the object being operated upon // (apply the getDDMMYYY method to the object being operated on) getDDMMYYYY (day, month, year); os << day << '/' << month << '/' << year; } void Date::move (int days) { long newDayNumber = dayNumber + days; if ( (newDayNumber < 1)||(newDayNumber > highestDayNumber) ) { throw invalid_argument ("Date::move - attempt to move date outside range"); } dayNumber = newDayNumber; } int Date::dayInWeek () const { // January 1st, 1900 was a Monday return 1 + ((dayNumber - 1) % 7); } void Date::getDDMMYYYY (int &day, int &month, int &year) const { dayNumberToDDMMYYYY (dayNumber, day, month, year); } int Date::daysAfter (const Date &otherDate) const { return dayNumber - otherDate.dayNumber; } int Date::compareTo (const Date &otherDate) const { if (dayNumber > otherDate.dayNumber) return 1; if (dayNumber < otherDate.dayNumber) return -1; return 0; // dates must be equal } /* simple implemtation Date Date::nextDay () const { Date result; // constructed using default constructor if (dayNumber == highestDayNumber) { throw invalid_argument ("Date::nextDay - next day is out of range"); } result.dayNumber = dayNumber + 1; return result; } */ /* somewhat trickier implementation (uses private constructor) Date Date::nextDay () const { Date result (dayNumber + 1); // uses private constructor return result; } */ // the last word (creates temporary object and returns it in one step) Date Date::nextDay () const { return Date (dayNumber + 1); } Date Date::plusDays (int days) const { return Date (dayNumber + days); } bool Date::equals (const Date &otherDate) const { return dayNumber == otherDate.dayNumber; } bool Date::operator== (const Date &otherDate) const { return dayNumber == otherDate.dayNumber; }