The Calendar Problem#
The calendar problem is seemingly a difficult one. There are several different scenarios and we do not really know how to navigate them. One way to navigate different scenarios in programming is to use conditional statements, like if-then-else. At this stage in the course however, we have not “discovered” conditionals. And it is worth trying to solve the problem without looking ahead in the course material.
Solving the calendar problem without conditional statements is a fantastic learning moment. A solution without conditional statements requires some serious analysis first, illustrating that most computing problems follow the 80-20 rule: 80% thinking, 20% coding. The analysis begins by turning off our IDE, moving our focus from coding to thinking. Next, we draw a few scenarios on paper, on a notes app, whatever works for you.
A 31-day month with Sunday as its second day.#
As we try different combinations, e.g., 31-day month whose first day is a Sunday, or a 28-day month whose first day is a Tuesday, etc, we begin to see a pattern like the one to the right. The lines of the calendar are separated into three groups. The first group (yellow) may have a few empty spaces in the front. The second group (blue) has every space filled with a date. And the third group (green) may have a few empty spaces at the back.
The “blue” week#
Printing the “blue” week.#
The middle group (blue), shown to the left, may be the easiest to describe. It prints the dates for a full week, starting with Sunday. It adds 1 for every subsequent day. We do this for as many days as there are in a week (hint: 7). This is an operation easily implemented with a for-loop. Let’s call the loop’s variable \(\texttt{day}\); its range will be \(0\leq\texttt{day}<7\).
The code now becomes straight forward. We need a method that prints seven numbers, starting with the date of the that week’s Sunday. That date is assigned to int sundayDate. The first value of the loop variable is 0 and so sundayDate+0 prints the date of that week’s Sunday. The constant DAYS_IN_A_WEEK is assigned the value 7, to make our code more descriptive.
public static void blueWeek(int sundayDate) {
for ( int day = 0; day < DAYS_IN_A_WEEK; day++ ) {
// print the date: sundayDate + day;
}
}
The “yellow” week#
Printing the “yellow” week.#
Next let’s look at the first line of the calendar, the “yellow” week. After a bit of head-scratching, we observe that the yellow week comprises two groups. A group of empty cells (in orange outlines) and a group of filled cells (pink outline). The total number of cells in these two groups is always 7 (= DAYS_IN_WEEK).
Can we tell how many cells are in each group? In the example here, we see that if the first Sunday of the month is its second day, there are 6 empty cells. Pretty soon, we can see that the number of empty and filled cells for the yellow line depends on the date of the first Sunday of the month. The best formula for computing the number of blank cells is:
Parameterizing \(D=7\) as the variable with the number of days in the week, the expression above becomes:
The corresponding Java statement (line 124 in the code below) uses the constant DAYS_IN_WEEK instead of \(D\), and becomes:
int blanksAtBeginning = ((DAYS_IN_WEEK +1) - firstSunday) % DAYS_IN_WEEK;
Remaining analysis and observations#
The analysis above can be used to determine how to print the “green” week and the “blue” weeks. And the final code will take the following form:
print yellowWeek
print blueWeek // \
... // Do this 3 - 4 times
print blueWeek // /
print greenWeek
To determine the greenWeek method and the number of times we invoke blueWeek, it’s important to realize the importance of the yelloWeek. It tells us how many days are printed on the first row of the calendar. i And so, it tells us how many days are left to be printed. We can use that information to determine how may of blueWeek calls we need and also how many days to print in a greenWeek – if one is needed.
Let’s close with an observation. The problem starts with the requirement to not use conditional statements. To be precise, what we have avoided so far is the explicit use of if-then-else statements. Because our code will contain for-loops, there will be implied conditions: the loops’ terminating conditions. It can also be argued that the modulo operator represents a conditional evaluation.
The KF code#
The best solution, to date, for this problem came from KF, a student who took COMP 170 in Spring 2021. Her code is shown below. I have only made style and commend edits, and added a few class constants. Also, I added one if-statement in spite of the problem forbiting us to use them. The if-statement is not related to the construction of the calendar grid, but makes sure that the arguments we pass are valid. In other words, if we ask for the calendar of a month with 43 days, whose first Sunday is the 11th day in the month, nothing will appear on our screens.
1/**
2 * Class that creates a monthly calendar on a grid with 7 columns. Each column corresponds to a day of
3 * the week, beginning with Sunday.
4 *
5 * Sun Mon Tue Wed Thu Fri Sat
6 * +------+------+------+------+------+------+------+
7 * | | | | | | | |
8 * +------+------+------+------+------+------+------+
9 *
10 * The first row of the grid may include empty cells until the first day of the month is marked. The two extreme
11 * scenarios are when the first day of the month is Sunday or Saturday:
12 *
13 * Sun Mon Tue Wed Thu Fri Sat There are no empty cells
14 * +------+------+------+------+------+------+------+ in the grid for the first
15 * | 1 | 2 | 3 | 4 | 5 | 6 | 7 | week when the first day of
16 * +------+------+------+------+------+------+------+ the month is Sunday
17 *
18 *
19 * Sun Mon Tue Wed Thu Fri Sat There are 6 empty cells
20 * +------+------+------+------+------+------+------+ in the grid for the first
21 * | | | | | | | 1 | week when the first day of
22 * +------+------+------+------+------+------+------+ the month is Saturday
23 *
24 * Once we determine the occupancy of the grid for the first week, the grids for the remaining weeks can be
25 * computed easily.
26 *
27 * The code to compute correctly the contents of the grid for the first week of the month, was developed by KF,
28 * a student in COMP 170, Spring 2021. Leo Irakliotis rewrote the code to introduce class constants and additional
29 * comments.
30 *
31 */
32public class Calendar {
33
34 /** Number of days in shortest month */
35 public static final int SHORTEST_MONTH = 28;
36 /** Number of days in longest month */
37 public static final int LONGEST_MONTH = 31;
38 /** Number of days in a week */
39 public static final int DAYS_IN_WEEK = 7;
40 /** Size of cell padding */
41 public static final int PADDING = 5;
42 /** Grid header */
43 public static final String DAYS_NAMES = " Sun Mon Tue Wed Thu Fri Sat ";
44
45
46 /** Driver method to test the code. */
47 public static void main(String[] args) {
48 // A few test calls
49 printCalendarMonth(6, 30);
50 printCalendarMonth(3, 31);
51 printCalendarMonth(1, 28);
52 printCalendarMonth(3, 30);
53 printCalendarMonth(7, 31);
54 } // method main
55
56
57 /**
58 * Method to produce the calendar, by calling other methods for various parts of the calendar.
59 * @param firstSunday int date of the first Sunday of the month
60 * @param numberOfDays int number of days in the month.
61 */
62 public static void printCalendarMonth (int firstSunday, int numberOfDays){
63 // Valid arguments: Month has 28, 29, 30, or 31 days? First Sunday on 1st-thru-7th day?
64 boolean argumentsAreValid = (numberOfDays >= SHORTEST_MONTH && numberOfDays <= LONGEST_MONTH) &&
65 (firstSunday > 0 && firstSunday <= DAYS_IN_WEEK);
66 if (argumentsAreValid) {
67 printHeader();
68 printBlankDaysBeg(firstSunday);
69 printFirstWeek(firstSunday);
70 printOtherWeeks(firstSunday, numberOfDays);
71 printLastWeek(firstSunday, numberOfDays);
72 printBlankDaysEnd(firstSunday, numberOfDays);
73 separator();
74 }
75 } // method printCalendarMonth
76
77
78 /**
79 * Method to print a calendar header showing the days of the week, followed by a separator line.
80 */
81 public static void printHeader(){
82 System.out.println(DAYS_NAMES);
83 separator();
84 } // method printHeader
85
86
87 /**
88 * Method to print a line with spacing marks to indicate a grid pattern.
89 */
90 public static void separator(){
91 System.out.println("+------".repeat(DAYS_IN_WEEK) + "+");
92 } // method separator
93
94
95 /**
96 * Prints the number of blank days at the beginning of the calendar. To compute how many blank dates (and
97 * therefore blank cells) needed, we obseeve that when the first Sunday falls on the first day of the month
98 * there are 0 blank cells, is Sunday is the 2nd day of the month, there are 6 empty cells and so on. These
99 * observations are summarized below, leading to the expression that computes the number of empty cells.
100 *
101 * if first Sunday there are this expression to generate
102 * falls on this many blank cells the number of empty
103 * day of the month in first week cells required
104 *
105 * 1 0 (8-1) % 7 = 0
106 * 2 6 (8-2) % 7 = 6
107 * 3 5 (8-3) % 7 = 5
108 * 4 4 (8-4) % 7 = 4
109 * 5 3 (8-5) % 7 = 3
110 * 6 2 (8-6) % 7 = 2
111 * 7 1 (8-7) % 7 = 1
112 *
113 * Based on the observation above, we can compute the number of blank cells with the formula:
114 *
115 * blankCells = (8 - firstSunday) % 7
116 *
117 * and since we have a constant daysInWeek to represent number seven, the formula is rewritten:
118 *
119 * blankCells = ( (daysInWeek + 1) - firstSunday ) % daysInWeek
120 *
121 * @param firstSunday int date of first Sunday of the month
122 */
123 public static void printBlankDaysBeg(int firstSunday){
124 int blanksAtBeginning = ((DAYS_IN_WEEK +1) - firstSunday) % DAYS_IN_WEEK;
125 System.out.print("| ".repeat( blanksAtBeginning ) + "|".repeat(Math.min(firstSunday-1, 1)));
126 } // method printBlankDaysBeg
127
128
129 /**
130 * Prints the first week, taking into account the number of blanks at the beginning of the month
131 * @param firstSunday int date of first Sunday of the month
132 */
133 public static void printFirstWeek(int firstSunday){
134 // Print squares with dates in them, continuing on the same line as printBlankDays
135 for (int day = 1; day <= firstSunday - 1 ; day++) {
136 System.out.print(padded(day, PADDING) + " |");
137 }
138 // Only go to a new line if we printed anything in the first loop.
139 // If not, our first week stars on Sunday and it will be taken care of by printOtherWeeks.
140 for (int j = firstSunday; j > 1; j = j - 100) {
141 System.out.println();
142 }
143 } // method printFirstWeek
144
145
146 /**
147 * Prints the more standard weeks
148 *
149 * @param firstSunday int date of the first Sunday of the month
150 * @param numberOfDays int number of days in the month.
151 */
152 public static void printOtherWeeks(int firstSunday, int numberOfDays){
153 // Tells us the number of weeks we'll need, since if 7 doesn't divide evenly into our numberOfDays
154 // we need to round it up to print the final, shorter week
155 int numberOfRows = (int) Math.ceil((numberOfDays - firstSunday + 1) / ((double) DAYS_IN_WEEK));
156 for (int week = 0; week < numberOfRows - 1 ; week++) {
157 // Adds 7 to firstSunday to give us the first day of each week
158 int firstDayOfWeek = firstSunday + (week * DAYS_IN_WEEK);
159 // The last day of the week is whichever is smaller, the total numberOfDays in the month
160 // or the Saturday of that week. This takes care of what happens if the month ends in the middle of a week.
161 int lastDayOfWeek = Math.min((firstSunday + (week * DAYS_IN_WEEK) + (DAYS_IN_WEEK -1)), numberOfDays);
162 // left edge formatting
163 System.out.print("|");
164 // This loop prints the days in padded formatting
165 for (int day = firstDayOfWeek; day <= lastDayOfWeek ; day++) {
166 System.out.print(padded(day, PADDING) + " |");
167 }
168 System.out.println();
169 }
170 } // method printOtherWeeks
171
172
173 /**
174 * Prints the final week of the calendar
175 *
176 * @param firstSunday int date of the first Sunday of the month
177 * @param numberOfDays int number of days in the month.
178 */
179 public static void printLastWeek(int firstSunday, int numberOfDays){
180 int numberOfRows = (int) Math.ceil((numberOfDays - firstSunday + 1) / ((double) DAYS_IN_WEEK));
181 System.out.print("|"); // left edge formatting
182 for (int day = firstSunday + ((numberOfRows - 1) * DAYS_IN_WEEK) ; day <= numberOfDays; day++) {
183 System.out.print(padded(day, PADDING) + " |");
184 }
185 } // method printLastWeek
186
187
188 /**
189 * Prints the blank days at the end of the calendar.
190 * @param firstSunday int date of the first Sunday of the month
191 * @param numberOfDays int number of days in the month.
192 */
193 public static void printBlankDaysEnd(int firstSunday, int numberOfDays){
194 int blanksAtBeginning = (((DAYS_IN_WEEK +1) - firstSunday) % DAYS_IN_WEEK);
195 /*
196 This is just like our other int numberOfRows used earlier, except for the Math.min statement at the end
197 which adds an additional row iff we have any blanks at the beginning. We need it to do this otherwise it will
198 print one extra row at the bottom if the firstSunday is 1.
199 */
200 int numberOfRows2 = (int) Math.ceil((numberOfDays - firstSunday + 1 ) / ((double) DAYS_IN_WEEK)) + Math.min(blanksAtBeginning, 1);
201 /*
202 The repeat statement comes from the fact that the total number of squares in the calendar (numberOfRows2 * 7)
203 minus the total numberOfDays and blanksAtBeginning will tell us how many leftover squares we have at the endt
204 hat need to be printed blank.
205 */
206 System.out.println(" |".repeat((numberOfRows2 * DAYS_IN_WEEK) - (numberOfDays + blanksAtBeginning)));
207 } // method printBlankDaysEnd
208
209
210 /**
211 * Helper method provided by the book assignment
212 * @param n int Number to display in padded string
213 * @param width int width of the string
214 * @return String with padded content
215 */
216 public static String padded (int n, int width){
217 String s = "" + n;
218 for (int i = s.length(); i < width; i++) {
219 s = " " + s;
220 }
221 return s;
222 } // method padded
223
224} // class Calendar