In input forms on web pages you often have to validate dates and users are always entering something off the wall. Simon Incutio came up with a script to parse the dates. I added some stuff and turned it into a control of sorts. It is sort of US centric with date entering but it does accept iso style entries.
// Date Box Control
//
// based on:
// 'Magic' date parsing, by Simon Willison (6th October 2003)
// http://simon.incutio.com/archive/2003/10/06/betterDateInput
//
// Notes
// To create a date box control, call the SetupDateBoxControl function.
// It should be passed an input element with type of text.
// This will first create a div after the input box.
// Then it will associate the div with the input element.
// The div will use the css classes: DateBoxControlMsg, DateBoxControlErrorMsg.
// Then the div is associated with the input element.
// Then the contents are validated so the div will get populated initially.
// The onchange event of the input element will invoke the validation function.
// The validation function populates the div.
// If a successfully parsed date, the div gets a nicely formatted date.
// If an unsuccessfully parse date, the div gets an error message.
//
// History
// 02/03/2005 - WSR : modified for use as datebox control
// 09/09/2005 - WSR : datebox is not required anymore (blank input is valid)
// : added style class to datebox itself
// hooks functionality up to given textbox
function SetupDateBoxControl( ctlDateBox )
{
// if a valid object was given
if (ctlDateBox)
{
// add div after control for messages
var divMessage = document.createElement('div');
divMessage.className = 'DateBoxControlMsg';
// if there is a next sibling
if (ctlDateBox.nextSibling)
{
// insert before next sibling
ctlDateBox.parentNode.insertBefore( divMessage, ctlDateBox.nextSibling );
}
// if there is not a next sibling
else
{
// append child to parent
ctlDateBox.parentNode.appendChild( divMessage );
}
// link message div to textbox for easy script access
ctlDateBox.message = divMessage;
// validate current contents
DateBoxControl_Validate( ctlDateBox );
// hook up event handlers
ctlDateBox.onchange = function () { DateBoxControl_Validate(this); };
}
}
// add indexOf function to Array type
// finds the index of the first occurence of item in the array, or -1 if not found
Array.prototype.indexOf = function(item) {
for (var i = 0; i < this.length; i++) {
if (this[i] == item) {
return i;
}
}
return -1;
};
// add filter function to Array type
// returns an array of items judged true by the passed in test function
Array.prototype.filter = function(test) {
var matches = [];
for (var i = 0; i < this.length; i++) {
if (test(this[i])) {
matches[matches.length] = this[i];
}
}
return matches;
};
// add right function to String type
// returns the rightmost x characters
String.prototype.right = function( intLength ) {
if (intLength >= this.length)
return this;
else
return this.substr( this.length - intLength, intLength );
};
// add trim function to String type
// trims leading and trailing whitespace
String.prototype.trim = function() { return this.replace(/^\s+|\s+$/, ''); };
// arrays for month and weekday names
var monthNames = "January February March April May June July August September October November December".split(" ");
var weekdayNames = "Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" ");
/* Takes a string, returns the index of the month matching that string, throws
an error if 0 or more than 1 matches
*/
function parseMonth(month) {
var matches = monthNames.filter(function(item) {
return new RegExp("^" + month, "i").test(item);
});
if (matches.length == 0) {
throw new Error("Invalid month string");
}
if (matches.length < 1) {
throw new Error("Ambiguous month");
}
return monthNames.indexOf(matches[0]);
}
/* Same as parseMonth but for days of the week */
function parseWeekday(weekday) {
var matches = weekdayNames.filter(function(item) {
return new RegExp("^" + weekday, "i").test(item);
});
if (matches.length == 0) {
throw new Error("Invalid day string");
}
if (matches.length < 1) {
throw new Error("Ambiguous weekday");
}
return weekdayNames.indexOf(matches[0]);
}
function DateInRange( yyyy, mm, dd )
{
// if month out of range
if ( mm < 0 || mm > 11 )
throw new Error('Invalid month value. Valid months values are 1 to 12');
// get last day in month
var d = (11 == mm) ? new Date(yyyy + 1, 0, 0) : new Date(yyyy, mm + 1, 0);
// if date out of range
if ( dd < 1 || dd > d.getDate() )
throw new Error('Invalid date value. Valid date values for ' + monthNames[mm] + ' are 1 to ' + d.getDate().toString());
return true;
}
/* Array of objects, each has 're', a regular expression and 'handler', a
function for creating a date from something that matches the regular
expression. Handlers may throw errors if string is unparseable.
*/
var dateParsePatterns = [
// Today
{ re: /^today/i,
handler: function() {
return new Date();
}
},
// Tomorrow
{ re: /^tomorrow/i,
handler: function() {
var d = new Date();
d.setDate(d.getDate() + 1);
return d;
}
},
// Yesterday
{ re: /^yesterday/i,
handler: function() {
var d = new Date();
d.setDate(d.getDate() - 1);
return d;
}
},
// mmddyyyy (American style)
{ re: /(\d{2})(\d{2})(\d{4})/,
handler: function(bits) {
var yyyy = parseInt(bits[3], 10);
var dd = parseInt(bits[2], 10);
var mm = parseInt(bits[1], 10) - 1;
if ( DateInRange( yyyy, mm, dd ) )
return new Date(yyyy, mm, dd);
}
},
// mmddyy (American style) short year
{ re: /(\d{2})(\d{2})(\d{2})/,
handler: function(bits) {
var d = new Date();
var yyyy = d.getFullYear() - (d.getFullYear() % 100) + parseInt(bits[3], 10);
var dd = parseInt(bits[2], 10);
var mm = parseInt(bits[1], 10) - 1;
if ( DateInRange(yyyy, mm, dd) )
return new Date(yyyy, mm, dd);
}
},
// 4th
{ re: /^(\d{1,2})(st|nd|rd|th)?$/i,
handler: function(bits) {
var d = new Date();
var yyyy = d.getFullYear();
var dd = parseInt(bits[1], 10);
var mm = d.getMonth();
if ( DateInRange( yyyy, mm, dd ) )
return new Date(yyyy, mm, dd);
}
},
// 4th Jan
{ re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+)$/i,
handler: function(bits) {
var d = new Date();
var yyyy = d.getFullYear();
var dd = parseInt(bits[1], 10);
var mm = parseMonth(bits[2]);
if ( DateInRange( yyyy, mm, dd ) )
return new Date(yyyy, mm, dd);
}
},
// 4th Jan 2003
{ re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+),? (\d{4})$/i,
handler: function(bits) {
var yyyy = parseInt(bits[3], 10);
var dd = parseInt(bits[1], 10);
var mm = parseMonth(bits[2]);
if ( DateInRange( yyyy, mm, dd ) )
return new Date(yyyy, mm, dd);
}
},
// Jan 4th
{ re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?$/i,
handler: function(bits) {
var d = new Date();
var yyyy = d.getFullYear();
var dd = parseInt(bits[2], 10);
var mm = parseMonth(bits[1]);
if ( DateInRange( yyyy, mm, dd ) )
return new Date(yyyy, mm, dd);
}
},
// Jan 4th 2003
{ re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,? (\d{4})$/i,
handler: function(bits) {
var yyyy = parseInt(bits[3], 10);
var dd = parseInt(bits[2], 10);
var mm = parseMonth(bits[1]);
if ( DateInRange( yyyy, mm, dd ) )
return new Date(yyyy, mm, dd);
}
},
// next Tuesday - this is suspect due to weird meaning of "next"
{ re: /^next (\w+)$/i,
handler: function