1

I am taking a certain date as an input from user in following format: mm/dd/yyyy.

How can I make certain that the user is entering numeric values only and if not then prompt him again to enter a valid date. I am already checking if a date is valid or not on basis of the values entered but if the values entered by user are not entirely numeric then the program crashes.

Please help...

The Code:

int x=0;
printf("\nEnter the date of birth(mm/dd/yyyy):");
do
{
    scanf("%d/%d/%d",&add.dob.month,&add.dob.day,&add.dob.year);
    if((add.dob.month<13 && add.dob.month>0) && (add.dob.day>0 && add.dob.day<32) && (add.dob.year<2016))
    {
        x=1;
    }
    else
    {
        printf("\nThe above date of birth is invalid.\nEnter a valid date of birth(mm/dd/yyyy):");
    }
}while(x!=1);
Rishabh Malhotra
  • 269
  • 3
  • 13
  • Note that the `scanf()` will allow you to enter a number on one line, a slash on the next, a couple of blank lines, another number on a line, and a slash, some blanks and the third number on the next line. If that's not what you want, then `scanf()` is not the best function to use. You should probably then get a line of text ([`fgets()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/fgets.html) or POSIX [`getline()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/getline.html)) and then parse it with `sscanf()`. That'll still allow `0000001 / 1 / 002015.00` as a date. – Jonathan Leffler Feb 21 '15 at 06:59

5 Answers5

3

There are a couple of issues. If using scanf check its return. In addition to the numeric validity checks, you also have the issue of needing to flush the input buffer in the event something other than an integer is entered by the user. While there are several ways to do this, one way keeping with your approach would simply be to preform the checks you are doing, then manually flush the input buffer with a second do/while loop. You can also turn your logic around and test for any one of the conditions being true forcing another prompt for the date. As noted there are many ways to approach this. Consider this as one among the other answers you receive.

int c = 0;          /* value to test for end of input buffer */

printf("\nEnter the date of birth(mm/dd/yyyy):");

while ((scanf("%d/%d/%d",&add.dob.month,&add.dob.day,&add.dob.year) != 3) ||
       (add.dob.month < 1) || 
       (add.dob.month > 12 ) ||
       (add.dob.day < 1 ) ||
       (add.dob.day > 31) ||
       (add.dob.year < 1) || 
       (add.dob.year > 2015))
{
    printf("\nThe above date of birth is invalid.\nEnter a valid date of birth(mm/dd/yyyy):");
    do { c = getchar(); } while (c != '\n' && c != EOF);  /* flush input buffer */
}

As mentioned a slightly more robust way to read the information from stdin would be to read the date as a character string into a buffer using fgets or getline and then to parse the month, day, year information from the buffer. This can be done in many ways as well. Since you will ultimately be converting from character to decimal, you may as well just use strtol to parse and convert rather then either stepping down the string with a pointer or parsing with strtok or strsep (as each would require conversion to decimal after separation anyway). This is a quick example that provides a fairly robust solution. It also allows dates to be entered as mm-dd-yyyy or mm.dd.yyy in addition to mm/dd/yyyy:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <errno.h>

#define SZDATE 12

int main (void) {

    int mm = 0;                     // month/day/year values
    int dd = 0;
    int yyyy = 0;
    int gooddate = 0;               // flag signifying valid date
    char datestr[SZDATE] = {0};     // buffer to hold input string
    char *p = NULL;                 // pointer to use with strtol
    char *endptr = NULL;            // end pointer for strtol
    long val = 0;                   // long value for strtol

    while (gooddate == 0)
    {
        printf("\nEnter the date of birth (mm/dd/yyyy): ");

        /* read string with fgets */
        fgets (datestr, SZDATE-1, stdin);

        /* test sufficient length */
        if (strlen (datestr) < 8) {
            printf ("\n insufficient date length entered, try again.\n");
            continue;
        }

        /* parse month value with strtol (with error checking) */
        errno = 0;
        val = strtol (datestr, &endptr, 10);
        if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
            || (errno != 0 && val == 0)
            || (endptr == datestr)
            || (*endptr != '/' && *endptr != '-' && *endptr != '.')
            || (val < 1)
            || (val > 12)) {
                printf ("\n invalid month entered, try again.\n");
                continue;
        }
        mm = (int) val;     /* set month on successful conversion   */

        p = ++endptr;       /* set p to start of day, reset endptr  */
        endptr = NULL;

        /* parse day value with strtol (with error checking) */
        errno = 0;
        val = strtol (p, &endptr, 10);
        if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
            || (errno != 0 && val == 0)
            || (endptr == p)
            || (*endptr != '/' && *endptr != '-' && *endptr != '.')
            || (val < 1)
            || (val > 31)) {
                printf ("\n invalid day entered, try again.\n");
                continue;
        }
        dd = (int) val;     /* set day on successful conversion     */

        p = ++endptr;       /* set p to start of year, reset endptr */
        endptr = NULL;

        /* parse year value with strtol (with error checking) */
        errno = 0;
        val = strtol (p, &endptr, 10);
        if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
            || (errno != 0 && val == 0)
            || (endptr == p)
            || (*endptr != '\n' && *endptr != 0)
            || (val < 1900)
            || (val > 2015)) {
                printf ("\n invalid year entered, try again.\n");
                continue;
        }
        yyyy = (int) val;   /* set year on successful conversion    */

        gooddate = 1;       /* set gooddate flag ending loop        */
    }

    printf ("\n valid date is: %d/%d/%d\n\n", mm, dd, yyyy);

    return 0;
}

Think through the checks associated with the strtol conversions. You may have more you would like to add. Also, try the parsing with strtok or the like and compare. This is just one way of approaching reading as a string and then parsing. It can be done many different ways.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
2

You should check the return value of scanf to see whether it was able to make all of the assignments. If not, you need to consume the user's input before you try again, or you will have an infinite loop trying and failing to read the same input over and over. For instance something like:

do {
    printf("\nEnter the date of birth(mm/dd/yyyy): ");
    fflush(stdout);
    if (scanf("%d/%d/%d", &month, &day, &year) != 3) {
        scanf("%*[^\n]"); // consume line from input, do not assign
    } else if (month < 13 && month > 0 && day < 32 && day > 0 && year < 2016) {
        break;
    }
    printf("Invalid date!\n");
} while (!feof(stdin));

On another note your check for the date's validity is lacking, the user could still enter 02/30/-5, for instance.

edit: In reference to discussion in comments below, the handling of corner cases here is poor; the assumption is that this is a program intended to always run interactively on a local terminal. For proper handling of input I would strongly recommend to switch from scanf to fgets (+ possibly sscanf afterwards). The return value of fgets should then be checked and the error handled there, instead of the potentially problematic feof check. Of course the scanf solution could be made more pedantic by checking properly for different return values (such as EOF), but I don't see it as being worthwhile.

Arkku
  • 41,011
  • 10
  • 62
  • 84
  • 1
    Due to the pitfalls of `scanf` you might also wish to consider using `fgets` to read one line of user input and then parse that line (possibly using `sscanf`). – Arkku Feb 21 '15 at 06:50
  • [“while( !feof( file ) )” is always wrong](http://stackoverflow.com/questions/5431941/while-feof-file-is-always-wrong) – pmg Feb 21 '15 at 09:07
  • @pmg: but `do { ... } while (!feof(file));` is not always wrong. – Jonathan Leffler Feb 21 '15 at 09:23
  • @pmg Perhaps it may lead to an extra prompt being printed if the EOF condition occurs only at the next `scanf`, but given all the other issues with reading user's input with `scanf`, I'd argue that it serves its purpose here (i.e., terminate loop eventually if user terminates input). – Arkku Feb 21 '15 at 09:24
  • What if `stdin` is redirected to a network file and soneone plugs the cable while the loop is running? `feof(stdin)` will always return false and the `scanf` wil never return 3. – pmg Feb 21 '15 at 09:40
  • @pmg Should it then terminate, perhaps it is the user's wish to continue running when the network file comes back online… – Arkku Feb 21 '15 at 09:44
  • Anyhow, I would argue from the looks of this program that this is an exercise that comes with the precondition that it be run interactively on a typical terminal, and if it were made a “proper application” then I would immediately ditch both `scanf` and `feof`. (To be honest, I originally posted a version with the loop `do { … } while (1)`. =) – Arkku Feb 21 '15 at 10:21
2

You can check the return value of scanf() to test if the three entered values are of int type.

Code:

int returnval = scanf("%d/%d/%d",&input1, &input2, &input3);
if (returnval == 3)
{
    //The input is correct.
}
else
{
   printf("The input is not correct.");
   //The input is not correct.
}
shauryachats
  • 9,975
  • 4
  • 35
  • 48
  • If you enter something other than an `integer`, the `scanf` call will loop continually if utilized within a loop as the question presented. Try: `a/1/2014` and confirm. – David C. Rankin Feb 21 '15 at 07:02
  • Yes, you are right. Can you suggest a workaround for this, because I'm blank. – shauryachats Feb 21 '15 at 07:12
  • @shauryachats My answer shows the workaround, i.e., consume input… But overall `fgets` instead of `scanf` would be better to read the user's input. – Arkku Feb 21 '15 at 07:22
1

you want to check the return value of scanf, it will return the number of inputs read.
Just say input1 input2 and input3 are ints

int isValid = 0;
while (!isValid)
{
    isValid = scanf("%d/%d/%d",&input1, &input2, &input3);
    if(isValid != 3)
    {
        printf("\nThe above date of birth is invalid.\nEnter a valid date of birth(mm/dd/yyyy):");
    }
    else
    {
        // the user entered integers
        if((add.dob.month<13 && add.dob.month>0) && (add.dob.day>0 && add.dob.day<32) && (add.dob.year<2016))
        {
        }
        else
        {
            isValid = 0; // it didn't pass your date validation but it did pass integer validation 
        }
    }
}

if the user enters characters or strings or anything that's not an integer then scanf will return 0 and you can make it loop. I used a pretty bad if/else up there by leaving the if part blank, it'd be nicer to negate each one but that's a different story. Moral of the story is just check what scanf returns, if it returns > 1, the user entered digits, if it returns 0, then they tried typing letters in and it failed.

1

You can scan the input and then parse that through a function like strtol(). strtol() will tell you if you have all numeric or a mix. This will save you quite some hassle and all you have to do in the end is check the numeric boundaries.

Please have a look at the answers to atoi() — string to int

Community
  • 1
  • 1
weaz
  • 35
  • 8