I am a novice programmer and am currently learning Python programming by myself through a book. I stumbled upon a question in a chapter and it took me 2 days to figure out a solution for it. Although, it may not be an efficient code for the problem, I would be grateful if you guys could review it. Just to let everyone know of my Knowledge level on programming, I just have basic knowledge of functions, sequences and decision structure.
Write a program to check if a date is valid or not? The date should be in the mm/dd/yyyy format.
Pseudocode:
- Define 3 functions to check the validity of month,day and year, if valid return True for each function.
- input a string date separated by a "/" and assign it to a date variable.
- split the date variable to 3 separate variables.
- check if the 3 variables are True or not.
- Print if the date is valid or not.
I didn't take into account leap years where February has 29 days and also assumed the year to start at 1 A.D. to current years.
def monthcheck(month):
if month > 0 and month <= 12: ## If month is between 1 and 12, return True.
return True
else:
return False
def daycheck(month,day):
monthlist1 = [1,3,5,7,8,10,12] ## monthlist for months with 31 days.
monthlist2 = [4,6,9,11] ## monthlist for months with 30 days.
monthlist3 = 2 ## month with month with 28 days.
for mon in monthlist1: ## iterate through monthlist1.
if month == mon: ## Check if the parameter month equals to any month with 31 days.
if day >=1 and day <= 31: ## If the parameter day is between 1 and 31, return True.
return True
else:
return False
for mon in monthlist2: ## iterate through the monthlist with 30 days.
if month == mon: ## check if the parameter month equals to any month with 30 days.
if day >= 1 and day <= 30: ## if the parameter day is between 1 and 30,return True.
return True
else:
return False
if month == monthlist3: ## check if parameter month equals month with 28 days.
if day >=1 and day <= 28: ## if the parameter day is between 1 and 28,return True.
return True
else:
return False
def yearcheck(year):
if len(year) >= 1 and len(year) <= 4: ## Check if year has between 1 to 4 numbers and return True.
return True
else:
return False
def main():
date = str(input("Enter the date in mm/dd/yyyy format: ")) ## Input date in the given format.
month,day,year = date.split("/") ## split the date into 3 separate variables.
monthvalidity = monthcheck(int(month))
dayvalidity = daycheck(int(month),int(day))
yearvalidity = yearcheck(year)
if monthvalidity == True and dayvalidity == True and yearvalidity == True: ## check if all 3 variables are valid or True
print("The date {0} is valid.".format(date))
else:
print("The date {0} is invalid.".format(date))
main()
-
17\$\begingroup\$ Welcome to hell. Validating a date is one of the hardest (programming) problems ever. For example you are not checking if the year is n the range of dates when the gregorian calendar is valid. So you may return true for a date which does not exist. Read about the falsehoods programmers believe about time here and here. \$\endgroup\$allo– allo2018年07月31日 09:28:03 +00:00Commented Jul 31, 2018 at 9:28
-
3\$\begingroup\$ For a simple example from the lists: You are not handling leap years at all, so you will reject valid dates. When you start to, note that there are special rules for years divisible by 4 not being leap years, i.e. being divisible by 100. There are more exceptions, which do not fit into a comment. Date calculations are very very hard. \$\endgroup\$allo– allo2018年07月31日 09:32:59 +00:00Commented Jul 31, 2018 at 9:32
-
1\$\begingroup\$ @allo you're right, but 1. in some cases, simple checks like these will suffice (i.e. if you have some degree of control over what types of dates serve as input), and 2. this is an exercise from a book, so it's quite unfair to expect OP to handle every possible edge case when this is only intended as a programming problem to get better at Python. \$\endgroup\$Daniel– Daniel2018年07月31日 12:25:56 +00:00Commented Jul 31, 2018 at 12:25
-
2\$\begingroup\$ @Daniel: I don't think you should or can handle every edge case in your software. This would be ... hell. But at least some leap year handling would be nice to have and with the 4/100/400 rule a nice exercise for a real-life fizzbuzz ;-). The rest of the article is more about having heard of these problems the next time you're implementing something date related and think about if you need to consider them or not. \$\endgroup\$allo– allo2018年07月31日 13:54:51 +00:00Commented Jul 31, 2018 at 13:54
-
4\$\begingroup\$ Btw: ISO 8601 is now 30 years old. \$\endgroup\$Martin Schröder– Martin Schröder2018年07月31日 13:57:35 +00:00Commented Jul 31, 2018 at 13:57
8 Answers 8
Good work! While there is certainly room for improvement, it is good that you stuck it out and discovered a solution on your own.
def monthcheck(month): if month > 0 and month <= 12: ## If month is between 1 and 12, return True. return True else: return False
Can be more succinctly written as
def monthcheck(month): return 0 < month <= 12
While writing comments for practically every line of code can help you learn, and is a good way to keep track of where you are when working through pseudocode, don't just write what the code already told you.
PEP 8 - the style guide for Python - recommends using
snake_case
for naming functions, somonthcheck
would becomemonth_check
.What happens if I input "test" when asked for a date? The program crashes. It's a good idea to validate that any input from the user is in the format you expect.
The program asks for input in the format
mm/dd/yyyy
but will acceptm/d/y
, which probably isn't desired.Don't compare explicitly to
True
/False
if monthvalidity == True and dayvalidity == True and yearvalidity == True:
Can be more easily read if it is written as
if monthvalidity and dayvalidity and yearvalidity
daycheck
has several locations in which it could be improved. You could use a dictionary (as heather does), but if you haven't learned about those yet, you can still improve the algorithm.- Python's
in
operator can also check if a value is in a list, so instead of looping overmonthlist1
andmonthlist2
, you can just checkif month in monthlist1
. - Since
monthlist1
doesn't really describe what is in the variable, and I am having difficulty coming up with a better name, I would just inline the list withif month in [1, 3, 5, 7, 8, 10, 12]
and add a clarifying comment.
- Python's
Don't be afraid to use natural sounding variable names. I believe
monthcheck
is better namedis_month_valid
.What about negative numbers?
1/1/-111
is apparently a valid date. This might be desired, but probably deserves a comment.Converting numbers to strings to determine the size is generally not a good idea, and can get you into trouble in more complex cases with floats / doubles. I would prefer the check
1 <= year < 10000
.There is no need to have
str()
around an input function,input
returns a string already.
Python, as a "batteries included" language, has a feature that can do this for us! In production code, this is what you should use.
from datetime import datetime
def main():
user_input = input('Enter the date in mm/dd/yyyy format: ')
try:
datetime.strptime(user_input, '%m/%d/%Y')
print('The date {} is valid.'.format(user_input))
except ValueError:
print('The date {} is invalid'.format(user_input))
if __name__ == '__main__':
main()
This was rather long, but don't worry! It is completely normal to get a lot of feedback when you are starting out. The more you code, the more natural it becomes.
-
1\$\begingroup\$ A few suggestions: you can make your code a little less verbose by using
from datetime import datetime
and if you have Python 3.6+, can useprint(f"The date {date:%m/%d/%Y} is valid.")
. Finally, just a small note -date
is a reserved word in datetime, I often use 'today' or 'date_val' instead of the word 'date'. Of course, if you don't do afrom datetime import datetime, date
then it shouldn't matter, but it helps to avoid naming clashes so you're working on the expected variable and not the library function accidently. \$\endgroup\$C. Harley– C. Harley2018年07月31日 04:49:39 +00:00Commented Jul 31, 2018 at 4:49 -
\$\begingroup\$ Thanks! I don't with Python much so it looks like I have a lot to learn too. \$\endgroup\$Gerrit0– Gerrit02018年07月31日 11:58:06 +00:00Commented Jul 31, 2018 at 11:58
-
\$\begingroup\$ Minor nit. Instead of
is_month_valid
, I think I'd usemonth_is_valid
as it reads better:if month_is_valid and ...
. \$\endgroup\$rcollyer– rcollyer2018年07月31日 14:11:22 +00:00Commented Jul 31, 2018 at 14:11 -
\$\begingroup\$ Thank you @Gerrit0 for your valuable feedback. it really motivated me to learn more and keep going. I haven't really learned about dictionaries and i haven't really soaked up exception handling. The next time I post any code, I will try to make it better. Thank you again for setting aside your valuable time to review my code. \$\endgroup\$mathlover92– mathlover922018年07月31日 18:10:07 +00:00Commented Jul 31, 2018 at 18:10
-
1\$\begingroup\$ @rcollyer I'm not a Python programmer, but it is very normal to name boolean variables with a yes/no verb prepended:
if is_valid_month
,if is_leap_year
,if has_time
, etc. You read it in your head as "if it is a valid month ..." \$\endgroup\$CJ Dennis– CJ Dennis2018年08月01日 13:57:35 +00:00Commented Aug 1, 2018 at 13:57
For the most part, looks pretty good to me!
Format
You didn't use underscores to separate the words in a lot of variable names, which kinda bothered me. Snake_case is recommended by PEP8, so I changed that.
You also added extra indentation in your first function (whether that was just the copy-paste of your code into the question messed up or not, I dunno) so I removed it.
Algorithm
Your day_check
was the thing I changed the most. I basically just created a dictionary matching the month numbers with the day numbers and checked if the day inputted in the function was between 0 and the max defined by the dictionary. I felt that significantly shortened and clarified the function.
The other change I made was to change your if
statement in main to if month_validity and day_validity and year_validity:
which I personally felt was cleaner.
If you're using main()
...
Might as well make sure you're in the right spot when calling main()
, so I added the last if
statement.
Poor February
Leap years aren't too bad to check for. Simply add the year as a parameter to month_check
and do
if int(year) % 4 == 0:
if int(year) % 100 == 0:
days_in_month[2] = 28
else:
days_in_month[2] = 29
else:
days_in_month[2] = 28
User input check
As Gerrit0 pointed out
What happens if I input "test" when asked for a date? The program crashes. It's a good idea to validate that any input from the user is in the format you expect.
This can be fixed with a simple try/except - try all the code in the main()
function, except ValueError
and print a reminder to the user of the proper format.
Negative Dates
Gerrit0 also points out that the function allows for negative numbers (reminds me of this XKCD) in the year. While I didn't add this to the code, I'd change the year function to making sure the actual number is in the proper range, which then allows for the nicety that you can do all your type conversions on the inputs directly instead of before each function input. It also provides a nice way to make sure the date is in the Gregorian calendar.
Final Code
def month_check(month):
if month > 0 and month <= 12: ## If month is between 1 and 12, return True.
return True
else:
return False
def day_check(month, day):
days_in_month = {1:31, 2:28, 3:31, 4:30, 5:31, 6:30, 7:31, 8:31, 9:30, 10:31, 11:30, 12:31}
if 0 < day <= days_in_month[month]:
return True
else:
return False
def year_check(year):
if len(year) >= 1 and len(year) <= 4: ## Check if year has between 1 to 4 numbers and return True.
return True
else:
return False
def main():
date = str(input("Enter the date in mm/dd/yyyy format: ")) ## Input date in the given format.
try:
month,day,year = date.split("/") ## split the date into 3 separate variables.
month_validity = month_check(int(month))
day_validity = day_check(int(month),int(day))
year_validity = year_check(year)
if month_validity and day_validity and year_validity: ## check if all 3 variables are valid or True
print("The date {0} is valid.".format(date))
else:
print("The date {0} is invalid.".format(date))
except ValueError:
print('Your input was not valid. Please enter the date in a mm/dd/yyyy format.')
if __name__ == "__main__":
main()
-
\$\begingroup\$ Thank you @heather for reviewing my code. I ve started implementing the proper way to name functions and variables. I didn't understand the part of using main(). As for the input check, it falls under exception handling? I'll read more on that part. \$\endgroup\$mathlover92– mathlover922018年07月31日 18:15:11 +00:00Commented Jul 31, 2018 at 18:15
-
\$\begingroup\$ @mathlover92 So what the
if __name__ == "__main__":
does is check if you are running that file from that file's location - i.e., whether or not you are importing it. This is useful because if you are importing the file, it runs any "loose" code. So if you wanted to import yourmain()
function into another file by doingfrom filename import main
then it would automatically run main and you probably don't want that. Theif
statement prevents that from happening. And yeah, the input check is exception handling and it's very useful to control program flow [...] \$\endgroup\$auden– auden2018年07月31日 18:22:22 +00:00Commented Jul 31, 2018 at 18:22 -
\$\begingroup\$ @mathlover92 just keep in mind when using exception handling that it's not very computationally expensive unless you catch an exception. So if you're only catching exceptions now and again, like here, with user error, then it's a good spot to use it. It basically just runs the code, and if it runs into the error
ValueError
it goes into the other part of the code that explains what to do if that happens. If an error other than ValueError is raised, then you see it, which is why you generally don't want to dotry: ... except: ...
because it means that it catches all errors, [...] \$\endgroup\$auden– auden2018年07月31日 18:24:25 +00:00Commented Jul 31, 2018 at 18:24 -
\$\begingroup\$ @mathlover92 [...] whether or not they're the right one, which means if your program isn't behaving as expected, you can't see any of the other errors (or in our case ValueErrors unrelated to user input) popping up. That's one thing to be careful about using them. Tl;dr, look up exceptions =) \$\endgroup\$auden– auden2018年07月31日 18:25:37 +00:00Commented Jul 31, 2018 at 18:25
-
\$\begingroup\$ thank you @heather i'll find some problems to practice exception handling so i can get a good grip on it..and is exception handling used in almost all programs? do u recommend any good site for beginners to practice python programming problems? \$\endgroup\$mathlover92– mathlover922018年07月31日 20:35:31 +00:00Commented Jul 31, 2018 at 20:35
Following on from heather's answer with simplified functions.
Simplified month check return:
def month_check(month):
return 0 < month <= 12
This is a different implementation of day_check, using a plain list rather than a dictionary, and a simplified return:
def day_check(month, day):
days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
return 0 < day <= days_in_month[month-1]
Simplified year_check return
def year_check(year):
return 1 <= len(year) <= 4
This doesn't answer the OP's question, but wanted to mention dateutil
as a shortcut for parsing many different date string styles into a date (or datetime) object.
from dateutil.parser import parse
def valid_date(date_value):
try:
return parse(date_value).date()
except ValueError as e:
logger.exception(f"Not a valid date: '{date}' - reason: {e}.")
There are really great answers here, but i want to show you how do i think when i see comments (and every time i want to add one).
Comments are used normally when you want to clarify your code. But, if your code is clear enough, the comment will become redundant. And reading redundant things makes ourselves feels like we are wasting time, so naturally the comment start to be innecesary: deletable.
I will suppose you understood the given answers by others here, so i will focus on comments:
def is_month_valid(month):
return 0 < month <= 12 ## If month is between 1 and 12, return
If you are in a situation where you want to keep your comment because you find it clearer than your code: Make your code look a bit more like that comment.
For example, you could try thinking:
return 0 < month <= 12 ## If month is between 1 and 12, return
return 1 <= month <= 12
return is_between(month, 1, 12)
return is_the(month).between(1, 12)
return comparable(month).is_between(1, 12) ## Or you could call is_month_valid(comparable(month)) instead and this gives you...
return month.is_between(1, 12) ## If month is between 1 and 12, return
## Also, if month.is_between(1, 12): return... I think the other answers actually answers why this should be discarded. If not, keep reading and think why.
All of them are valid solutions. And if you see, the last one will make the comment really redundant:
def is_month_valid(month):
return month.is_between(1, 12) ## If month is between 1 and 12, return
So, why you will keep this comment? The code is clear, and actually, that comment is making your code look unclean, so you don't have another choice than delete that.
Yay!!... Fixed? ... Is this better? We just focus on readability and nothing else? Not exactly. You have to keep in mind the complexity that each solution has. So:
return 0 < month <= 12 ## If month is between 1 and 12, return
This is a pretty good solution. Why? Is readable to the point it makes the comment redundant. And you don't need nothing else to make it work. But
return 1 <= month <= 12 ## If month is between 1 and 12, return
Is this better? The comment now is more redundant. And you didn't add complexity. This is like a free change. Also, this doesn't allow numbers between (0, 1) as valid months. So yes, this is actually better.
return is_between(month, 1, 12) ## If month is between 1 and 12, return
Well, at least for me, this is less legible than previous ones. And needs you to create another function to make it work. Is this function worth enough? Well, one could point that if you create it you could reuse that function in different places, like in is_between(year, 1, 10000)
. And it allows you to centralize integer validations here... But, you have to decide. That's programming actually: Take decisions and justify them. ((削除) Like naming things... (削除ここまで))
return is_the(month).between(1, 12) ## If month is between 1 and 12, return
This is actually more redundant and more readable. But, now, you've created a class (another function) with an arguable name, and also another function associated (like a method). However, this maintains the pros from last solution. At this point, this is starting to feel like an unnecessary over engineered solution, but we are just explaining our thinkings.
return comparable(month).is_between(1, 12) ## If month is between 1 and 12, return
Why is_the
an arguably name? Think that some time has passed and you see a function called is_the. My main reaction would be "What the heck is this thing!". It says nothing. You can't know it's purpose without reading it inner code. So, this solution fixes a bit that arguably name. Now month
is comparable to 1 and 12. However, this is still over engineering for a little advantage (helps to dry).
return month.is_between(1, 12) ## If month is between 1 and 12, return
Now, this is better. That code is more redundant and readable than previous solutions. But, what is its price? Now, every time you want to call is_month_valid
you depend on sending a comparable object first: is_month_valid(comparable(month))
. And don't forget you still have to create comparable and is_between too to make this work. But, is this over engineered solution needed? That will depend on how necessary is this in your program: how much this helps to dry, and how probably is this going to change in the future. If it's not stable, could cause more problems than fixes.
Of course, for every solution exists a better one at a particular time. To get them you just have to continue over engineering it and balance their pros and cons. I will not continue doing that because i think is clear now. And looking at the size of your program, i'm sure the last solution is too much, while the second one (1 <= month <= 12
) is simple and clear.
And keep doing the same for every comment you have, or you are tempted to make.
monthlist1 = [1,3,5,7,8,10,12] ## monthlist for months with 31 days.
monthlist2 = [4,6,9,11] ## monthlist for months with 30 days.
monthlist3 = 2 ## month with month with 28 days.
Wouldn't those lines be replaceable with this?
months_with_31_days = [1,3,5,7,8,10,12] ## monthlist for months with 31 days.
months_with_30_days = [4,6,9,11] ## monthlist for months with 30 days.
month_with_28_days = 2 ## month with month with 28 days.
Redundant comments now, they are just messing with your code.
months_with_31_days = [1,3,5,7,8,10,12]
months_with_30_days = [4,6,9,11]
february = 2 ## And month_with_28_days? Isn't that just february?.
Also, like @heather said, i would prefer to make a function days_in_month instead of this. But, my focus here is to show how do i think when facing comments.
for month in monthlist: ## iterate through monthlist.
I know this is going to dissapear, but, is that comment actually needed? Is for (each) month in monthlist:
less clear than iterate through monthlist? I'm sure most programmers are going to recognize what is this line doing by just reading it. In this case, i think the comment isn't needed.
if month == mon: ## Check if the parameter month equals to any month with 31 days.
This is an interesting comment, because you need some context to understand this code. You can't just read that and know what is happening. You need this picture:
def is_day_valid(month, day):
...
for mon in months_with_31_days:
if month == mon: ## Check if the parameter month equals to any month with 31 days.
Now, you can understand why you use mon
instead of month, and why is there another month variable. This situation; when you need some context to understand a line; is undesirable. Luckily, you've put a comment with the required context to understand it, but, is necessary? This was solved by @Gerrit0:
if month in months_with_31_days: ## Check if the parameter month equals to any month with 31 days.
That comment does add noise! Remove it and its perfect. But what can you do if you needed that for?
for month_with_31_days in months_with_31_days:
if month == month_with_31_days: ## Check if the parameter month equals to any month with 31 days.
Now is possible to forget that for
line while reading the if
line if you want. You don't care which month with 31 days it is, but you know if you enter inside that if
block is because month
have 31 days. And you can over engineer solutions like we did previously for getting more readability in that single line... Check for example if days_in(month) == 31:
, that will move the previous for line from is_day_valid
. Try to make functions short and readable. With less lines of code in each function, you have less probability to need more context.
What about this?
date = input("Enter the date in mm/dd/yyyy format: ") ## Input date in the given format.
If you read that line, you know you are asking the user for an input and saving it in a variable called date
. You are actually saying less in that comment. However, redundant and needless. And what about dividing that user interaction into another function. For example:
date = ask_user_for_input_a_date()
Too verbose, and unnecessary in this case. However, i want to point that, when you start to have multiple inputs, you will start asking yourself where to ask those inputs. The same happens with the outputs. One good practice for novice is to divide your main
function like:
def main():
inputs = take_inputs() ## You could validate inputs inside if you want
solution = solve_problem(inputs)
show(solution)
You gather all needed information before starting your problem (group your inputs), then solve the problem completely (group your outputs) and lastly show your solution. If you have problems related to inputs, you know where to start looking for. The same applies to the core logic of your problem and final presentation. This is a nice starting point, and fit well on problems that don't need user interaction while solving it.
However, nice start, seriously. I've started my programming life with the same problem; date validation, and my code was a lot worse than yours :)
What you did well
You packaged the code into a main
function and three helper functions, each with a specific purpose.
Style
The most important function to define for this task,
is_valid_mmddyyyy(date)
, isn't defined! That would make your work reusable.The comments are excessive, and mainly serve to guide Python beginners. Instead of those comments, it would be better to document the purposes of the functions instead, as docstrings.
You should rarely need to write
True
orFalse
explicitly in your code. The patternif day >= 1 and day <= 31: return True else: return False
... would be better written as
return 1 <= day <= 31
.Similarly,
if monthvalidity == True and dayvalidity == True and yearvalidity == True:
... would be better written as
if monthvalidity and dayvalidity and yearvalidity:
The names
monthcheck
,daycheck
, andyearcheck
don't quite convey how they behave for valid or invalid inputs. Do they print error messages? Do they raise exceptions? It would be better to name themis_valid_month
,is_valid_day
, andis_valid_year
to make it clear that they are predicates (functions that return eitherTrue
orFalse
with no side-effects).It's annoying that
monthcheck(month)
anddaycheck(month, day)
accept integers as inputs, butyearcheck(year)
accepts a string.monthlist1
and its friends are not optimally named, thus necessitating comments like## monthlist for months with 31 days
. Also,monthlist3
is, deceptively, not a list. A slightly better idea:months_with_31_days = [1, 3, 5, 7, 8, 10, 12] months_with_30_days = [4, 6, 9, 11] months_with_28_days = [2] if month in months_with_31_days: return 1 <= day <= 31 if month in months_with_30_days: return 1 <= day <= 30 if month in months_with_28_days: return 1 <= day <= 28
Validation quality
You assume that there will be two
/
characters in the input. If there are fewer parts or more parts, then it crashes with aValueError
.If the month or day are not integers, then it crashes with a
ValueError
.You only care about the length of the year, so
1/1/zero
and1/1/3.14
are both considered valid dates.As you say, you haven't bothered to support leap years. But the design of the
daycheck
function makes it hard to enhance the code to add leap year support.
Suggested solution
The main challenge is to determine how many days there are in a month. Your daycheck
kind of does that, but falls short in two respects:
It assumes that the
month
is valid. Fordaycheck
to do its job, it needs to perform a lookup anyway, somonthcheck
doesn't really need to be a separate function. You just need to indicate that the month is not in the lookup table.If you wanted to support leap years, the month alone is insufficient: you would also need to know the year.
Keeping that in mind, I would redesign the program this way:
def days_in_month(year, month):
"""
Given a year and month as integers, return the last day of that month
(assuming the Gregorian calendar). Raise ValueError if the month is
invalid.
"""
if month == 2:
is_leap_yr = (year % 4 == 0) and (year % 100 != 0 or year % 400 == 0)
return 29 if is_leap_yr else 28
try:
return {
1: 31, 3: 31, 4: 30, 5: 31, 6: 30,
7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31
}[month]
except KeyError:
raise ValueError('Invalid month')
def is_valid_mmddyyyy(date):
"""
Test whether the date is a string in mm/dd/yyyy format representing a valid
date (assuming the Gregorian calendar).
"""
try:
mm, dd, yyyy = [int(part) for part in date.split("/")]
return 0 < yyyy <= 9999 and 0 < dd <= days_in_month(yyyy, mm)
except ValueError:
# Too few slashes, too many slashes, non-integer part, or invalid month
return False
def main():
date = input("Enter the date in mm/dd/yyyy format: ")
valid = is_valid_mmddyyyy(date)
print("The date {0} is {1}.".format(date, "valid" if valid else "invalid"))
if __name__ == '__main__':
main()
Almost everything is already said by the previous posted answers. But two notes:
if you really use names like monthlist1, monthlist2, monthlist3 then use them in a consistent way: if monthlist1 and monthlist2 are lists of numbers then monthlist3 should be a list of numbers, too. Such a consistent naming makes it easier to read the code. So
monthlist3=[2]
.
# calculate the days of february
if year%4==0:
if year%100==0:
if year%400==0:
days = 29
else:
days = 28
else:
days = 29
else:
days = 28
Most things here are already covered. While the year and the month check are fairly straight-forward, the day check is hard.
def daycheck(year, month, day):
"""
Check if the day is valid.
Parameters
----------
year : int
month : int
day : int
Returns
-------
is_valid : bool
"""
assert 1 <= month <= 12
max_days_per_month = {1: 31, 2: 30, 3:31,
4: 30, 5: 31, 6:30, 7: 31,
8: 31, 9: 30, 10: 31, 11: 30, 12: 31}
if (day > max_days_per_month[day]) or (day <= 0):
return False
if month != 2:
# Not quite true, e.g. September 1752 in the UK had less than 20 days.
return True
dst_applies = (year % 4 == 0) and (year % 100 != 0 or year % 400 == 0)
if not_dst_applies and day >= 29:
return False
return True
Things to learn:
- Use dictionaries. They make things shorter / easier to understand
- Yes, February once had 30 days: https://en.wikipedia.org/wiki/February_30#Swedish_calendar
- Yes, if a local date is valid depends on the city / country, because of calendar system switches.
- It is capture error cases / easy cases in the beginning. The less deep your code is nested, the easier it is to read / check.
Explore related questions
See similar questions with these tags.