Python date validation (tests)

Asked

Viewed 3,895 times

3

I am trying to do a date validation with Python. My exercise asks for the validation of months with 31 days and leap year.

def data_valida(data):
    #Valida data. Recebe uma string no formato dd/mm/aaaa e informa
    #um valor lógico indicando se a data é válida ou não.

    dia, mes, ano = data.split("/")

    meses_31 = ('01', '02', '03', '05', '07', '08', '10', '12')

    if ano > '0001' and '12' <= mes >= '01' and '31' <= dia >= '01':

        if int(ano) % 4 == 0 and mes == '2':
            if dia <= '29':
                return True
            else:
                return False
        elif mes == '2':
            if dia < '29':
                return True
            else:
                return False

        if mes == meses_31 and dia <= '31':
            return True
        elif dia <= '31':
            return False

    else:
        return False

Testing:

def main():
    print('Valida datas:')
    test(data_valida("01/01/2014"), True)
    test(data_valida("31/01/2014"), True)
    test(data_valida("00/00/0000"), False)
    test(data_valida("30/04/2014"), True)
    test(data_valida("31/04/2014"), False)
    test(data_valida("30/09/2014"), True)
    test(data_valida("31/09/2014"), False)
    test(data_valida("30/06/2014"), True)
    test(data_valida("31/06/2014"), False)
    test(data_valida("30/11/2014"), True)
    test(data_valida("31/11/2014"), False)
    test(data_valida("32/01/2014"), False)
    test(data_valida("01/01/0000"), False)
    test(data_valida("01/13/2014"), False)
    test(data_valida("01/00/2014"), False)
    test(data_valida("29/02/2014"), False)
    test(data_valida("29/02/2016"), True)

I’ve tried to find content on and even the closest I’ve come is the current algorithm. Whenever I arrive in the leap year I can’t fix and this time the code seems ok, it’s returning False for everything.

  • 1

    if mes == meses_31 doesn’t make much sense, I think the right thing here is if mes in meses_31. This is because we want to know if the mes is one of the elements of meses_31, and not if it is equal to the entire tuple (all elements). The condition below this (elif dia <= '31': return False) also has a problem in logic, a day less than or equal to 31 may be valid yes. An example is the day 30/04/2014. You want to try to work it out from this?

  • 1

    And pass the dates (day, month and year) to integers. Comparing string with string is different from comparing integers with integers. '04' >= '02' returns True, but '04' >= '2' returns False. When comparing strings, Python takes the ASCII value of the characters to compare.

  • Thanks for the tips. I think I’ve managed to finish now rs. I haven’t done the conversion to integers you indicated. Anyway, could you tell me what would be the most 'right' way to do the conversion? I’ll post how it went down

3 answers

3


Because day, month, and year values are numerical, it is best to turn them into numbers. The comparisons you make with strings take into account the lexicographical order ("alphabetic") and "works" by coincidence. But if you are dealing with numerical values, turn them into numbers (this facilitates even the arithmetic operations to check leap years, as we will see below).

For this you can use map, passing the function int as a parameter, thus the result of split will be transformed into numbers:

dia, mes, ano = map(int, data.split('/'))

Thus, each element of the list returned by split will be passed to the function int, that turns the string into a number.

Then I saw that you only want dates whose year is greater than or equal to 1, so I already put this check right at the beginning, along with the month check.

Soon after, we need to determine the amount of days of that month and year, and for that we need to know not only the value of the month (to know if it has 30 or 31 days), but also if the year is leap, if the month is February. And the rule of the leap year is: the year must be multiple of 4, but if it is multiple of 100, it is only leap if it is also multiple of 400 (so the year 2000 is leap, but 1900 is not).

This rule of considering leap all years multiples of 4 was previously used in the Julian Calendar, but we currently use the Gregorian Calendar, which instituted the new rule of multiples of 400 - see more information on wiki of the respective tag.

Having the last day of the month set, we can check if the day is within these limits. The code looks like this:

def data_valida(data):
    # faz o split e transforma em números
    dia, mes, ano = map(int, data.split('/'))

    # mês ou ano inválido (só considera do ano 1 em diante), retorna False
    if mes < 1 or mes > 12 or ano <= 0:
        return False

    # verifica qual o último dia do mês
    if mes in (1, 3, 5, 7, 8, 10, 12):
        ultimo_dia = 31
    elif mes == 2:
        # verifica se é ano bissexto
        if (ano % 4 == 0) and (ano % 100 != 0 or ano % 400 == 0):
            ultimo_dia = 29
        else:
            ultimo_dia = 28
    else:
        ultimo_dia = 30

    # verifica se o dia é válido
    if dia < 1 or dia > ultimo_dia:
        return False

    return True

If you want, you can also put an additional control, because if the function data_valida receive a string containing any text ('abc'), or numbers that are not in the "day/month/year" format (for example "12345" or "1-2-3"), there will be a ValueError. So you can put all the code inside a block try and return False if the ValueError:

def data_valida(data):
    try:
        # faz o split e transforma em números
        dia, mes, ano = map(int, data.split('/'))

        # mes ou ano inválido, retorna False
        if mes < 1 or mes > 12 or ano <= 0:
            return False

        # verifica qual o último dia do mês
        if mes in (1, 3, 5, 7, 8, 10, 12):
            ultimo_dia = 31
        elif mes == 2:
            if (ano % 4 == 0) and (ano % 100 != 0 or ano % 400 == 0):
                ultimo_dia = 29
            else:
                ultimo_dia = 28
        else:
            ultimo_dia = 30

        # verifica se o dia é válido
        if dia < 1 or dia > ultimo_dia:
            return False

        return True
    except ValueError:
        return False

As an exercise, it is valid to try to implement the above logic. But if it is for code in production, prefer to use specific functions to handle dates, such as module datetime:

from datetime import datetime

def data_valida(data):
    try:
        datetime.strptime(data, '%d/%m/%Y')
        return True
    except ValueError:
        return False

The method strptime tries to do the Parsing of the string, using the given format (in this case, '%d/%m/%Y', meaning "day/month/year" - see the documentation for more details about this parameter). If the date is invalid, it launches a ValueError.

1

You passed the tests now, follow my resolution:

day, month, year = data.split("/")

months_31 = ('01', '02', '03', '05', '07', '08', '10', '12')

if (year >= '0001') and (month >= '01') and (month <= '12') and (day >= '01') and (day <= '31'):

    if int(year) % 4 == 0 and month == '02':
        if day <= '29':
            return True
        else:
            return False
    elif month == '02':
        if day < '29':
            return True
        else:
            return False

    if month in months_31 and day <= '31' or day <= '30':
        return True
    elif day == '31':
        return False

else:
    return False

You could point out things I’m doing wrong (other than not using integers)?

  • 2

    Leap year is one that occurs every four years, except multiple years of 100 that are not multiples of 400. So to know if the year is leap, the condition would be something like: if (ano%4==0 and ano%100!=0 or ano%400==0): print("Ano Bissexto").

0

You can use the following function:

def validar_data(data):
    """
    A função validar_data efetua a validação de uma data digitada pelo usuário (DD/MM/AAAA).
    :param texto: Data digitada pelo usuário(DD/MM/AAAA)
    :return: False (caso a data seja inválida) e True (caso a data seja válida)
    """
    data_digitada = str(data[6:]+'-'+data[3:5]+'-'+data[0:2])
    while True:
        try:
            verif = date.fromisoformat(data_digitada)
            return True
        except ValueError:
            return False

Browser other questions tagged

You are not signed in. Login or sign up in order to post.