[ Team LiB ] Previous Section Next Section

2.12 Validating a Date

NN 4, IE 4

2.12.1 Problem

You want to validate a date entered by the user in a form.

2.12.2 Solution

Use the checkDate( ) function shown in the Discussion. This function takes a text input element as its sole argument and expects the user to enter a date value in either mm/dd/yyyy or mm-dd-yyyy format. For example, the following validation function could be triggered from an onchange event handler of a date entry form field:

function validateDate(fld) {
    if (!checkDate(fld)) {
        // focus if validation fails
        fld.focus( );
        fld.select( );
    }
}

2.12.3 Discussion

Before you undertake validating a date entry, you must clearly understand your assumptions about the users, the purpose of the entry, and what you want to report back to the user for invalid entries. It's comparatively easy to test whether a field expecting a date in the mm/dd/yyyy format has numbers in the right places, but that typically is not good enough. After all, you don't want someone to get away with entering the 45th of June into a date field.

The checkDate( ) validation function in Example 2-3 assumes that users will enter dates in either mm/dd/yyyy or mm-dd-yyyy formats (in that order only), and that the validation must test for the entry of a true date. There is no boundary checking here, so practically any year is accepted. As a form-validation function, this one takes a reference to the text input element as the sole argument (just like the other validation routines in Recipe 8.2). Upon successful validation, the function returns true; otherwise, the user receives an alert message with some level of detail about the error, and the function returns false.

Example 2-3. Basic date validation function
function checkDate(fld) {
    var mo, day, yr;
    var entry = fld.value;
    var re = /\b\d{1,2}[\/-]\d{1,2}[\/-]\d{4}\b/;
    if (re.test(entry)) {
        var delimChar = (entry.indexOf("/") != -1) ? "/" : "-";
        var delim1 = entry.indexOf(delimChar);
        var delim2 = entry.lastIndexOf(delimChar);
        mo = parseInt(entry.substring(0, delim1), 10);
        day = parseInt(entry.substring(delim1+1, delim2), 10);
        yr = parseInt(entry.substring(delim2+1), 10);
        var testDate = new Date(yr, mo-1, day);
        alert(testDate)
        if (testDate.getDate( ) =  = day) {
            if (testDate.getMonth( ) + 1 =  = mo) {
                if (testDate.getFullYear( ) =  = yr) {
                    return true;
                } else {
                    alert("There is a problem with the year entry.");
                }
            } else {
                alert("There is a problem with the month entry.");
            }
        } else {
            alert("There is a problem with the date entry.");
        }
    } else {
        alert("Incorrect date format. Enter as mm/dd/yyyy.");
    }
    return false;
}

The basic operation of the checkDate( ) function is to first validate the format of the entry against a regular expression pattern. If the format is good, the function creates a date object from the entered numbers. Then the components of the resulting date object are compared against the initial entries. If there is any discrepancy between the two sets of numbers, a problem with the entry exists. It helps that the JavaScript Date object constructor accepts out-of-range dates and calculates the effective date from those wacky values. When the user enters 2/30/2003, the resulting date object is for 3/2/2003. Since the month and date no longer coincide with the entries, it's clear that the user entered an invalid date.

Although this function uses a regular expression only to verify the basic format of the date entry, it uses more rudimentary string parsing for the detailed analysis of the entry. This tactic is needed for backward-compatibility to overcome incomplete implementations of advanced regular expression handling in browsers prior to IE 5.5 for Windows. The checkDate() function works in all mainstream browsers from Version 4 onward.

In a high-volume data-entry environment, where productivity is measured in operators' keystrokes and time spent per form, you want to build more intelligence in a form. For example, you want to allow two-digit year entries, but code the validation routine so that it fills the field with the expanded version of the date because the backend database requires it. Moreover, the two-digit entry needs to be done in a maintenance-free way so that the range of allowable years for two-digit dates continues to modify itself as the years progress. Example 2-4 is an enhanced version of the checkDate( ) function with these upgrades shown in bold.

Example 2-4. Enhanced date validation function
function checkDate(fld) {
    var mo, day, yr;
    var entry = fld.value;
    var reLong = /\b\d{1,2}[\/-]\d{1,2}[\/-]\d{4}\b/;
    var reShort = /\b\d{1,2}[\/-]\d{1,2}[\/-]\d{2}\b/;
    var valid = (reLong.test(entry)) || (reShort.test(entry));
    if (valid) {
        var delimChar = (entry.indexOf("/") != -1) ? "/" : "-";
        var delim1 = entry.indexOf(delimChar);
        var delim2 = entry.lastIndexOf(delimChar);
        mo = parseInt(entry.substring(0, delim1), 10);
        day = parseInt(entry.substring(delim1+1, delim2), 10);
        yr = parseInt(entry.substring(delim2+1), 10);
        // handle two-digit year
        if (yr < 100) {
            var today = new Date( );
            // get current century floor (e.g., 2000)
            var currCent = parseInt(today.getFullYear( ) / 100) * 100;
            // two digits up to this year + 15 expands to current century
            var threshold = (today.getFullYear( ) + 15) - currCent;
            if (yr > threshold) {
                yr += currCent - 100;
            } else {
                yr += currCent;
            }
        }
        var testDate = new Date(yr, mo-1, day);
        if (testDate.getDate( ) =  = day) {
            if (testDate.getMonth( ) + 1 =  = mo) {
                if (testDate.getFullYear( ) =  = yr) {
                    // fill field with database-friendly format
                    fld.value = mo + "/" + day + "/" + yr;
                    return true;
                } else {
                    alert("There is a problem with the year entry.");
                }
            } else {
                alert("There is a problem with the month entry.");
            }
        } else {
            alert("There is a problem with the date entry.");
        }
    } else {
        alert("Incorrect date format. Enter as mm/dd/yyyy.");
    }
    return false;
}

You can short-circuit a lot of the potential problems for date validationóincluding the one involving cultural differences in date formatsóby providing either three text boxes (for month, day, and year in any order), or three select lists. Even the select list solution isn't free from validation, however, because you have to make sure that the user has chosen a valid combination (e.g., not something like June 31). You can get creative in this regard by using dynamic forms to repopulate the date list each time the user changes the month (see Recipe 8.13).

Date fields are generally important to the form in which they exist. Don't skimp on the thoroughness of validation for dates either on the client or on the server.

2.12.4 See Also

Recipe 8.2 for additional form field validation functions.

    [ Team LiB ] Previous Section Next Section