Prev - #20 Leap Year | Table of Contents | Next - #22 Rock, Paper, Scissors

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/.

Further Reading

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/.

Prev - #20 Leap Year | Table of Contents | Next - #22 Rock, Paper, Scissors