Problems with a date validation function in C

Asked

Viewed 134 times

3

I’m developing a system for patient registration. It’s a college project. I’m splitting function groups into different files. It turns out that I created a function to validate the date registration, the function works but does not record the last type value that would be correct.

Follows the code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "validacoes.h"

void validaData(int valor, int x, int y, char data[4]){
if(valor < x || valor > y){
        do{
            printf("%s não existente, digite um %s válido! \n", data, data);
            printf("%s: ", data);
            scanf("%d", &valor);
            printf("%d \n", valor);
        }while(valor < x || valor > y);
    };
}

This is the function file:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pacientes.h"
#include "validacoes.h"

typedef struct {
        int dia;
        int mes;
        int ano;
} data;
typedef struct {
        char rua[30];
        char bairro[30];
        char cidade[30];
        char estado[3];
        char cep[10];
} endereco;
typedef struct {
    int id;
    char nome[30];
    char email[30];
    char cpf[15];
    char telefone[15];
    //char comorbidade;
    data dataNascimento;
    endereco enderecoPaciente;
} cadastro;

cadastro paciente[100];   
void registro(){    

fflush(stdin);
        printf("Data de nascimento \n");
        printf("Dia: ");
        scanf("%d", &paciente[i].dataNascimento.dia);
        validaData(paciente[i].dataNascimento.dia, 1, 31, "Dia");
        printf("Mês: ");
        scanf("%d", &paciente[i].dataNascimento.mes);
        validaData(paciente[i].dataNascimento.mes, 1, 12, "Mes");
        printf("Ano: ");
        scanf("%d", &paciente[i].dataNascimento.ano);
        validaData(paciente[i].dataNascimento.ano, 1900, 2020, "Ano");
        system("Pause");
 }

The function registro is where the function validaData should work.

However, when I am making the registration, the function validaData can take the value if typed outside the parameters passed in the function. But when it finishes executing, with the correct typing of the value, what is saved in the variable paciente[i].dataNascimento.dia is the first value entered before going through validation.

Someone would know how to solve?

1 answer

2


The parameter valor of function validaData is passed by value. So when you do scanf("%d", &valor);, you will change only the value of the local variable valor, but this will not alter the content of paciente[i].dataNascimento.dia, nor of paciente[i].dataNascimento.mes nor paciente[i].dataNascimento.ano. The right thing would be validaData return the new value read.

Another detail is that a function called validaData should, given her name do only the validation. But she is not doing this, she is doing the rereading if the date is incorrect. A more robust approach would be to do the function to read the date and reread as many times as necessary until the informed date is valid.

There is also a validation issue that your code accepts invalid dates such as 31/04/2020 or 29/02/2021, as not every month has days 29, 30 and 31.

In addition, there is the case that the user responds xyz when the day is asked, and it will confuse the scanf. Since this is a college project, it is best to read what the user type without making any kind of presumption about what he type and then try to build a date with it.

Another thing is that the fflush(stdin) only works in windows. See that one and that one answers explaining how to solve.

In fact, the function scanf It’s an old-fashioned, hard-to-use function and its biggest problems stem from it. This function should no longer be used. See here explanations about why not to use it and what to use in its place.

Therefore, I suggest this function to read a date:

// Autor: Victor Stafusa - /users/132
#include <stdio.h>

typedef struct {
    int dia;
    int mes;
    int ano;
} data;

// Veja mais sobre isso nesses links:
// /a/231882/132
// /a/96012/132
#if defined(__MINGW32__) || defined(_MSC_VER)
#define limpar_input() fflush(stdin)
#else
#include <stdio_ext.h>
#define limpar_input() __fpurge(stdin)
#endif

// Monta um número de 2 dígitos a partir de 2 chars.
#define numero2(a, b) \
    ( (a - '0') * 10 \
    + (b - '0'))

// Monta um número de 4 dígitos a partir de 4 chars.
#define numero4(a, b, c, d) \
    ( (a - '0') * 1000 \
    + (b - '0') * 100 \
    + (c - '0') * 10 \
    + (d - '0'))

// Verifica se a string dada tem um formato NN/NN/NNNN.
// Mas não verifica nada após o 10o caractere.
int validarFormatoData(const char *digitado) {
    for (int i = 0; i < 10; i++) {
        char c = digitado[i];
        if ((i == 2 || i == 5) ? c != '/' : (c < '0' || c > '9')) return 0;
    }
    return 1;
}

// Descobre quantos dias há no mês de um determinado ano.
int diasNoMes(int mes, int ano) {
    // São bissextos os anos divisíveis por 4,
    // com exceção dos que terminam com 00 e não são divisíveis por 400.
    int bissexto = (ano % 4 == 0 && ano % 100 != 0) || ano % 400 == 0;

    // Tabela com os dias de cada mês.
    // Observe que fevereiro varia dependendo se o ano for ou não bissexto.
    // Observe que a posição zero não é usada porque não há mês zero.
    int dias[] = {0, 31, bissexto ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    return dias[mes];
}

// Descobre se a data do meio de fato está entre a data mínima e a máxima.
int comparaData3(const data *min, const data *meio, const data *max) {
    // Monta um inteiro para cada data na forma AAAAMMDD.
    int a = min->ano * 10000 + min->mes * 100 + min->dia;
    int b = meio->ano * 10000 + meio->mes * 100 + meio->dia;
    int c = max->ano * 10000 + max->mes * 100 + max->dia;

    // Como as datas foram reduzidos a números inteiros,
    // fica fácil saber se estão na ordem correta.
    return a <= b && b <= c;
}

// Lê uma data do usuário no formato DD/MM/AAAA.
// Insiste até que uma data válida seja digitada.
// Inclui uma mensagem que pede para o usuário digitar alguma coisa.
// Inclui uma mensagem de erro para o caso de o usuário digitar algo inválido.
// Inclui as datas mínimas e máximas permitidas.
data lerData(const char *mensagem, const char *erro, const data *min, const data *max) {
    char digitado[12];
    int primeiraVez = 1;

    // Repete quantas vezes for necessário, a menos que o return seja alcançado. 
    while (1) {
        // Se não for a primeira vez, então é porque a vez anterior deu errado e uma
        // mensagem de erro deve ser mostrada.
        if (primeiraVez) {
            primeiraVez = 0;
        } else {
            printf("%s\n", erro);
        }

        // Mostra a mensagem e lê o que o usuário digitar.
        printf("%s", mensagem);
        fgets(digitado, 12, stdin);
        limpar_input(); // Limpa o input se ficou sujeira nele.

        // Se os 10 primeiros caracteres não estiverem no formato NN/NN/NNNN,
        // então a data é inválida. E se houver algo depois desse NN/NN/NNNN, ela
        // também é.
        if (!validarFormatoData(digitado) || digitado[10] != '\n') continue;

        // Se chegou aqui, então a data tem um formato válido, o que ainda não
        // necessariamente significa que ela é válida. Nesse ponto já podemos
        // obter o número do dia, do mês e do ano ao montar inteiros a partir dos
        // caracteres digitados.
        int dia = numero2(digitado[0], digitado[1]);
        int mes = numero2(digitado[3], digitado[4]);
        int ano = numero4(digitado[6], digitado[7], digitado[8], digitado[9]);

        // Verifica se o dia e o mês são válidos.
        // Inclusive, graças a função diasNoMes, verifica se o mês tem 30 ou 31
        // dias ou sse for fevereiro, se o ano é bissexto ou não.
        if (mes < 1 || mes > 12 || dia < 1 || dia > diasNoMes(mes, ano)) continue;

        // A data é válida. Podemos construí-la.
        data d = {dia, mes, ano};

        // Verifica se que a data montada está entre a data mínima e a máxima.
        if (!comparaData3(min, &d, max)) continue;

        // Se chegou até aqui, a data está ok.
        return d;
    };
}

// Teste do código todo.
// Pede para digitar uma data, insistindo até que uma data válida seja digitada
// e então exibe essa data.
int main(void) {
    data min = {1, 1, 1900};
    data max = {31, 12, 2020};
    data d = lerData(
        "Digite a data de nascimento (DD/MM/AAAA): ",
        "Essa data não estava certa. Vamos tentar novamente.",
        &min,
        &max);
    printf("A data que você digitou foi %d/%d/%d.", d.dia, d.mes, d.ano);
    return 0;
}

The comments in the code explain what he is doing. Basically, it will give you a function lerData that will ask the user to enter a date and will insist until the user enters a valid date.

See here the code working in repl.it.

  • 1

    Very good! 133 lines of code, month size validation, leap years, etc. Whenever I see these solutions to university problems my appreciation for modern Apis from Date/Time is renewed.

  • 2

    @Anthonyaccioly has the struct tm which would be the best type of data already prefabricated for this with several ready-made library functions that manipulate it. However, using it correctly also brings its challenges and complications. It was because of so much living with this kind of situation using so much low-level stuff in C and having to reinvent the square wheel every day, that I ended up specializing in Java and now in Python.

  • @Victorstafusa Bro, thank you very much, helped me a lot.

Browser other questions tagged

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