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.

../../_images/Calendar.png

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#

../../_images/sundayDate.png

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#

../../_images/yellow.png

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:

\[\text{number of blank cells} = (8 - \text{first Sunday}) \bmod 7\]

Parameterizing \(D=7\) as the variable with the number of days in the week, the expression above becomes:

\[\text{number of blank cells} = ((D+1) - \text{first Sunday}) \bmod D\]

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