# Printing a calendar for a month based on user input

I have a long piece of code that has many repeating points. I am sure that it doesn't take this amount of code to create a simple calendar, but I do not know how to shorten this piece of code.

# Creating a calendar in proportion to the user defined starting date

n = int(input('Enter number of days: '))#days in a month
s = input('The first day of the week: ')#The date that user wants the month to start
s = s.capitalize()

lis1 = ['Sa','M','T','W','Th','F','Su']
print('{:>3}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}'.format('Sa','M','T','W','Th','F','Su'))

for i in range(1,n+1):#loop to generate the dates for a month
if s==lis1[0] and i ==1:#this is to determine how much should the code indent to align to traditional calendar formatting
print('{:>3}'.format(i),end =' ')
elif s==lis1[1] and i ==1:
print(' '*4+'{:>3}'.format(i),end =' ')
elif s==lis1[2] and i ==1:
print(' '*8+'{:>3}'.format(i),end =' ')
elif s==lis1[3] and i ==1:
print(' '*12+'{:>3}'.format(i),end =' ')
elif s==lis1[4] and i ==1:
print(' '*16+'{:>3}'.format(i),end =' ')
elif s==lis1[5] and i ==1:
print(' '*20+'{:>3}'.format(i),end =' ')
elif s==lis1[6] and i ==1:
print(' '*24+'{:>3}'.format(i),end =' ')
else:
print('{:>3}'.format(i),end =' ')#after the indent this is to print the remaining dates

if s==lis1[0] and i%7==0:#this is to print a new line in proportion the choosen starting date so as to stay in check with the traditional calendar format
print()
elif s==lis1[1] and i%7==6:
print()
elif s==lis1[2] and i%7==5:
print()
elif s==lis1[3] and i%7==4:
print()
elif s==lis1[4] and i%7==3:
print()
elif s==lis1[5] and i%7==2:
print()
elif s==lis1[6] and i%7==1:
print()


This is an example of the calendar I am outputting.

Please guide me on shortening code that is related to loops if possible.

• You may be interested in Pythons calendar module. For example a simple text calendar for May 2010 with Sunday as the start of the week could be: import calendar; calendar.setfirstweekday(6); print(calendar.month(2010, 5)) Jul 5 '17 at 14:57

First improvement

don't repeat yourself, the dry principle

You have many elif statements that can be easily rewritten using a dictionary.

My final program has none at all. I rewrote your lst (also badly named) into a dictionary like this:

weekdays = {'Su':0,'M':1,'T':2,'W':3,'Th':4,'F':5,'Sa':6}


Now weekdays is used on 2 different locations to replace the long elif structure.

First elif block. Notice they all increment by 4. So I can use the value of the dictionary like this: weekdays[first_weekday]*4

Second elif block. Here you can use the same dictionary. Notice how weekdays[first_weekday] + current_day becomes a multitude of 7.

Second Improvement

You should rename the variables in your program to understand what they do. For example n should be renamed to number_of_days

A good way to follow are the PEP8 guidelines

Final Program

As you can see, this program not only is shorter but also far more readable

# I omitted comments because the names speak for themselves
number_of_days = int(input('Enter number of days in month: '))
first_weekday = input('First weekday of the month: ')

# Using a dictionary over a list is the biggest improvement in my opinion
weekdays = {'Su': 0, 'M': 1, 'T': 2, 'W': 3, 'Th': 4, 'F': 5, 'Sa': 6}
print('{:>3}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}'
.format('Su', 'M', 'T', 'W', 'Th', 'F', 'Sa'))

# First elif block
print (weekdays[first_weekday]*4*' ' + '{:>3}'.format(1), end=' ')
for current_day in range(1, number_of_days+1):
# Second elif block
if (weekdays[first_weekday] + current_day) % 7 == 0:
print ()
print ('{:>3}'.format(current_day ), end=' ')

• I think there is a bug in your program, just try 20 and 'Su' as input. Jul 5 '17 at 14:35
• In your days_of_week, Saturday and Sunday are swapped Jul 5 '17 at 14:47
• Thanks man for the thoughtful write out! I was wondering if the same can be achieved using just using nested loop and not having to use a dictionary? Jul 5 '17 at 22:16

The first important rule of thumb is always: If possible use libraries that deal with your problem. Probably there is a calendar library, or you can use whitespace CSV printing to print column wise oriented text.

Under the assumption that you want to implement it without libraries there are some improvements. Some of them are very general, some are python specific.

1. Formatting (python specific)

1. Separate functionality

It is always a good idea to have separate functions for separate operations like reading input, calculating, outputting results...

1. Less global state

If you use functions write them in a way, that you don't need global variables.

1. Use list comprehensions (python specific)

Just like dictionaries in the answer of Ludisposed list comprehensions are bread and butter in python. It helps to get familiar with them early.

1. Self documenting code + better variable names

There is a simple rule: avoid comments!

If you have a line like the following:

n = int(input('Enter number of days in a month: ))#days in a month


The user won't know, if it is the number of days in a week or in a month, because the user should not be forced to look into your source code.

n = int(input('Enter number of days in a month:))


You don't need comments in the source code and the user knows what to type. One could argue that this should be even extended:

n_days_in_month = int(input('Enter number of days in a month:))


Then you definitely won't need a comment even lateron.

Taking everything into account you can rewrite it like this:

days = ['Su','M','T','W','Th','F','Sa']

def get_user_input():
while True:
message = 'Enter number of days in a month (has to be between 1 and 31): '
n_days_in_month = int(input(message))
if 1 <= n_days_in_month <= 31:
break
while True:
message = "The first day of the week (has to be one of the following {}): ".format(days)
first_day = input(message)
if first_day in days:
break
return n_days_in_month, first_day

def reshape(n_days_in_month, first_day):
i = days.index(first_day)
n_weeks = (n_days_in_month // 7) + 1 + i
rows_of_days = []
rows_of_days.append(['' for _ in range(i)] + list(range(1, 7 - i + 1)))
last = 7 - i + 1
for week in range(1, n_weeks):
rows_of_days.append(range(last, min(last + 7, n_days_in_month + 1)))
last += 7
return rows_of_days

def output_calendar(rows_of_days):
print(*['{:>3}'.format(x) for x in days])
for row in rows_of_days:
print(*['{:>3}'.format(x) for x in row])

n_days_in_month, first_day = get_user_input()

output_calendar(reshape(n_days_in_month, first_day))

• Thx man! Though I have not learnt how to use function in python yet, but I am sure this will serve as an great example. Jul 5 '17 at 22:43