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.
|