1

I am currently attempting to solve a problem which requires a user to input a month and year where the program will produce an extract of a calendar for that specific month. The format in which the calendar prints is not matching the sample output: Sample Output:

(edit: the same edits do not show how they actually appear, but the sample output prints the dates directly underneath the dates, but the actual output does not, the dates are separated by a single space not appearing directly under the dates)

May 
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 29 30 31

Actual Output:

May 2020
Mo Tu We Th Fr Sa Su
           1 2
 3 4 5 6 7 8 9
10111213141516
17181920212223
24252627282930
31            

Heres the program:

import math

def day_of_week(day, month, year):
    if month < 3:
        month += 12
        year -= 1
    h = (day + math.floor(13*(month+1)/5) + year + math.floor(year/4) - math.floor(year/100) + math.floor(year/400)) % 7
    return (h + 6) % 7

def is_leap(year):
    return (year % 4 == 0 and year % 100 != 0) or year % 400 == 0

def month_num(month_name):
    months = {'january': 1, 'february': 2, 'march': 3, 'april': 4, 'may': 5, 'june': 6, 'july': 7, 'august': 8, 'september': 9, 'october': 10, 'november': 11, 'december': 12}
    return months[month_name.lower()]

def num_days_in(month_num, year):
    if month_num in [1, 3, 5, 7, 8, 10, 12]:
        return 31
    elif month_num == 2:
        return 29 if is_leap(year) else 28
    else:
        return 30

def num_weeks(month_num, year):
    first_day = day_of_week(1, month_num, year)
    num_days = num_days_in(month_num, year)
    return math.ceil((first_day + num_days) / 7)

def week(week_num, start_day, days_in_month):
    week_str = ''
    for i in range(7):
        day = (week_num - 1) * 7 + i + 1 - start_day
        if day > 0 and day <= days_in_month:
            week_str += '{:>2}'.format(day)  # right justify single digit dates
        else:
            week_str += '  '
    return week_str

def main():
    month_name = input('Enter month:\n')
    year = int(input('Enter year:\n'))
    month_num1 = month_num(month_name)
    num_days = num_days_in(month_name, year)
    num_weeks1 = num_weeks(month_num1, year)
    print('{} {}'.format(month_name.title(), year))
    print('Mo Tu We Th Fr Sa Su')
    for week_num in range(1, num_weeks1+1):
        start_day = day_of_week(week_num * 7 - 6, month_num1, year)
        print(week(week_num, start_day, num_days_in(month_num1, year)))
                   

if __name__ == '__main__':
    main()

I attempted changing the print statement at the end of the main function, but to no avail. Any solutions? (please)

Michael Butscher
  • 10,028
  • 4
  • 24
  • 25
  • It just needs an additional space after each day number (like there are spaces after the day of week abbreviations). You could handle the last column differently but the additional space at the end shouldn't matter. – Michael Butscher Apr 13 '23 at 22:05

1 Answers1

0

I tried out your code and noticed the tweaking for spaces as noted in the good comments above. Also, in testing out your sample month/year, the other thing I noticed that the days of the week aligned with a Sunday - Saturday column arrangement and not a Monday - Sunday arrangement. With that following is a refactored version of your code with some small tweaks.

import math

def day_of_week(day, month, year):
    if month < 3:
        month += 12
        year -= 1
    h = (day + math.floor(13*(month+1)/5) + year + math.floor(year/4) - math.floor(year/100) + math.floor(year/400)) % 7
    return (h + 6) % 7

def is_leap(year):
    return (year % 4 == 0 and year % 100 != 0) or year % 400 == 0

def month_num(month_name):
    months = {'january': 1, 'february': 2, 'march': 3, 'april': 4, 'may': 5, 'june': 6, 'july': 7, 'august': 8, 'september': 9, 'october': 10, 'november': 11, 'december': 12}
    return months[month_name.lower()]

def num_days_in(month_num, year):
    if month_num in [1, 3, 5, 7, 8, 10, 12]:
        return 31
    elif month_num == 2:
        return 29 if is_leap(year) else 28
    else:
        return 30

def num_weeks(month_num, year):
    first_day = day_of_week(1, month_num, year)
    num_days = num_days_in(month_num, year)
    return math.ceil((first_day + num_days) / 7)

def week(week_num, start_day, days_in_month):
    week_str = ''
    for i in range(7):
        day = (week_num - 1) * 7 + i + 1 - start_day
        if day > 0 and day <= days_in_month:
            week_str += '{:>2}'.format(day) + ' '   # Right justify single digit dates
        else:
            week_str += '   '                       # Needed one extra space for padding
    return week_str

def main():
    month_name = input('Enter month:\n')
    year = int(input('Enter year:\n'))
    month_num1 = month_num(month_name)
    num_days = num_days_in(month_name, year)
    num_weeks1 = num_weeks(month_num1, year)
    print('{} {}'.format(month_name.title(), year))
    print('Su Mo Tu We Th Fr Sa')                   # Start the calendar day column with Sunday
    for week_num in range(1, num_weeks1+1):
        start_day = day_of_week(week_num * 7 - 6, month_num1, year)
        print(week(week_num, start_day, num_days_in(month_num1, year)))
                   

if __name__ == '__main__':
    main()

As noted, following were the tweaks made to the code.

  • An additional padding space was added for a week that did not have a full seven dates.
  • The day column headings were revised to denote a "Sunday - Saturday" week which is usual and customary.

With those tweaks following was the output for your test of May, 2020.

@Vera:~/Python_Programs/Calendar$ python3 NewCal.py 
Enter month:
may
Enter year:
2020
May 2020
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                   

I tested out a few other month/year combinations and they agreed with a printed calendar.

Your code was almost where you wanted it to be. Try out this refactored code and see if it meets the spirit of your project.

NoDakker
  • 3,390
  • 1
  • 10
  • 11