// 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(bits) { var d = new Date(); var day = d.getDay(); var newDay = parseWeekday(bits[1]); var addDays = newDay - day; if (newDay <= day) { addDays += 7; } d.setDate(d.getDate() + addDays); return d; } }, // last Tuesday { re: /^last (\w+)$/i, handler: function(bits) { var d = new Date(); var wd = d.getDay(); var nwd = parseWeekday(bits[1]); // determine the number of days to subtract to get last weekday // calculates 0 if weekdays are the same so we have to change this to 7 var addDays = (wd == nwd) ? -7 : (-1 * (wd + 7 - nwd)) % 7; // adjust date and return d.setDate(d.getDate() + addDays); return d; } }, // Tuesday { re: /^(\w+)$/i, handler: function(bits) { var d = new Date(); var wd = d.getDay(); var nwd = parseWeekday(bits[1]); // if same weekday, return date if (nwd == wd) return d; // if new weekday is before current weekday if (nwd < wd ) { // calculate last weekday d.setDate(d.getDate() + ((wd == nwd) ? -7 : (-1 * (wd + 7 - nwd)) % 7)); } // if new weekday is after current weekday else { // calculate next weekday d.setDate(d.getDate() + (nwd - wd)); } return d; } }, // mm/dd/yyyy (American style) { re: /(\d{1,2})\/(\d{1,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); } }, // mm/dd/yy (American style) short year { re: /(\d{1,2})\/(\d{1,2})\/(\d{1,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); } }, // mm/dd (American style) omitted year { re: /(\d{1,2})\/(\d{1,2})/, handler: function(bits) { var d = new Date(); var yyyy = d.getFullYear(); var dd = parseInt(bits[2], 10); var mm = parseInt(bits[1], 10) - 1; if ( DateInRange(yyyy, mm, dd) ) return new Date(yyyy, mm, dd); } }, // yyyy-mm-dd (ISO style) { re: /(\d{4})-(\d{1,2})-(\d{1,2})/, handler: function(bits) { var yyyy = parseInt(bits[1], 10); var dd = parseInt(bits[3], 10); var mm = parseInt(bits[2], 10) - 1; if ( DateInRange( yyyy, mm, dd ) ) return new Date(yyyy, mm, dd); } }, // yy-mm-dd (ISO style) short year { re: /(\d{1,2})-(\d{1,2})-(\d{1,2})/, handler: function(bits) { var d = new Date(); var yyyy = d.getFullYear() - (d.getFullYear() % 100) + parseInt(bits[1], 10); var dd = parseInt(bits[3], 10); var mm = parseInt(bits[2], 10) - 1; if ( DateInRange( yyyy, mm, dd ) ) return new Date(yyyy, mm, dd); } }, // mm-dd (ISO style) omitted year { re: /(\d{1,2})-(\d{1,2})/, handler: function(bits) { var d = new Date(); var yyyy = d.getFullYear(); var dd = parseInt(bits[2], 10); var mm = parseInt(bits[1], 10) - 1; if ( DateInRange( yyyy, mm, dd ) ) return new Date(yyyy, mm, dd); } } ]; // parses date string input function parseDateString( strDateInput ) { // cycle through date parse patterns for (var i = 0; i < dateParsePatterns.length; i++) { // get regular expression for this pattern var re = dateParsePatterns[i].re; // get handler function for this pattern var handler = dateParsePatterns[i].handler; // parse input using regular expression var bits = re.exec(strDateInput); // if there was a match if (bits) { alert( re ); // return the result of the handler function (which constitutes bits into a date) return handler(bits); } } // if no pattern matched - throw exception throw new Error("Invalid date string"); } // validates the input from datebox as a date function DateBoxControl_Validate( ctlDateBox ) { ctlDateBox.value = ctlDateBox.value.trim(); if ( ctlDateBox.value.length > 0 ) { try { // parse input to get date (error is raised if it can't be parsed) var dtValue = parseDateString(ctlDateBox.value.trim()); // assign date in mm/dd/yyyy format to textbox ctlDateBox.value = ('0' + (dtValue.getMonth() + 1).toString()).right(2) + '/' + ('0' + dtValue.getDate().toString()).right(2) + '/' + dtValue.getFullYear().toString(); // add more formal date to message div associated with textbox if (!ctlDateBox.message.firstChild) ctlDateBox.message.appendChild(document.createTextNode(dtValue.toDateString())); else ctlDateBox.message.firstChild.nodeValue = dtValue.toDateString(); // swith class name back to default so styling is changed ctlDateBox.message.className = 'DateBoxControlMsg'; ctlDateBox.className = 'DateBoxControl'; } catch (e) { // use error message from exception var strMessage = e.message; // give a nicer message to built-in javascript exception message if (strMessage.indexOf('is null or not an object') < -1) strMessage = 'Invalid date string'; // add error message to message div associated with textbox if (!ctlDateBox.message.firstChild) ctlDateBox.message.appendChild(document.createTextNode(strMessage)); else ctlDateBox.message.firstChild.nodeValue = strMessage; // switch class name to error so styling is changed ctlDateBox.message.className = 'DateBoxControlErrorMsg'; ctlDateBox.className = 'DateBoxControlError'; } } else { // clear message div associated with textbox if (!ctlDateBox.message.firstChild) ctlDateBox.message.appendChild(document.createTextNode('')); else ctlDateBox.message.firstChild.nodeValue = ''; // swith class name back to default so styling is changed ctlDateBox.message.className = 'DateBoxControlMsg'; ctlDateBox.className = 'DateBoxControl'; } }
You need to create an account or log in to post comments to this site.