CS303E Project 1

Instructor: Dr. Bill Young
Due Date: Friday, February 28, 2025 at 11:59pm

A Calendar Utility

Most of us have calendar apps on our smartphones and laptops. It's pretty clever software that can give you the days for any month and year. For example, on Linux and MacOS systems is an application called cal that can give you a calendar for the current month, a calendar for any given year, or a calendar for a specific month within a given year:
> cal 1 2024
    January 2025      
Su Mo Tu We Th Fr Sa  
          1  2  3  4  
 5  6  7  8  9 10 11  
12 13 14 15 16 17 18  
19 20 21 22 23 24 25  
26 27 28 29 30 31     

> cal 9 1752
   September 1752     
Su Mo Tu We Th Fr Sa  
       1  2 14 15 16  
17 18 19 20 21 22 23  
24 25 26 27 28 29 30  

I'll bet you didn't realize there was a month with only 19 days! (That's when the Julian calendar was replaced by the Gregorian calendar in Britain and its colonies, including the U.S.)

There's even a built in calendar app within Python:

>>> import calendar
>>> print( calendar.month(2025, 2) )
   February 2025
Mo Tu We Th Fr Sa Su
                1  2
 3  4  5  6  7  8  9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28

>>> 
In this project, you'll be building your own calendar app, but one much simpler than those. You app need only work for the months in the year 2025. The user of your application will enter a positive integer in the range [1..12], indicating the month. In response, you'll print the calendar for that month in 2025. This functionality is embedded within a loop so that the user can ask for calendars for a series of months. Exit the loop when the user enters 0.

Here are some samples:

> python Project1.py

Welcome to our calendar utility!
Print the calendar for any month in 2025.

Enter a month [1..12] or 0 to stop: 0
Thanks for visiting! Goodbye!

> python Project1.py

Welcome to our calendar utility!
Print the calendar for any month in 2025.

Enter a month [1..12] or 0 to stop: -12
Month must be a number between 1 and 12, or 0 to stop. Try again.

Enter a month [1..12] or 0 to stop: 17
Month must be a number between 1 and 12, or 0 to stop. Try again.

Enter a month [1..12] or 0 to stop: abd
Month must be a number between 1 and 12, or 0 to stop. Try again.

Enter a month [1..12] or 0 to stop: 1

   January, 2025    
Su Mo Tu We Th Fr Sa
          1  2  3  4 
 5  6  7  8  9 10 11 
12 13 14 15 16 17 18 
19 20 21 22 23 24 25 
26 27 28 29 30 31 

Enter a month [1..12] or 0 to stop: 2

   February, 2025   
Su Mo Tu We Th Fr Sa
                   1 
 2  3  4  5  6  7  8 
 9 10 11 12 13 14 15 
16 17 18 19 20 21 22 
23 24 25 26 27 28 

Enter a month [1..12] or 0 to stop: 3

    March, 2025     
Su Mo Tu We Th Fr Sa
                   1 
 2  3  4  5  6  7  8 
 9 10 11 12 13 14 15 
16 17 18 19 20 21 22 
23 24 25 26 27 28 29 
30 31 

Enter a month [1..12] or 0 to stop: 12

   December, 2025   
Su Mo Tu We Th Fr Sa
    1  2  3  4  5  6 
 7  8  9 10 11 12 13 
14 15 16 17 18 19 20 
21 22 23 24 25 26 27 
28 29 30 31 

Enter a month [1..12] or 0 to stop: 0
Thanks for visiting! Goodbye!

>
That's it. Note that one way to accomplish this would be to write separate code for each month and just print it out, as you did with your initials in HW1. That is not allowed. You have to compute and print each calendar using the algorithm outlined below.

Here are some hints and requirements:

  1. You must validate the user input. Don't assume that the value entered is a number or an integer in the range [1..12]. (Hint: check first that the string entered satisfies s.isdigit(), and then that it's in the right range. If it's not, ask the user to re-enter. Follow the examples above. Allow any number of tries.
  2. You'll need to know how many days are in each month in 2025. You can get that by looking at any calendar. Note that 2025 is not a leap year.
  3. One of the hardest things about a general calendar app is figuring out on which day of the week the month begins. There is actually a general algorithm for that, but it's a bit tricky. You should just build in the information about the start day for each month in 2024. Just look it up in the calendar for 2024 and code that into your program.
  4. Print one blank line above and at least one blank line below the calendar.
  5. The title line with month and year does not have to be exactly centered. But you are permitted to use the string.center() method if you like to make it centered; that's what I did. Use print(your_string.center(n)) to center your string in a field of n.
  6. You must use functions in your code; see below.
Here's the algorithm for printing a single month calendar.
  1. First, print a blank line, the line with month and year, and the line with days of the week, as illustrated above.
  2. For the first line of numbers, print the correct number of spaces (depending on the first day of the month) to take you to the appropriate start column. It's 3 spaces for each empty column. You get the start column by determining on which day of the week that month starts. (Hint: if s is a string, s * n repeats the string n times. So, " " * (3 * k) will give you a string of 3k spaces.)
  3. Suppose M is the number of days in the month specified. You'll print the numbers [1..M], each in an integer field of width 2 followed by a single space. You should do this with a single for loop. Be sure to stay on the same line until you reach column 7, and then go to a new line. (See step 4.)
  4. While doing step 3, keep track of what column you're in. When the column reaches 7, print a newline and reset the column counter to 0. You don't have to pad the last line with blanks.
  5. When you're done print a couple of newlines. If you only print one, it may take you to the next line, but not show an empty line. It's OK to have two blank lines sometimes, but you must have at least one.
This assignment requires you to use formatting, loops, conditionals, functions, keep track of the columns, etc. Do not use constructs (like lists) that are not covered in slidesets 1-6. Note that slideset 6 covers functions and you must have at least the functions mentioned below. Below is a template of the code. For each function, replace pass with the body of the function.

# Some symbolic constants it would be useful to define:
SUN = 0
MON = 1
...
SAT = 6

JAN = 1
FEB = 2
...
DEC = 12

def monthName( n ):
    """Given the number of a month, return its name.  Assume that n is in
    range [1..12].  Example, if n == JAN (i.e., 1), return 'January'.

    """
    pass

def firstDayOfMonth( n ): 
    """Given a month in 2025, on which day (number) does it begin.
    Example, if n == JAN, return WED (i.e, 3), since January, 2025
    begins on Wednesday (day 3).  It's best to number the days from
    [0..6].  This helps because, if the month begins on FRI = 5,
    you'll need to start the first line of numbers with 5 * 3 blanks.

    """
    pass

def daysInMonth2025( n ):
   """Given a month in 2025, how many days are in it?  For example, given
    n == JAN (i.e., 1), return 31 since there are 31 days in January of
    2025.  You can obtain this information by looking at a calendar
    for 2025, or just use your knowledge of how many days are in each
    month.  2025 is not a leap year.

   """
   pass

def printCalendarMonth( n ):
    """Print the calendar for the month n for 2025 where n is in range
    [1..12].  For example, printCalendarMonth(1) should display:

       January, 2025    
    Su Mo Tu We Th Fr Sa
              1  2  3  4 
     5  6  7  8  9 10 11 
    12 13 14 15 16 17 18 
    19 20 21 22 23 24 25 
    26 27 28 29 30 31 

    """
    pass

def main():
    """Run a loop accepting from the user month numbers.  Print the
    calendar for each month requested from 2025.  Stop when the user
    enters 0.  Validate user input and allow multiple tries if the
    user enters invalid inputs.

    """
    pass

BTW: you don't have to validate the input for any of the functions. That is done inside main(); you should never call any of the other functions except on data that is known to be legal.

Turning in the Assignment:

The program should be in a file named Project1.py. Submit the file via Canvas before the deadline shown at the top of this page. Submit it to the assignment project1 under the assignments sections by uploading your Python file. Make sure that you follow good coding style and comment your code as appropriate.

Your file must compile and run before submission. It must also contain a header with the following format:

# File: Project1.py
# Student: 
# UT EID:
# Course Name: CS303E
# 
# Date Created:
# Description of Program: 

Programming Tips:

Program incrementally: Even more than earlier assignments, it would be crazy to try this all at once. Write and test each individual function before proceeding. I would suggest writing and testing the functions in the order given above. You can test individual functions by importing the module and running the function. For example:
>>> from Project1 import *
>>> monthName( 7 )
'July'
>>> firstDayOfMonth( 10 )
3
>>> daysInMonth2025( 2 )
28
>>> printCalendarMonth( 8 )

    August, 2025    
Su Mo Tu We Th Fr Sa
                1  2 
 3  4  5  6  7  8  9 
10 11 12 13 14 15 16 
17 18 19 20 21 22 23 
24 25 26 27 28 29 30 
31 

>>> 
Using Symbolic Constants: In this program, we're "encoding" months as numbers [1..12] and days of the week as numbers [0..6]. That might not immediately be obvious to someone reading your code. This is a great example when symbolic constants are useful. By convention, the names of symbolic constants should be in all capital letters.

For example, instead of using the numbers [1..12] to refer to the months of the year, we might define the number associated with each month as a symbolic constant. These are defined outside of any function, preferably near the top of your file.

JAN = 1
FEB = 2
...
DEC = 12
Then, any reference to 2, say, that refers to February will instead be FEB in your code. This makes your program much more readible and maintainable. For example, to see whether the month entered is legal, you might have code containing the condition:
   if JAN <= month <= DEC:
      ...
And when computing the first day of a month in 2025 you might have have a clause such as:
   elif month == OCT:
      return WED
Now, isn't that more readible than:
   elif month == 10:
      return 3
But notice that the Python assignment
JAN = 1
is really just defining a variable, not a constant. There's nothing to prevent you from assigning JAN a different value within your program. (But you'd be nuts to do that.) Treat it as a symbolic constant even though there's nothing in Python (unlike some other programming languages) that really guarantees that it remains constant.

Another great use is to give a symbolic name to a numeric constant that appears frequently in your code, but which never changes within your program:

INTEREST_RATE = 8.5
In addition to making your code easier to read, it makes it simple to update your program if the value of this constant changes. Otherwise, you'd have to comb through your entire program to find every occurrence of 8.5 (and make sure that that is actually referencing the interest rate).

BTW: Many students get confused when defining symbolic constants. Remember JAN and SUN are just the names of variables that contain numbers; they're not strings. So, you wouldn't say:

    if "JAN" <= month <= "DEC":
That would give you a type error; you'd be comparing numbers and strings.