# Exercise #21: Validate Date

isValidDate(2005, 3, 29)   True
isValidDate(2005, 13, 32)   False

You can represent a date with three integers for the year, month, and day, but this doesn’t mean that any integers represent a valid date. After all, there is no 13th month of the year or 32nd day of any month. This exercise has you check if a year/month/day combination is valid, given that different months have different numbers of days. You’ll use the solution you wrote for Exercise #20, “Leap Year” as part of the solution for this exercise, so finish Exercise #20 before attempting this one.

Exercise Description

Write an `isValidDate()` function with parameters `year`, `month`, and `day`. The function should return True if the integers provided for these parameters represent a valid date. Otherwise, the function returns `False`. Months are represented by the integers `1` (for January) to `12` (for December) and days are represented by integers `1` up to `28`, `29`, `30`, or 31 depending on the month and year. Your solution should import your leapyear.py program from Exercise #20 for its `isLeapYear()` function, as February 29th is a valid date on leap years.

September, April, June, and November have 30 days. The rest have 31, except February which has 28 days. On leap years, February has 29 days.

These Python `assert` statements stop the program if their condition is `False`. Copy them to the bottom of your solution program. Your solution is correct if the following assert statements’ conditions are all `True`:

assert isValidDate(1999, 12, 31) == True

assert isValidDate(2000, 2, 29) == True

assert isValidDate(2001, 2, 29) == False

assert isValidDate(2029, 13, 1) == False

assert isValidDate(1000000, 1, 1) == True

assert isValidDate(2015, 4, 31) == False

assert isValidDate(1970, 5, 99) == False

assert isValidDate(1981, 0, 3) == False

assert isValidDate(1666, 4, 0) == False

import datetime

d = datetime.date(1970, 1, 1)

oneDay = datetime.timedelta(days=1)

for i in range(1000000):

assert isValidDate(d.year, d.month, d.day) == True

d += oneDay

Try to write a solution based on the information in this description. If you still have trouble solving this exercise, read the Solution Design and Special Cases and Gotchas sections for additional hints.

Prerequisite concepts: `import` statements, Boolean operators, chaining operators, `elif` statements

Solution Design

Any integer is a valid value for the `year` parameter, but we do need to pass `year` to our leapyear.isLeapYear() function if `month` is set to `2` (February). Be sure your program has an import leapyear instruction and that the leapyear.py file is in the same folder as your solution’s file.

Any `month` values outside of 1 to `12` would be an invalid date. In Python, you can chain together operators as a shortcut: the expression `1 <= month <= 12` is the same as the expression `(1 <= month) and (month <= 12)`. You can use this to determine if your `month` and day parameters are within a valid range.

The number of days in a month depends on the month:

·       If `month` is `9`, `4`, `6`, or 11 (September, April, June, and November, respectively) the maximum value for `day` is `30`.

·       If `month` is `2` (February), the maximum value for `day` is 28 (or `29` if leapyear.isLeapYear(year) returns `True`).

·       Otherwise, the maximum value for day is `31`.

Like the solution for Exercise #20, “Leap Year,” this solution is a series of `if`, `elif`, or else statements.

Special Cases and Gotchas

To simplify your code as much as possible, think about the cases that can cause the function to return as soon as possible. For example, if month is outside of the `1` to 12 range, the function returns `False`. There’s no other set of circumstances you need to check; the function can immediately return `False`. If it doesn’t return, you can assume that month contains a valid value for the rest of the function.

The other immediate check is if `leapyear.isLeapYear(year)` returns `True` and `month` is `2` and day is `29`, the function can immediately return `True`. If the function hasn’t returned `True` for the leap day, you can assume that leap years are irrelevant for the rest of the code in the function.

Now try to write a solution based on the information in the previous sections. If you still have trouble solving this exercise, read the Solution Template section for additional hints.

Solution Template

Try to first write a solution from scratch. But if you have difficulty, you can use the following partial program as a starting place. Copy the following code from https://invpy.com/validatedate-template.py and paste it into your code editor. Replace the underscores with code to make a working program:

# Import the leapyear module for its isLeapYear() function:

____ leapyear

def isValidDate(year, month, day):

# If month is outside the bounds of 1 to 12, return False:

if not (1 ____ month ____ 12):

return ____

# After this point, you can assume the month is valid.

# If the year is a leap year and the date is Feb 29th, it is valid:

if leapyear.isLeapYear(____) and ____ == 2 and ____ == 29:

return ____

# After this point, you can assume the year is not a leap year.

# Check for invalid dates in 31-day months:

if month in (1, 3, 5, 7, 8, 10, 12) and not (1 <= ____ <= 31):

return ____

# Check for invalid dates in 30-day months:

elif ____ in (4, 6, 9, 11) and not (1 <= day <= 30):

return ____

# Check for invalid dates in February:

elif month == ____ and not (1 <= ____ <= 28):

return ____

# Date passes all checks and is valid, so return True:

return ____

The complete solution for this exercise is given in Appendix A and https://invpy.com/validatedate.py. You can view each step of this program as it runs under a debugger at https://invpy.com/validatedate-debug/.

Python’s `datetime` module has several features for dealing with dates and calendar data. You can learn more about it in Chapter 17 of Automate the Boring Stuff with Python at https://automatetheboringstuff.com/2e/chapter17/.