// ---------------------------------------------------------------- // System Headers // ---------------------------------------------------------------- #include #include #include #include #include #include #include // ---------------------------------------------------------------- // Using Declarations // ---------------------------------------------------------------- using std::vector; using std::string; // ---------------------------------------------------------------- // Days of the week // ---------------------------------------------------------------- static const int SUNDAY = 0; static const int MONDAY = 1; static const int TUESDAY = 2; static const int WEDNESDAY = 3; static const int THURSDAY = 4; static const int FRIDAY = 5; static const int SATURDAY = 6; // ---------------------------------------------------------------- // Months of the year // ---------------------------------------------------------------- static const int JANUARY = 1; static const int FEBRUARY = 2; static const int MARCH = 3; static const int APRIL = 4; static const int MAY = 5; static const int JUNE = 6; static const int JULY = 7; static const int AUGUST = 8; static const int SEPTEMBER = 9; static const int OCTOBER = 10; static const int NOVEMBER = 11; static const int DECEMBER = 12; // ---------------------------------------------------------------- // Constants // ---------------------------------------------------------------- namespace Constants { // The year the calendar switched over from the Julian Calendar // to the Gregorian Calendar and the number of days skipped // in September of that year enum { SWITCHOVER_YEAR = 1752, SWITCHOVER_DAYS = 11 }; // The smallest/largest year this program will accept enum { MIN_YEAR = 1, MAX_YEAR = 9999 }; // The width of a day field, the number of spaces between // day fields and the number of spaces between months enum { FIELD_WIDTH = 2, DAY_SPACING = 1, MONTH_SPACING = 2 }; // Seconds in a day enum { SECONDS_PER_DAY = 24 * 60 * 60 }; }; // ---------------------------------------------------------------- // isLeapYear - Return true if 'year' is a leap year, false if not // ---------------------------------------------------------------- static bool isLeapYear(int year) { // Up to the point where the calendar switched from // Julian to Gregorian, *all* years divisible by 4 were // treated as being leap years if(year <= Constants::SWITCHOVER_YEAR) return(year % 4 == 0); // The correct calculation is used after the switchover // date, note that years divisible by 100 are excluded // (unless they also are evenly divisible by 400) return(year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)); } // ---------------------------------------------------------------- // isSwitchoverYear - Return true if 'year' is the year we // switched from Julian to Gregorian // ---------------------------------------------------------------- static bool isSwitchoverYear(int year) { return(year == Constants::SWITCHOVER_YEAR); } // ---------------------------------------------------------------- // checkYear - Return true if 'year' is valid, false otherwise // and 'errMsg' will have the error details // ---------------------------------------------------------------- static bool checkYear(int year, string &errMsg) { std::ostringstream out; if(year < Constants::MIN_YEAR || year > Constants::MAX_YEAR) { out << "Invalid year: '" << year << "', " << "Expected value: " << Constants::MIN_YEAR << "-" << Constants::MAX_YEAR; errMsg = out.str(); return(false); } return(true); } // ---------------------------------------------------------------- // checkMonth - Return true if 'month' is valid, false otherwise // and 'errMsg' will have the error details // ---------------------------------------------------------------- static bool checkMonth(int month, string &errMsg) { std::ostringstream out; if(month < JANUARY || month > DECEMBER) { out << "Invalid month: '" << month << "', " << "Expected value: " << JANUARY << "-" << DECEMBER; errMsg = out.str(); return(false); } return(true); } // ---------------------------------------------------------------- // getDaysInMonth - Return the number of days in 'month' for 'year' // ---------------------------------------------------------------- static int getDaysInMonth(int month, int year) { int nDays = 0; switch(month) { case JANUARY: case MARCH: case MAY: case JULY: case AUGUST: case OCTOBER: case DECEMBER: { nDays = 31; } break; case APRIL: case JUNE: case NOVEMBER: { nDays = 30; } break; case FEBRUARY: { nDays = 28; if(isLeapYear(year)) nDays++; } break; case SEPTEMBER: { nDays = 30; if(isSwitchoverYear(year)) nDays -= Constants::SWITCHOVER_DAYS; } break; } return(nDays); } // ---------------------------------------------------------------- // getDaysInYear - Return the number of days in 'year' // ---------------------------------------------------------------- static int getDaysInYear(int year) { int value = isLeapYear(year) ? 366 : 365; if(isSwitchoverYear(year)) value -= Constants::SWITCHOVER_DAYS; return(value); } // ---------------------------------------------------------------- // getSecondsInYear - Return the number of seconds in 'year' // ---------------------------------------------------------------- static int getSecondsInYear(int year) { return(Constants::SECONDS_PER_DAY * getDaysInYear(year)); } // ---------------------------------------------------------------- // getSecondsInMonth - Return the number of seconds in 'month' // ---------------------------------------------------------------- static int getSecondsInMonth(int month, int year) { return(Constants::SECONDS_PER_DAY * getDaysInMonth(month, year)); } // ---------------------------------------------------------------- // getCurrent - Get the current year and month // ---------------------------------------------------------------- static void getCurrent(int *yearPtr, int *monthPtr) { time_t now; time(&now); int currentYear = 1970; for(;;) { int seconds = getSecondsInYear(currentYear); if(now >= seconds) { now -= seconds; currentYear++; } else break; } *yearPtr = currentYear; int currentMonth = 1; for(;;) { int seconds = getSecondsInMonth(currentMonth, currentYear); if(now >= seconds) { now -= seconds; currentMonth++; } else break; } *monthPtr = currentMonth; } // ---------------------------------------------------------------- // getDayOfWeekFirst - Determine what day of the week that // first day of 'month' falls on for 'year' // ---------------------------------------------------------------- static int getDayOfWeekFirst(int month, int year) { // ------------------------------------------------ // Determine the day of the week of Jan 1, 'year'. // Using Jan 1, 1995 = SUNDAY as a reference point // ------------------------------------------------ int theYear = 1995; int theDayOfWeek = SUNDAY; if(theYear < year) { do { theDayOfWeek += (getDaysInYear(theYear) % 7); theDayOfWeek %= 7; theYear++; } while(theYear < year); } else if(theYear > year) { do { theYear--; theDayOfWeek += 7 - (getDaysInYear(theYear) % 7); theDayOfWeek %= 7; } while(theYear > year); } // ------------------------------------------ // Now, work out the day of the week for the // first day of 'month' // ------------------------------------------ int theMonth = 1; while(theMonth < month) { theDayOfWeek += getDaysInMonth(theMonth, theYear) % 7; theDayOfWeek %= 7; theMonth++; } return(theDayOfWeek); } // ---------------------------------------------------------------- // isPositiveInteger - Return true if 's' is a positive integer // false otherwise // ---------------------------------------------------------------- static bool isPositiveInteger(const string &s) { int n = s.size(); if(n == 0) return(false); for(int i = 0; i < n; i ++) if(!isdigit(s[i])) return(false); return(true); } // ---------------------------------------------------------------- // makeBlank - Create and return a blank string of size 'length' // ---------------------------------------------------------------- static string makeBlank(int length) { return(string(length, ' ')); } // ---------------------------------------------------------------- // doCenter - Center 's' within the specified 'width' // ---------------------------------------------------------------- static string doCenter(int width, const string &s) { int n = s.size(); if(n >= width) return(s); int before = (width - n) / 2; int after = width - (n + before); return(makeBlank(before) + s + makeBlank(after)); } // ---------------------------------------------------------------- // MonthInfo // ---------------------------------------------------------------- class MonthInfo { public: // ---------------------------------------------------------------- // Constructor - Assumes 'month' and 'year' have already been // checked to ensure they are valid // ---------------------------------------------------------------- MonthInfo(int month, int year) : mMonth(month), mYear(year) { // Assign month name and create 6 rows (initially // all empty) for the calendar output static string names[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; mName = names[mMonth - 1]; for(int i = 0; i < 6; i ++) { vector theRow; for(int j = SUNDAY; j <= SATURDAY; j ++) theRow.push_back(makeBlank(Constants::FIELD_WIDTH)); mRows.push_back(theRow); } // Work out the number days in this month then // create a list of those days { 1, 2, ... } // (note the special handling for SEPTEMBER // of SWITCHOVER_YEAR) int daysInMonth = getDaysInMonth(mMonth, mYear); vector days; for(int i = 1; i <= daysInMonth; i ++) days.push_back(i); if(mYear == Constants::SWITCHOVER_YEAR && mMonth == SEPTEMBER) { for(int i = 2, n = days.size(); i < n; i ++) days[i] += Constants::SWITCHOVER_DAYS; } // Determine which day of the week the first of // the 'mMonth' falls on int dayOfWeekFirst = getDayOfWeekFirst(mMonth, mYear); int nDays = days.size(); int dayIndex = 0; int rowIndex = 0; // Create our calendar data, we'll start outputting // numbers when we reach the slot for the first of // the month and will continue until we've used up // all the values in 'days'. while(dayIndex < nDays) { for(int i = SUNDAY; i <= SATURDAY; i ++) { bool setValue = false; if(rowIndex == 0) { if(i >= dayOfWeekFirst) setValue = true; } else { if(dayIndex < nDays) setValue = true; } if(setValue) mRows[rowIndex][i] = formatDay(days[dayIndex++]); } rowIndex++; } } static string getDayHeadings() { // These abbreviations should be of width 'FIELD_WIDTH' static string names[] = { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" }; static string theHeadings = ""; if(theHeadings == "") { std::ostringstream out; for(int i = SUNDAY; i <= SATURDAY; i ++) out << names[i] << makeBlank(Constants::DAY_SPACING); theHeadings = out.str(); } return(theHeadings); } static string formatDay(int number) { std::ostringstream out; out << std::setw(Constants::FIELD_WIDTH) << number; return(out.str()); } vector toStringVector(bool includeYear) { vector theStringVector; std::ostringstream title; title << mName; if(includeYear) title << " " << mYear; string dayHeadings = getDayHeadings(); theStringVector.push_back( doCenter(dayHeadings.size(), title.str()) ); theStringVector.push_back(dayHeadings); for(int i = 0, nRows = mRows.size(); i < nRows; i ++) { std::ostringstream out; for(int j = 0, nColumns = mRows[i].size(); j < nColumns; j ++) out << mRows[i][j] << makeBlank(Constants::DAY_SPACING); theStringVector.push_back(out.str()); } return(theStringVector); } string toString() { string theString; vector theStringVector = toStringVector(true); for(int i = 0, n = theStringVector.size(); i < n; i ++) theString += (i == 0 ? "" : "\n") + theStringVector[i]; return(theString); } private: string mName; int mMonth, mYear; vector< vector > mRows; }; // ---------------------------------------------------------------- // main // ---------------------------------------------------------------- int main(int argc, char **argv) { int selectedYear = -1; int selectedMonth = -1; if(argc < 2) { getCurrent(&selectedYear, &selectedMonth); } else { string errMsg; int yearIndex = 1; if(argc > 2) { yearIndex++; if(!isPositiveInteger(argv[1])) { std::cout << argv[0] << ": Positive integer expected " << "for month (" << argv[1] << ")\n"; return(1); } if(!checkMonth(selectedMonth = atoi(argv[1]), errMsg)) { std::cout << argv[0] << ": " << errMsg << "\n"; return(1); } } if(!isPositiveInteger(argv[yearIndex])) { std::cout << argv[0] << ": Positive integer expected " << "for year (" << argv[yearIndex] << ")\n"; return(1); } if(!checkYear(selectedYear = atoi(argv[yearIndex]), errMsg)) { std::cout << argv[0] << ": " << errMsg << "\n"; return(1); } } if(selectedMonth == -1) { string spacing = makeBlank(Constants::MONTH_SPACING); for(int i = JANUARY; i <= OCTOBER; i += 3) { MonthInfo m1(i, selectedYear); MonthInfo m2(i + 1, selectedYear); MonthInfo m3(i + 2, selectedYear); vector v1 = m1.toStringVector(false); vector v2 = m2.toStringVector(false); vector v3 = m3.toStringVector(false); int n = v1.size(); for(int j = 0; j < n; j ++) { std::ostringstream theOutput; theOutput << v1[j] << spacing << v2[j] << spacing << v3[j]; string theLine = theOutput.str(); if(i == JANUARY && j == 0) { // Before we output any of our actual // calendar data, we need to output the // year we're generating the calendar for. theOutput.str(""); theOutput << selectedYear; std::cout << doCenter(theLine.size(), theOutput.str()) << "\n\n"; } std::cout << theLine << "\n"; } } std::cout << "\n"; } else { MonthInfo m(selectedMonth, selectedYear); std::cout << m.toString() << "\n"; } return(0); }