Search code examples
pythonfunctionexceptiontry-catchcs50

CS50 Python Week3 pset question "Outdated" - How to reject input instead of reprompt


I was doing the week3 pset question from CS50 when I ran into this problem. The whole question is as follow:

In the United States, dates are typically formatted in month-day-year order (MM/DD/YYYY), otherwise known as middle-endian order, which is arguably bad design. Dates in that format can’t be easily sorted because the date’s year comes last instead of first. Try sorting, for instance, 2/2/1800, 3/3/1900, and 1/1/2000 chronologically in any program (e.g., a spreadsheet). Dates in that format are also ambiguous. Harvard was founded on September 8, 1636, but 9/8/1636 could also be interpreted as August 9, 1636!

Fortunately, computers tend to use ISO 8601, an international standard that prescribes that dates should be formatted in year-month-day (YYYY-MM-DD) order, no matter the country, formatting years with four digits, months with two digits, and days with two digits, “padding” each with leading zeroes as needed.

In a file called outdated.py, implement a program that prompts the user for a date, anno Domini, in month-day-year order, formatted like 9/8/1636 or September 8, 1636, wherein the month in the latter might be any of the values in the list below:

[ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ]

Then output that same date in YYYY-MM-DD format. If the user’s input is not a valid date in either format, prompt the user again. Assume that every month has no more than 31 days; no need to validate whether a month has 28, 29, 30, or 31 days.

I wrote my code and when I ran the check50 to check my answer, some inputs failed shown in this image enter image description here Apparently, my code should reprompt if date or month is out of range, but needs to reject if the whole format is wrong.

mth = [
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December"
]

def check(d):

    if "/" in d:
        numeric(d)
    else:
        words(d)

def numeric(d):
    
    list = d.split("/")
    month = int(list[0])
    day = int(list[1])
    year = int(list[2])

    while True:
        if month > 12 or day > 31:
            date = input("Date: ")
            check(date)
        else:
            break

    print(f"{year}-{str(month).zfill(2)}-{str(day).zfill(2)}")

def words(d):

    list = d.split(" ")
    month = list[0]
    day = list[1]
    year = list[2]

    while True:
        if not str(month).title() in mth:
            date = input("Date: ")
            check(date)
        else:
            index = mth.index(month)
            day = str(day).strip(",")
            break

    while True:
        if int(day) > 31:
            date = input("Date: ")
            check(date)
        else:
            break

    print(f"{year}-{str(index+1).zfill(2)}-{str(day).zfill(2)}")

date = input("Date: ")
check(date)

This is the code I wrote so far, I am assuming I will need to add something like raise exception if d.isalpha() is true in the function numeric(), but I tried and it did not work(maybe I used the wrong way to raise). I will like to know how to resolve this issue and reject the input. Really appreciate it :)


Solution

  • I believe the reason your code fails on "October/9/1701" is that it throws an exception rather than re-prompting the user. The "/" delimiter flags the code to process the month as an int() and int("October") throws a ValueError.

    Note that there are a bunch of other things that can throw ValueError as well, so you might want to just have any validation failure throw it.

    I might try:

    def try_parse_date(date_string):
        valid_month_names = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
    
        ## -------------------
        ## Try to pick out the year, month, and day
        ## -------------------
        if "/" in date_string:
            month, day, year = date_string.split("/")
        else:
            month, day, year = date_string.split(" ")
            month = valid_month_names.index(month) + 1
            day = day.strip(",")
        ## -------------------
    
        ## -------------------
        ## Do some validation
        ## -------------------
        year = int(year)
        month = int(month)
        day = int(day)
    
        if year < 1:  # https://en.wikipedia.org/wiki/Anno_Domini
            raise ValueError
    
        if not (0 < month < 13):
            raise ValueError
    
        if not (0 < day < 32):
            raise ValueError
        ## -------------------
    
        return f"{year:>04}-{month:>02}-{day:>02}"
    
    def prompt_for_date_until_valid():
        while True:
            user_date_string = input("Date: ")
            try:
                return try_parse_date(user_date_string)
            except ValueError:
                pass
    
    iso_date = prompt_for_date_until_valid()
    print(iso_date)
    

    Note: Using cs50.dev is a huge security issue for anyone who has not created a throwaway account for the class as it requires access to private Github repositories.