This program takes any user input year since 1753 and month and creates a calendar for it. However I'm having issues with the offset which is the day the month starts out on. As far as I can tell it is just the offset that is off and everything else seems to work great. Here is my code.
#include <iostream>
#include <iomanip>
using namespace std;
int getMonth(int month);
int getYear(int year);
int computeOffset(int year, int month);
int numDaysYear(int year);
int numDaysMonth(int year, int month);
bool isLeapYear(int year);
void display(int year, int month, int offset);
/**********************************************************************
* This function will call all the functions necessary to make a calendar
* for any given month and year.
***********************************************************************/
int main()
{
int numDays;
int offset;
int month;
int year;
month = getMonth(month);
year = getYear(year);
offset = computeOffset(year, month);
display(year, month, offset);
return 0;
}
/***********************************************************************
* Gets the month number.
**********************************************************************/
int getMonth(int month)
{
cout << "Enter a month number: ";
cin >> month;
while ( month < 1 || month > 12)
{
cout << "Month must be between 1 and 12.\n"
<< "Enter a month number: ";
cin >> month;
}
return month;
}
/***********************************************************************
* Gets the year.
**********************************************************************/
int getYear(int year)
{
cout << "Enter year: ";
cin >> year;
while ( year < 1753)
{
cout << "Year must be 1753 or later.\n"
<< "Enter year: ";
cin >> year;
}
return year;
}
/***********************************************************************
* Computes the offset.
**********************************************************************/
int computeOffset(int year, int month)
{
int offset = 0;
int count = year - 1753;
for ( int iYear = 0; iYear < count; iYear++)
{
offset = ( offset + 365 + isLeapYear(year)) % 7;
}
for ( int iMonth = 1; iMonth < month; iMonth++)
{
offset = ( offset + numDaysMonth(year, iMonth)) % 7;
}
return offset;
}
/***********************************************************************
* Computes the number of days in the given year.
**********************************************************************/
int numDaysYear(int year)
{
int daysYear = 365 + isLeapYear(year);
return daysYear;
}
/***********************************************************************
* Calculates the number of days in the given month.
**********************************************************************/
int numDaysMonth(int year, int month)
{
int daysMonth;
if ( month == 1)
daysMonth = 31;
else if ( month == 2)
{
if (isLeapYear(year) == true)
daysMonth = 29;
else
daysMonth = 28;
}
else if ( month == 3)
daysMonth = 31;
else if ( month == 4)
daysMonth = 30;
else if ( month == 5)
daysMonth = 31;
else if ( month == 6)
daysMonth = 30;
else if ( month == 7)
daysMonth = 31;
else if ( month == 8)
daysMonth = 31;
else if ( month == 9)
daysMonth = 30;
else if ( month == 10)
daysMonth = 31;
else if ( month == 11)
daysMonth = 30;
else if ( month == 12)
daysMonth = 31;
return daysMonth;
}
/***********************************************************************
* Determines if given year is a leap year.
**********************************************************************/
bool isLeapYear(int year)
{
if ( year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
return true;
else
return false;
}
/**********************************************************************
* Displays the calender table.
**********************************************************************/
void display(int year, int month, int offset)
{
int dayOfWeek;
int day;
cout << endl;
if ( month == 1)
cout << "January";
else if ( month == 2)
cout << "February";
else if ( month == 3)
cout << "March";
else if ( month == 4)
cout << "April";
else if ( month == 5)
cout << "May";
else if ( month == 6)
cout << "June";
else if ( month == 7)
cout << "July";
else if ( month == 8)
cout << "August";
else if ( month == 9)
cout << "September";
else if ( month == 10)
cout << "October";
else if ( month == 11)
cout << "November";
else if ( month == 12)
cout << "December";
cout << ", " << year << "\n";
// Display month header
cout << " Su Mo Tu We Th Fr Sa\n";
// Gets the correct offset width and end the line on the right
//day of the week
if (offset == 0)
{
day = 2;
cout << setw(6);
}
else if (offset == 1)
{
day = 3;
cout << setw(10);
}
else if (offset == 2)
{
day = 4;
cout << setw(14);
}
else if (offset == 3)
{
day = 5;
cout << setw(18);
}
else if (offset == 4)
{
day = 6;
cout << setw(22);
}
else if (offset == 5)
{
day = 7;
cout << setw(26);
}
else if (offset == 6)
{
day = 1;
cout << setw(2);
}
else
cout << "Error offset must be >= 0 and <=6\n";
// The loop for displaying the days and ending the line in the right place
for ( dayOfWeek = 1; dayOfWeek <= numDaysMonth(year, month); dayOfWeek++ )
{
cout << " " << setw(2) << dayOfWeek;
++day;
if (day == 8)
{
cout << "\n";
day = 1;
}
}
if ( day >= 2 && day <= 7)
cout << "\n";
return;
}`
I know this was posted over a year ago, but I do have a solution for future visitors. In the computeOffset function in the original post, the function isn't actually counting how many days have passed in all. You need to count how many years have passed, and how many days were in those years, as well as the days in the months after those years, if that makes any sense. Here is the code for my working computeOffset function:
Hopefully this makes sense, sorry if some variables have weird names or if the style isn't what you're used to. Hope this helped!
New answer for old question. Rationale for new answer: Better tools and technology in this area.
This answer is heavily using this free, open-source header-only library. I'm going to present this answer starting at the highest level, and drilling down to the lower level details. But at all levels at no time do we have to get into detailed calendrical computations.
"date.h"
handles that for us.Here's
main
:This just output for me:
One could print out next year's calendar with:
I'll start out with the statement:
The literal
2017_y
is an object of typedate::year
, not a simple integer. Having types that meanyear
andmonth
means it is far less likely to mix up these concepts. Mistakes tend to be caught at compile time.print_calendar_year
is pretty simple:The expression
year/month
creates a type calleddate::year_month
which is nothing more than a simple struct{year, month}
. So this function simply sets up a loop to iterate from Jan of the year y, to the next Jan, excluding the next Jan. It is all quite readable. And note that "bareint
s" are not allowed. Everything has a non-integral type.print_calendar_month
is where the rubber meets the road:os << format("%B %Y\n", sys_days{ym/1});
is what prints out the title for each month (e.g.January 2016
). These are strftime-like formatting flags that will respect the localization settings of the current globalstd::locale
(as much as the OS supports).The subexpression
ym/1
creates a typedate::year_month_day
which stands for the first day of the indicated month and year.date::year_month_day
is a simply class holding{year, month, day}
.sys_days
is achrono::time_point
based onsystem_clock
with a precision ofdays
.date::format
can take any precisionsystem_clock
time_point
and format it using strftime-like formatting flags. Ayear_month_day
can be converted to asys_days
as shown. This is a conversion from a{year, month, day}
field type to a serial{count of days}
type.os << " S M T W T F S\n";
obviously prints out the day-of-the-week header for the calendar.auto wd = unsigned{weekday{ym/1}};
finds the day of the week of the first day of the month and converts thatweekday
into anunsigned
using the encoding[Sun == 0, Sat == 6]
. [Note: gcc requires the syntaxunsigned(weekday{ym/1})
. It doesn't like the{}
forunsigned
. — end note]os << string(wd*3, ' ');
just prints out 3 spaces for each day before the first day of the month to pad out the first row.auto const e = (ym/last).day();
is a constant of typedate::day
that is equal to the last day of the month for this year and month combination.for (day d = 1_d; d <= e; wd = 0)
Starting with day 1 loop until the last day of the month (inclusive) and set the
unsigned wd
back to the encoding for Sunday on each iteration.for (; wd < 7 && d <= e; ++wd, ++d)
: Until you reach the end of the week or the end of the month, increment both day of the week and day of the month.os << setw(3) << unsigned{d};
: Convert the day of the month to anunsigned
and print it out right-aligned in a width a of 3 spaces.os << '\n';
return after printing the week.And that's the bulk of the program! Almost all of the tricky calendrical logic is encapsulated within these two lines of code:
For completeness here are the functions to get the current
date::year
and the currentdate::year_month
:Both of these simply truncate a
system_clock::time_point
returned fromsystem_clock::now()
to a precision ofdays
usingfloor
, and then convert that days-precisiontime_point
to adate::year_month_day
type. This type then has getters foryear
andmonth
to pick out the desired partial calendar types.Update
Well, TemplateRex asked a question below that I didn't want to answer at first, and then I couldn't help myself because the answer highlights how powerful
"date.h"
is to work with. ;-)The question is:
Evidently so, because I wasn't about to type in all that above manually! ;-)
It requires a rewrite of
print_calendar_year
and the introduction of a a couple of new functions, most notably:This function prints just one line of the calendar associated with the
year_month ym
and is the heart of this 3x4 format.I also thought it would be fun to make this program localizable so that the desired first-day-of-week could be printed out, as well as localized names for the month and day-of-week (as much as the
std::locale
on your platform allows).The lines are numbered [0, infinity]. Line 0 prints out the month year such as
January 2016
. Line 1 prints out the day-of-week headers:Su Mo Tu We Th Fr Sa
. And then lines [2, infinity] print out the days of the month.Why infinity?
Because different months take different number of lines, so I wanted to be able to tell a
year/month
to print a next line even if it didn't need to (because another month in the quarter needed it). So when you ask for a calendar to print out a line that it doesn't need, it just outputs the proper number of' '
for padding purposes.Enough intro, here's the function:
So switch on line number [0, infinity], and for each line number, do the right thing:
0.
Print out the Month Year heading.This passes to
format
thelocale
of theos
to get the localized month name.1.
Print out the day-of-the-week heading.This passes to
format
thelocale
of theos
to get the localized weekday names, and prints the first 2 characters. This is (unfortunately) only approximately correct when these are multi-byte characters, but this post is mostly about calendars, not Unicode.2.
Print out the first week, which might be prefixed with spaces. The number of spaces to prefix with is 3*(number of days the first of the month is past the first day of the week). Then append days until you reach the last day of the week. Note that weekday subtraction is always modulo 7 so you don't have to worry about the underlying encoding of the days of the weeks. The weekdays form a circular range. This does require something along the lines of thisdo-while
as opposed to a traditionalfor
when looping over all the days in a week.3 - infinity
. Ah, here's the fun part.There's a type in
"date.h"
calledyear_month_weekday
which is a type storing{year, month, weekday, unsigned}
. This is how you might specify Mother's day: The second Sunday of May:sun[2]/may/2016
. This expression creates a struct{2016, 5, 0, 2}
(roughly speaking). And so if theswitch
lands here, then we are looking for the [first, last] Sunday of this month and year, where the exact index is dependent uponline
, and whether or not we printed a Sunday out on line 2.Also key, this library allows any index to be used:
index
could be 1, or it could be 57. The above line compiles and is not a run-time error.But months can't have 57 Sundays (or Mondays or whatever)!
No problem. You can ask if
ym/firstdow[index]
is a valid date. This is what the next line does:If the date is valid, then you've got work to do. Else you just print out a blank row.
If we've got work to do, then convert the
year_month_weekday
to ayear_month_day
so that you can get the day of the month from it (d
). And find the last day of the month:Then iterate from the first day of the week to whichever comes first: the end of the month or the last day of the week. Print out the day of the month for each spot. And then if you didn't end on the last day of the week, print spaces to pad out to the last day of the week.
And we're done with
print_line_of_calendar_month
! Note that newlines were never output on this level. Not even inter-month padding is output on this level. Each calendar is exactly 21char
wide, and can be printed out to an arbitrary number of rows.Now we need another minor utility: What is the number of rows a calendar month needs before it starts padding with blank rows?
This is the number of days in the month, plus the number of days from the first day of the week to the first of the month, plus 2 more rows for the day-of-the-week heading and the year-month heading. Fractional weeks at the end are rounded up!
Notes:
The number of days from the first day of the week to the first of the month is simply:
(weekday{ym/1} - firstdow)
.The number of days in the month is encoded here as
((ym/last).day() - day{0})
. Note thatday{0}
is not a validday
, but can still be useful in the subtraction:day - day
gives a result of thechrono::duration
days
. Another way to say this would have been((ym/last).day() - day{1} + days{1})
.Note that
ceil<weeks>
is used here to convert the number ofdays
to number ofweeks
, rounding up to the nextweeks
if the conversion is not exact. 1 week == 1 row. This roundup accounts for the last week that ends prior to the last day of the week.Now
print_calendar_year
can be rewritten in terms of these primitives:First compute for each month how many lines it needs:
Then for each "calendar row", find the number of lines needed for that row by searching the proper subset of
ml
.And then for each line, and for each "calendar column", print out the line of the corresponding calendar month for that column.
After each line print a
'\n'
.After each calendar row, print a
'\n'
.Note that still at no time did we need to sink down into calendrical arithmetic. At this level we needed to know "7 days per week", "3 spaces per day" and "12/cols months per calendar row".
On macOS this driver:
Outputs:
Your milage may vary on how well your std::lib/OS supports localization. But now you can print your calendar out in columns of months varying among any divisor of 12 ([1, 2, 3, 4, 6, 12]), using any year, using any day of the week as the first day of the week, and using any locale (modulo OS support for locales).
Here's the output for
print_calendar_year(std::cout, 4, 2017_y);
This code does not address your issue (I could not tell what the issue was) but it may be informative to contrast it with your version.