#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; } } 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; // leave the object containing January 1, 1900 return; } dayNumber = DDMMYYYYToDayNumber (day, month, year); } void Date::write (ostream &os) const { int day, month, year; dayNumberToDDMMYYYY (dayNumber, day, month, year); os << day << '/' << month << '/' << year; } void Date::move (int days) { dayNumber += days; if (dayNumber < 1) { dayNumber = 1; } else if (dayNumber > highestDayNumber) { dayNumber = highestDayNumber; } } 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 }