Never been to DZone Snippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

JavaScript DateBoxControl (See related posts)

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


Click here to browse all 5140 code snippets

Related Posts