I have made a Python program to find the day of the week of an inputted date. I did not want to use the time
or datetime
module, like in this answer, as I found that a bit too easy. (I like to challenge myself)
I want to improve on appropriately putting certain tasks in functions, so my program is easier to read and follow. I would like to know if the logic in my program makes sense, and if there is anything that could have been done easier. If things could have been in a more "Pythonic" way, that would be nice to know too.
Here is the code -
month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
day_names = ["Friday", "Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday"]
SYEAR = 2016
SMONTH = 1
SDAY = 1
def is_leap_year(year):
if year % 4 == 0:
if year % 100 == 0:
if year % 400 == 0:
return True
else:
return False
else:
return True
return False
def calc_year(year):
days = 0
tyear = SYEAR
while not tyear == year:
if tyear < year:
if is_leap_year(tyear):
days += 366
else:
days += 365
tyear += 1
if tyear > year:
if is_leap_year(tyear - 1):
days -= 366
else:
days -= 365
tyear -= 1
return days
def calc_month(month, year):
days = 0
tmonth = SMONTH
while not tmonth == month:
if tmonth == 2 and is_leap_year(year):
days += 1
days += month_days[tmonth - 1]
tmonth += 1
return days
def calc_day(day):
days = 0
tday = SDAY
while not tday == day:
tday += 1
days += 1
return days
def find_day(date):
total = calc_month(date[0], date[2]) + calc_day(date[1]) + calc_year(date[2])
if total < 0:
return day_names[total % -7]
else:
return day_names[total % 7]
def main():
date = input("Enter a day like so <MM, DD, YYYY>: ").split()
month = int(date[0])
day = int(date[1])
year = int(date[2])
if month > 12 or month <= 0:
print("Invalid month")
elif day > month_days[month - 1] or day <= 0:
if not (day == 29 and month == 2):
print("Invalid day")
else:
print(find_day((month, day, year)))
if __name__ == "__main__":
main()
I purposely start with the date 01 01 2016
, because it is a leap year and (so far) has not had an effect on the outcome of the program.
2 Answers 2
Your code looks quite good. You mostly stick to PEP8 though you might want to implement a distance of two empty lines in-between functions on module-level.
Regarding code optimization you may want to make month_days
and day_names
tuples since they do not change and thus may be implemented as invariant constants. While doing this you can also capitalize their names to indicate that they are global constants as you did with the other module-level variables:
MONTH_DAYS = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
DAY_NAMES = ("Friday", "Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday")
Furthermore, you can make is_leap_year
a one-liner, since the if-else blocks and zero-comparisons are a bit verbose for my taste of "pythonicness":
def is_leap_year(year):
return not year % 4 and (year % 100 or not year % 400)
Also I find it a bit intricately, that you pass a tuple date
to find_day
and then access its members via indexes.
I suggest changing that to:
def find_day(month, day, year):
total = calc_month(month, year) + calc_day(day) + calc_year(year)
if total < 0:
return day_names[total % -7]
else:
return day_names[total % 7]
def main():
date = input("Enter a day like so <MM, DD, YYYY>: ").split()
month = int(date[0])
day = int(date[1])
year = int(date[2])
if month > 12 or month <= 0:
print("Invalid month")
elif day > month_days[month - 1] or day <= 0:
if not (day == 29 and month == 2):
print("Invalid day")
else:
print(find_day(month, day, year))
However, if you really want to handle date
as such a tuple for later re-use, I recommend that you use tuple unpacking in find_day
instead of index accessing for better readability:
def find_day(date):
month, day, year = date
total = calc_month(month, year) + calc_day(day) + calc_year(year)
if total < 0:
return day_names[total % -7]
else:
return day_names[total % 7]
-
\$\begingroup\$ Instead of creating an
is_leap_year
function by yourself, you could also recommend thecalendar
module. \$\endgroup\$Grajdeanu Alex– Grajdeanu Alex2017年01月11日 09:22:56 +00:00Commented Jan 11, 2017 at 9:22 -
1\$\begingroup\$ @Dex'ter He does want to re-invent the wheel and explicitely stated that he does not want to use any library. \$\endgroup\$Richard Neumann– Richard Neumann2017年01月11日 09:25:47 +00:00Commented Jan 11, 2017 at 9:25
-
\$\begingroup\$ Oh, I didn't read through everything he wrote :) \$\endgroup\$Grajdeanu Alex– Grajdeanu Alex2017年01月11日 09:26:37 +00:00Commented Jan 11, 2017 at 9:26
If you are already reinventing the wheel regarding datetime
, I would take their interface as a bit of an example. I would propose to write a Date
class, with the ability to parse your input format.
For determining the weekday, I would use a similar strategy as datetime
uses, namely convert the dates into ordinals (dates since 0001年01月01日) and then use the fact that 0001年01月01日 was a Monday, conveniently, which means you can directly use that as index (mod 7).
import re
from itertools import accumulate
class Date(object):
format = re.compile('(\d{1,2}),? (\d{1,2}),? (\d{4})')
month_days = (None, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
days_before = (None, 0) + tuple(accumulate(month_days[1:-1]))
day_names = ("Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday", "Sunday")
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
assert year >= 1, "Invalid year"
assert 1 <= month <= 12, "Invalid month"
max_days = self.month_days[month] + (month == 2 and self.is_leap())
assert 1 <= day <= max_days, "Invalid day"
def __repr__(self):
return "Date({self.year}, {self.month}, {self.day})".format(self=self)
def is_leap(self):
year = self.year
return bool(not year % 4 and (year % 100 or not year % 400))
def _days_before_year(self):
"year -> number of days before January 1st of year."
y = self.year - 1
return y * 365 + y // 4 - y // 100 + y // 400
def _days_before_month(self):
"year, month -> number of days in year preceeding first day of month."
return self.days_before[self.month] + (self.month > 2 and self.is_leap())
def to_ordinal(self):
"year, month, day -> ordinal, considering 01-Jan-0001 as day 0."
return (self._days_before_year() +
self._days_before_month() +
self.day)
@property
def weekday(self):
# 0001年01月01日 was a Monday
return self.day_names[self.to_ordinal() - 1 % 7]
@classmethod
def strptime(cls, date_str):
match = cls.format.match(date_str)
if match is not None:
day, month, year = map(int, match.groups())
else:
raise ValueError("Could not match {}".format(date_str))
return cls(year, month, day)
def main():
date = Date.strptime(input("Enter a day like so <MM DD YYYY>: "))
print(date.weekday)
if __name__ == "__main__":
main()
I made the hard-coded day names etc all tuples, because they don't change.
Explore related questions
See similar questions with these tags.