/*
  DatePicker v5.4 by frequency-decoder.com

  Released under a creative commons Attribution-Share Alike 3.0 Unported license (http://creativecommons.org/licenses/by-sa/3.0/)

  Please credit frequency-decoder in any derivative work - thanks.

  You are free:

  * to Share — to copy, distribute and transmit the work
  * to Remix — to adapt the work

  Under the following conditions:

  * Attribution — You must attribute the work in the manner specified by the author or licensor (but not in any way that suggests that they endorse you or your use of the work).      
  * Share Alike — If you alter, transform, or build upon this work, you may distribute the resulting work only under the same, similar or a compatible license.
  */

  var datePickerController = (function datePickerController() {

      var debug               = false,
      isOpera             = Object.prototype.toString.call(window.opera) === "[object Opera]",
      isMoz               = /mozilla/.test( navigator.userAgent.toLowerCase() ) && !/(compatible|webkit)/.test( navigator.userAgent.toLowerCase() ),
      languageInfo        = parseUILanguage(),
      datePickers         = {},
      uniqueId            = 0,
      weeksInYearCache    = {},
      localeImport        = false,
      nbsp                = String.fromCharCode(160),
      describedBy         = "",
      nodrag              = false,            
      buttonTabIndex      = true,
      returnLocaleDate    = false,
      mouseWheel          = true,              
      cellFormat          = "d-sp-F-sp-Y",
      titleFormat         = "F-sp-d-cc-sp-Y",
      formatParts         = isOpera ? ["placeholder"] : ["placeholder", "sp-F-sp-Y"],
      dividors            = ["dt","sl","ds","cc","sp"],
      dvParts             = "dt|sl|ds|cc|sp",
      dParts              = "d|j",
      mParts              = "m|n|M|F",            
      yParts              = "Y|y",                        
      kbEvent             = false,
      bespokeTitles       = {},
      finalOpacity        = 100,
      validFmtRegExp      = /^((sp|dt|sl|ds|cc)|([d|D|l|j|N|w|S|W|M|F|m|n|t|Y|y]))(-((sp|dt|sl|ds|cc)|([d|D|l|j|N|w|S|W|M|F|m|n|t|Y|y])))*$/,
      rangeRegExp         = /^((\d\d\d\d)(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01]))$/,
      wcDateRegExp        = /^(((\d\d\d\d)|(\*\*\*\*))((0[1-9]|1[012])|(\*\*))(0[1-9]|[12][0-9]|3[01]))$/;                                      

      (function() {
          var scriptFiles = document.getElementsByTagName('script'),                    
          scriptInner = String(scriptFiles[scriptFiles.length - 1].innerHTML).replace(/[\n\r\s\t]+/g, " ").replace(/^\s+/, "").replace(/\s+$/, ""),                    
          json        = parseJSON(scriptInner);                

          if(typeof json === "object" && !("err" in json)) {                          
            affectJSON(json);
          };

          if(typeof(fdLocale) != "object") {
            var head   = document.getElementsByTagName("head")[0] || document.documentElement,
            loc    = scriptFiles[scriptFiles.length - 1].src.substr(0, scriptFiles[scriptFiles.length - 1].src.lastIndexOf("/")) + "/lang/",
            script;

            for(var i = 0; i < languageInfo.length; i++) {                                 
              script = document.createElement('script');                                               
              script.type = "text/javascript";                         
              script.src  = loc + languageInfo[i] + ".js"; 
              script.charSet = "utf-8";

              /*@cc_on
              /*@if(@_win32)
              var bases = document.getElementsByTagName('base');
              if (bases.length && bases[0].childNodes.length) {
              bases[0].appendChild(script);
              } else {
              document.getElementsByTagName('head')[0].appendChild(script);
              };
              bases = null;
              @else @*/
              head.appendChild(script);
              /*@end
              @*/    
            };
            script = null;                      
          } else {
            returnLocaleDate = true;
          };                              
      })();

      function parseUILanguage() {                                 
        var languageTag = document.getElementsByTagName('html')[0].getAttribute('lang') || document.getElementsByTagName('html')[0].getAttribute('xml:lang');

        if(!languageTag) {
          languageTag = "en";
        } else {
          languageTag = languageTag.toLowerCase();
        };

        return languageTag.search(/^([a-z]{2,3})-([a-z]{2})$/) != -1 ? [languageTag.match(/^([a-z]{2,3})-([a-z]{2})$/)[1], languageTag] : [languageTag];                       
      };

      function affectJSON(json) {
        if(typeof json !== "object") { return; };
        for(key in json) {
          value = json[key];                                                                
          switch(key.toLowerCase()) { 
            case "lang":
            if(value.search(/^[a-z]{2,3}(-([a-z]{2}))?$/i) != -1) {                                                
              languageInfo = [value.toLowerCase()];                                                   
              returnLocaleDate = true;
            };
            break;                                                               
            case "nodrag":
              nodrag = !!value;
              break;                                
            case "buttontabindex":
              buttonTabIndex = !!value;
              break;
            case "mousewheel":
              mouseWheel = !!value;
              break;  
            case "cellformat":
            if(typeof value == "string" && value.match(validFmtRegExp)) {
              parseCellFormat(value);
            };
            break;
            case "titleformat":
            if(typeof value == "string" && value.match(validFmtRegExp)) {
              titleFormat = value;
            }; 
            break;
            case "describedby":
            if(typeof value == "string") {
              describedBy = value;
            };
            break; 
            case "finalopacity":
            if(typeof value == 'number' && (+value > 20 && +value <= 100)) {
              finalOpacity = parseInt(value, 10);
            }; 
            break; 
            case "bespoketitles":
            bespokeTitles = {};
            for(var dt in value) {
              bespokeTitles[dt] = value[dt];
            };                                                                                                                                             
          };          
        };        
      };                  

      function parseCellFormat(value) {                  
        if(isOpera) { 
          // Don't use hidden text for opera due to focus outline problems               
          formatParts = ["placeholder"];
          cellFormat  = "j-sp-F-sp-Y";  
          return;
        };   

        // I'm sure this could be done with a regExp and a split in one line... seriously...
        var parts       = value.split("-"),
        fullParts   = [],
        tmpParts    = [],
        part;                              

        for(var pt = 0; pt < parts.length; pt++) {
          part = parts[pt];                         
          if(part == "j" || part == "d") { 
            if(tmpParts.length) {
              fullParts.push(tmpParts.join("-")); 
              tmpParts = [];
            };
            fullParts.push("placeholder");   
          } else { 
            tmpParts.push(part);
          };                                             
        };                  

        if(tmpParts.length) {
          fullParts.push(tmpParts.join("-"));                                         
        };

        if(!fullParts.length || fullParts.length > 3) {
          formatParts = ["placeholder", "sp-F-sp-Y"];
          cellFormat = "j-sp-F-sp-Y"; 
          return;
        };                

        formatParts = fullParts;
        cellFormat  = value;               
      };

      function pad(value, length) { 
        length = length || 2; 
        return "0000".substr(0,length - Math.min(String(value).length, length)) + value; 
      };

      function addEvent(obj, type, fn) { 
        try {                 
          if( obj.attachEvent ) {
            obj["e"+type+fn] = fn;
            obj[type+fn] = function(){obj["e"+type+fn]( window.event );};
            obj.attachEvent( "on"+type, obj[type+fn] );
          } else {
            obj.addEventListener( type, fn, true );
          };
        } catch(err) {}
      };

      function removeEvent(obj, type, fn) {
        try {
          if( obj.detachEvent ) {
            obj.detachEvent( "on"+type, obj[type+fn] );
            obj[type+fn] = null;
          } else {
            obj.removeEventListener( type, fn, true );
          };
        } catch(err) {};
      };   

      function stopEvent(e) {
        e = e || document.parentWindow.event;
        if(e.stopPropagation) {
          e.stopPropagation();
          e.preventDefault();
        };
        /*@cc_on
        @if(@_win32)
        e.cancelBubble = true;
        e.returnValue = false;
        @end
        @*/
        return false;
      };

      function parseJSON(str) {
        // Check we have a String
        if(typeof str !== 'string' || str == "") { return {}; };                 
        try {
          // Does a JSON (native or not) Object exist                              
          if(typeof JSON === "object" && JSON.parse) {                                              
            return window.JSON.parse(str);  
            // Genious code taken from: http://kentbrewster.com/badges/                                                      
          } else if(/lang|buttontabindex|mousewheel|cellformat|titleformat|nodrag|describedby/.test(str.toLowerCase())) {                                               
            var f = Function(['var document,top,self,window,parent,Number,Date,Object,Function,',
                'Array,String,Math,RegExp,Image,ActiveXObject;',
                'return (' , str.replace(/<\!--.+-->/gim,'').replace(/\bfunction\b/g,'function­') , ');'].join(''));
            return f();                          
          };
        } catch (e) { };

        if(debug) {
          throw "Could not parse the JSON object";
        };

        return {"err":"Could not parse the JSON object"};                                            
      };        

      function setARIARole(element, role) {
        if(element && element.tagName) {
          element.setAttribute("role", role);
        };
      };

      function setARIAProperty(element, property, value) {
        if(element && element.tagName) {
          element.setAttribute("aria-" + property, value);
        };	
      };

      // The datePicker object itself 
      function datePicker(options) {                                      
        this.dateSet             = null;                 
        this.timerSet            = false;
        this.visible             = false;
        this.fadeTimer           = null;
        this.timer               = null;
        this.yearInc             = 0;
        this.monthInc            = 0;
        this.dayInc              = 0;
        this.mx                  = 0;
        this.my                  = 0;
        this.x                   = 0;
        this.y                   = 0; 
        this.created             = false;
        this.disabled            = false;
        this.opacity             = 0; 
        this.opacityTo           = 99;
        this.inUpdate            = false;                              
        this.kbEventsAdded       = false;
        this.fullCreate          = false;
        this.selectedTD          = null;
        this.cursorTD            = null;
        this.cursorDate          = options.cursorDate ? options.cursorDate : "",       
        this.date                = options.cursorDate ? new Date(+options.cursorDate.substr(0,4), +options.cursorDate.substr(4,2) - 1, +options.cursorDate.substr(6,2)) : new Date();
        this.defaults            = {};
        this.dynDisabledDates    = {};
        this.firstDayOfWeek      = localeImport.firstDayOfWeek; 
        this.interval            = new Date();
        this.clickActivated      = false;
        this.noFocus             = true;
        this.kbEvent             = false; 
        this.disabledDates       = false;
        this.enabledDates        = false;
        this.delayedUpdate       = false;  
        this.bespokeTitles       = {};             

        for(var thing in options) {
          if(thing.search(/callbacks|formElements|formatMasks/) != -1) continue;
          this[thing] = options[thing];                 
        };

        /*@cc_on
        @if(@_win32)                   
        this.iePopUp             = null;
        this.isIE7               = false;                 
        @end
        @*/

        /*@cc_on
        @if(@_jscript_version <= 5.7)
        this.isIE7               = document.documentElement && typeof document.documentElement.style.maxHeight != "undefined";
        @end
        @*/

        for(var i = 0, prop; prop = ["callbacks", "formElements", "formatMasks"][i]; i++) { 
          this[prop] = {};                        
          for(var thing in options[prop]) {                                
            this[prop][thing] = options[prop][thing];                 
          };
        };

        // Adjust time to stop daylight savings madness on windows
        this.date.setHours(5);              

        this.changeHandler = function() {                        
          o.setDateFromInput();  
          o.callback("dateset", o.createCbArgObj());                                                                                 
        };
        this.createCbArgObj = function() {                        
          return this.dateSet ? {"id":this.id,"date":this.dateSet,"dd":pad(this.date.getDate()),"mm":pad(this.date.getMonth() + 1),"yyyy":this.date.getFullYear()} : {"id":this.id,"date":null,"dd":null,"mm":null,"yyyy":null};                         
        };
        this.getScrollOffsets = function() {                         
          if(typeof(window.pageYOffset) == 'number') {
            //Netscape compliant
            return [window.pageXOffset, window.pageYOffset];                                
          } else if(document.body && (document.body.scrollLeft || document.body.scrollTop)) {
            //DOM compliant
            return [document.body.scrollLeft, document.body.scrollTop];                                
          } else if(document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop)) {
            //IE6 standards compliant mode
            return [document.documentElement.scrollLeft, document.documentElement.scrollTop];
          };
          return [0,0];
        };
        this.reposition = function() {
          if(!o.created || o.staticPos) { return; };

          o.div.style.visibility = "hidden";
          o.div.style.left = o.div.style.top = "0px";                           
          o.div.style.display = "block";

          var osh         = o.div.offsetHeight,
          osw         = o.div.offsetWidth,
          elem        = document.getElementById('fd-but-' + o.id),
          pos         = o.truePosition(elem),
          trueBody    = (document.compatMode && document.compatMode!="BackCompat") ? document.documentElement : document.body,
          sOffsets    = o.getScrollOffsets(),
          scrollTop   = sOffsets[1], 
          scrollLeft  = sOffsets[0],
          fitsBottom  = parseInt(trueBody.clientHeight+scrollTop) > parseInt(osh+pos[1]+elem.offsetHeight+2),
          fitsTop     = parseInt(pos[1]-(osh+elem.offsetHeight+2)) > parseInt(scrollTop); 

          o.div.style.visibility = "visible";

          o.div.style.left = Number(parseInt(trueBody.clientWidth+scrollLeft) < parseInt(osw+pos[0]) ? Math.abs(parseInt((trueBody.clientWidth+scrollLeft) - osw)) : pos[0]) + "px";
          o.div.style.top  = (fitsBottom || !fitsTop) ? Math.abs(parseInt(pos[1] + elem.offsetHeight + 2)) + "px" : Math.abs(parseInt(pos[1] - (osh + 2))) + "px";
          /*@cc_on
          @if(@_jscript_version <= 5.7)
          if(o.isIE7) return;
          o.iePopUp.style.top    = o.div.style.top;
          o.iePopUp.style.left   = o.div.style.left;
          o.iePopUp.style.width  = osw + "px";
          o.iePopUp.style.height = (osh - 2) + "px";
          @end
          @*/
        };
        this.removeOldFocus = function() {
          var td = document.getElementById(o.id + "-date-picker-hover");
          if(td) {                                        
            try { 
              td.setAttribute(!/*@cc_on!@*/false ? "tabIndex" : "tabindex", "-1");
              td.tabIndex = -1;                                          
              td.className = td.className.replace(/date-picker-hover/, "");                                         
              td.id = ""; 
              td.onblur  = null; 
              td.onfocus = null;                                                                             
            } catch(err) {};
          };
        }; 
        this.addAccessibleDate = function() {
          var td   = document.getElementById(o.id + "-date-picker-hover");                            

          if(td && !(td.getElementsByTagName("span").length)) {                                                          
            var ymd = td.className.match(/cd-([\d]{4})([\d]{2})([\d]{2})/),
            noS = (td.className.search(/date-picker-unused|out-of-range|day-disabled|no-selection|not-selectable/) != -1),
            spn  = document.createElement('span'),
            spnC;                                        

            spn.className       = "fd-screen-reader";;

            while(td.firstChild) td.removeChild(td.firstChild);

            if(noS) {
              spnC = spn.cloneNode(false);
              spnC.appendChild(document.createTextNode(getTitleTranslation(13)));
              td.appendChild(spnC);
            };

            for(var pt = 0, part; part = formatParts[pt]; pt++) {
              if(part == "placeholder") {
                td.appendChild(document.createTextNode(+ymd[3]));
              } else {
                spnC = spn.cloneNode(false);
                spnC.appendChild(document.createTextNode(printFormattedDate(new Date(ymd[1], +ymd[2]-1, ymd[3]), part, true)));
                td.appendChild(spnC);
              };                                                
            };
          };
        };
        this.setNewFocus = function() {                                                                                             
          var td = document.getElementById(o.id + "-date-picker-hover");
          if(td) {
            try {                                             
              td.setAttribute(!/*@cc_on!@*/false ? "tabIndex" : "tabindex", "0");                                
              td.tabIndex  = 0;  

              td.className = td.className.replace(/date-picker-hover/, "") + " date-picker-hover"; 
              if(!this.clickActivated) {                                                
                td.onblur    = o.onblur;  
                td.onfocus   = o.onfocus;                                   
              };

              if(!isOpera && !this.clickActivated) o.addAccessibleDate();

              if(!this.noFocus && !this.clickActivated) {                                                                                                                                                   
                setTimeout(function() { try { td.focus(); } catch(err) {}; }, 0);
              };                                         
            } catch(err) { };
          };
        };
        this.setCursorDate = function(yyyymmdd) {                        
          if(String(yyyymmdd).search(/^([0-9]{8})$/) != -1) {
            this.date = new Date(+yyyymmdd.substr(0,4), +yyyymmdd.substr(4,2) - 1, +yyyymmdd.substr(6,2));
            this.cursorDate = yyyymmdd;

            if(this.staticPos) {                                         
              this.updateTable();
            };                                                                                                  
          };
        };                  
        this.updateTable = function(noCallback) {  
          if(!o || o.inUpdate || !o.created) return;

          o.inUpdate = true;                                         
          o.removeOldFocus();

          if(o.timerSet && !o.delayedUpdate) {
            if(o.monthInc) {
              var n = o.date.getDate(),
              d = new Date(o.date);                         

              d.setDate(2);                                               
              d.setMonth(d.getMonth() + o.monthInc * 1);
              d.setDate(Math.min(n, daysInMonth(d.getMonth(),d.getFullYear())));

              o.date = new Date(d);
            } else {                                 
              o.date.setDate(Math.min(o.date.getDate()+o.dayInc, daysInMonth(o.date.getMonth()+o.monthInc,o.date.getFullYear()+o.yearInc)));
              o.date.setMonth(o.date.getMonth() + o.monthInc);                                        
              o.date.setFullYear(o.date.getFullYear() + o.yearInc);
            };                                       
          }; 

          o.outOfRange();
          if(!o.noToday) { o.disableTodayButton(); };
          o.showHideButtons(o.date);

          var cd = o.date.getDate(),
          cm = o.date.getMonth(),
          cy = o.date.getFullYear(),
          cursorDate = (String(cy) + pad(cm+1) + pad(cd)),
          tmpDate    = new Date(cy, cm, 1);                      

          tmpDate.setHours(5);

          var dt, cName, td, i, currentDate, cellAdded, col, currentStub, abbr, bespokeRenderClass, spnC, dateSetD,
          weekDayC            = ( tmpDate.getDay() + 6 ) % 7,                
          firstColIndex       = (((weekDayC - o.firstDayOfWeek) + 7 ) % 7) - 1,
          dpm                 = daysInMonth(cm, cy),
          today               = new Date(),                        
          stub                = String(tmpDate.getFullYear()) + pad(tmpDate.getMonth()+1),
          cellAdded           = [4,4,4,4,4,4],                                                                   
          lm                  = new Date(cy, cm-1, 1),
          nm                  = new Date(cy, cm+1, 1),                          
          daySub              = daysInMonth(lm.getMonth(), lm.getFullYear()),                
          stubN               = String(nm.getFullYear()) + pad(nm.getMonth()+1),
          stubP               = String(lm.getFullYear()) + pad(lm.getMonth()+1),                
          weekDayN            = (nm.getDay() + 6) % 7,
          weekDayP            = (lm.getDay() + 6) % 7,                                       
          today               = today.getFullYear() + pad(today.getMonth()+1) + pad(today.getDate()),
          spn                 = document.createElement('span');                        

          o.firstDateShown    = !o.constrainSelection && o.fillGrid && (0 - firstColIndex < 1) ? String(stubP) + (daySub + (0 - firstColIndex)) : stub + "01";            
          o.lastDateShown     = !o.constrainSelection && o.fillGrid ? stubN + pad(41 - firstColIndex - dpm) : stub + String(dpm);
          o.currentYYYYMM     = stub;                    

          bespokeRenderClass  = o.callback("redraw", {id:o.id, dd:pad(cd), mm:pad(cm+1), yyyy:cy, firstDateDisplayed:o.firstDateShown, lastDateDisplayed:o.lastDateShown}) || {};                                            
          dts                 = o.getDates(cy, cm+1);                               

          o.checkSelectedDate();

          dateSetD            = (o.dateSet != null) ? o.dateSet.getFullYear() + pad(o.dateSet.getMonth()+1) + pad(o.dateSet.getDate()) : false;
          spn.className       = "fd-screen-reader";

          if(this.selectedTD != null) {
            setARIAProperty(this.selectedTD, "selected", false);
            this.selectedTD = null;
          };

          for(var curr = 0; curr < 42; curr++) {
            row  = Math.floor(curr / 7);                         
            td   = o.tds[curr];
            spnC = spn.cloneNode(false); 

            while(td.firstChild) td.removeChild(td.firstChild);

            if((curr > firstColIndex && curr <= (firstColIndex + dpm)) || o.fillGrid) {
              currentStub     = stub;
              weekDay         = weekDayC;                                
              dt              = curr - firstColIndex;
              cName           = [];                                         
              selectable      = true;                                     

              if(dt < 1) {
                dt              = daySub + dt;
                currentStub     = stubP;
                weekDay         = weekDayP;                                        
                selectable      = !o.constrainSelection;
                cName.push("month-out");                                                  
              } else if(dt > dpm) {
                dt -= dpm;
                currentStub     = stubN;
                weekDay         = weekDayN;                                        
                selectable      = !o.constrainSelection; 
                cName.push("month-out");                                                                                           
              }; 

              weekDay = ( weekDay + dt + 6 ) % 7;

              cName.push("day-" + localeDefaults.dayAbbrs[weekDay].toLowerCase());

              currentDate = currentStub + String(dt < 10 ? "0" : "") + dt;                            

              if(o.rangeLow && +currentDate < +o.rangeLow || o.rangeHigh && +currentDate > +o.rangeHigh) {                                          
                td.className = "out-of-range";  
                td.title = ""; 
                td.appendChild(document.createTextNode(dt));                                             
                if(o.showWeeks) { cellAdded[row] = Math.min(cellAdded[row], 2); };                                                                                                                                               
              } else {  
                if(selectable) {                                                                                                        
                  td.title = titleFormat ? printFormattedDate(new Date(+String(currentStub).substr(0,4), +String(currentStub).substr(4, 2) - 1, +dt), titleFormat, true) : "";                                                                                                      
                  cName.push("cd-" + currentDate + " yyyymm-" + currentStub + " mmdd-" + currentStub.substr(4,2) + pad(dt));
                } else {  
                  td.title = titleFormat ? getTitleTranslation(13) + " " + printFormattedDate(new Date(+String(currentStub).substr(0,4), +String(currentStub).substr(4, 2) - 1, +dt), titleFormat, true) : "";                                                                       
                  cName.push("yyyymm-" + currentStub + " mmdd-" + currentStub.substr(4,2) + pad(dt) + " not-selectable");
                };                                                                                                                                             

                if(currentDate == today) { cName.push("date-picker-today"); };

                if(dateSetD == currentDate) { 
                  cName.push("date-picker-selected-date"); 
                  setARIAProperty(td, "selected", "true");
                  this.selectedTD = td;
                };

                if(o.disabledDays[weekDay] || dts[currentDate] == 0) { cName.push("day-disabled"); if(titleFormat && selectable) { td.title = getTitleTranslation(13) + " " + td.title; }; }

                if(currentDate in bespokeRenderClass) { cName.push(bespokeRenderClass[currentDate]); }

                if(o.highlightDays[weekDay]) { cName.push("date-picker-highlight"); };

                if(cursorDate == currentDate) { 
                  td.id = o.id + "-date-picker-hover";                                                                                                                                                                 
                };      

                td.appendChild(document.createTextNode(dt));
                td.className = cName.join(" ");

                if(o.showWeeks) {                                                         
                  cellAdded[row] = Math.min(cName[0] == "month-out" ? 3 : 1, cellAdded[row]);                                                          
                }; 
              };                       
            } else {
              td.className = "date-picker-unused";                                                                                                                    
              td.appendChild(document.createTextNode(nbsp));
              td.title = "";                                                                              
            };                                                  

            if(o.showWeeks && curr - (row * 7) == 6) { 
              while(o.wkThs[row].firstChild) o.wkThs[row].removeChild(o.wkThs[row].firstChild);                                         
              o.wkThs[row].appendChild(document.createTextNode(cellAdded[row] == 4 && !o.fillGrid ? nbsp : getWeekNumber(cy, cm, curr - firstColIndex - 6)));
              o.wkThs[row].className = "date-picker-week-header" + (["",""," out-of-range"," month-out",""][cellAdded[row]]);                                          
            };                                
          };            

          var span = o.titleBar.getElementsByTagName("span");
          while(span[0].firstChild) span[0].removeChild(span[0].firstChild);
          while(span[1].firstChild) span[1].removeChild(span[1].firstChild);
          span[0].appendChild(document.createTextNode(getMonthTranslation(cm, false) + nbsp));
          span[1].appendChild(document.createTextNode(cy));

          if(o.timerSet) {
            o.timerInc = 50 + Math.round(((o.timerInc - 50) / 1.8));
            o.timer = window.setTimeout(o.updateTable, o.timerInc);
          };

          o.inUpdate = o.delayedUpdate = false; 
          o.setNewFocus();                         
        };

        this.destroy = function() {

          if(document.getElementById("fd-but-" + this.id)) {
            document.getElementById("fd-but-" + this.id).parentNode.removeChild(document.getElementById("fd-but-" + this.id));        
          };

          if(!this.created) { return; };

          // Cleanup for Internet Explorer
          removeEvent(this.table, "mousedown", o.onmousedown);  
          removeEvent(this.table, "mouseover", o.onmouseover);
          removeEvent(this.table, "mouseout", o.onmouseout);
          removeEvent(document, "mousedown", o.onmousedown);
          removeEvent(document, "mouseup",   o.clearTimer);

          if (window.addEventListener && !window.devicePixelRatio) {
            try {
              window.removeEventListener('DOMMouseScroll', this.onmousewheel, false);
            } catch(err) {};                                 
          } else {
            removeEvent(document, "mousewheel", this.onmousewheel);
            removeEvent(window,   "mousewheel", this.onmousewheel);
          }; 
          o.removeOnFocusEvents();
          clearTimeout(o.fadeTimer);
          clearTimeout(o.timer);

          /*@cc_on
          @if(@_jscript_version <= 5.7)                         
          if(!o.staticPos && !o.isIE7) {
          try {
          o.iePopUp.parentNode.removeChild(o.iePopUp);
          o.iePopUp = null;
          } catch(err) {};
          };
          @end
          @*/                         

          if(this.div && this.div.parentNode) {
            this.div.parentNode.removeChild(this.div);
          };

          o = null;
        };
        this.resizeInlineDiv = function()  {                        
          o.div.style.width = o.table.offsetWidth + "px";
          o.div.style.height = o.table.offsetHeight + "px";
        };
        this.create = function() {

          if(document.getElementById("fd-" + this.id)) return;

          this.noFocus = true; 

          function createTH(details) {
            var th = document.createElement('th');
            if(details.thClassName) th.className = details.thClassName;
            if(details.colspan) {
              /*@cc_on
              /*@if (@_win32)
              th.setAttribute('colSpan',details.colspan);
              @else @*/
              th.setAttribute('colspan',details.colspan);
              /*@end
              @*/
            };
            /*@cc_on
            /*@if (@_win32)
            th.unselectable = "on";
            /*@end@*/
            return th;
          };
          function createThAndButton(tr, obj) {
            for(var i = 0, details; details = obj[i]; i++) {
              var th = createTH(details);
              tr.appendChild(th);
              var but = document.createElement('span');
              but.className = details.className;
              but.id = o.id + details.id;
              but.appendChild(document.createTextNode(details.text || o.nbsp));
              but.title = details.title || "";                                          
              /*@cc_on
              /*@if(@_win32)
              th.unselectable = but.unselectable = "on";
              /*@end@*/
              th.appendChild(but);
            };
          };  

          this.div                     = document.createElement('div');
          this.div.id                  = "fd-" + this.id;
          this.div.className           = "datePicker";  

          // Attempt to hide the div from screen readers during content creation
          this.div.style.visibility = "hidden";
          this.div.style.display = "none";

          // Set the ARIA describedby property if the required block available
          if(this.describedBy && document.getElementById(this.describedBy)) {
            setARIAProperty(this.div, "describedby", this.describedBy);
          };

          // Set the ARIA labelled property if the required label available
          if(this.labelledBy) {
            setARIAProperty(this.div, "labelledby", this.labelledBy.id);
          };

          var tr, row, col, tableHead, tableBody, tableFoot;

          this.table             = document.createElement('table');
          this.table.className   = "datePickerTable";                         
          this.table.onmouseover = this.onmouseover;
          this.table.onmouseout  = this.onmouseout;
          this.table.onclick     = this.onclick;

          if(this.staticPos) {
            this.table.onmousedown  = this.onmousedown;
          };

          this.div.appendChild(this.table);   

          var dragEnabledCN = !this.dragDisabled ? " drag-enabled" : "";

          if(!this.staticPos) {
            this.div.style.visibility = "hidden";
            this.div.className += dragEnabledCN;
            document.getElementsByTagName('body')[0].appendChild(this.div);

            /*@cc_on
            @if(@_jscript_version <= 5.7) 

            if(!this.isIE7) {                                         
            this.iePopUp = document.createElement('iframe');
            this.iePopUp.src = "javascript:'<html></html>';";
            this.iePopUp.setAttribute('className','iehack');
            // Remove iFrame from tabIndex                                        
            this.iePopUp.setAttribute("tabIndex", -1);  			                
            // Hide it from ARIA aware technologies
            setARIARole(this.iePopUp, "presentation");
            setARIAProperty(this.iePopUp, "hidden", "true"); 			                
            this.iePopUp.scrolling = "no";
            this.iePopUp.frameBorder = "0";
            this.iePopUp.name = this.iePopUp.id = this.id + "-iePopUpHack";
            document.body.appendChild(this.iePopUp);                                        
            };

            @end
            @*/

            // Aria "hidden" property for non active popup datepickers
            setARIAProperty(this.div, "hidden", "true");
          } else {
            elem = document.getElementById(this.positioned ? this.positioned : this.id);
            if(!elem) {
              this.div = null;
              if(debug) throw this.positioned ? "Could not locate a datePickers associated parent element with an id:" + this.positioned : "Could not locate a datePickers associated input with an id:" + this.id;
              return;
            };

            this.div.className += " static-datepicker";                          

            if(this.positioned) {
              elem.appendChild(this.div);
            } else {
              elem.parentNode.insertBefore(this.div, elem.nextSibling);
            };

            if(this.hideInput) {
              for(var elemID in this.formElements) {
                elem = document.getElementById(elemID);
                if(elem) {
                  elem.className += " fd-hidden-input";
                };        
              };                                        
            };                                                                  

            setTimeout(this.resizeInlineDiv, 300);                               
          };                          

          // ARIA Grid role
          setARIARole(this.div, "grid");

          if(this.statusFormat) {
            tableFoot = document.createElement('tfoot');
            this.table.appendChild(tableFoot);
            tr = document.createElement('tr');
            tr.className = "date-picker-tfoot";
            tableFoot.appendChild(tr);                                
            this.statusBar = createTH({thClassName:"date-picker-statusbar" + dragEnabledCN, colspan:this.showWeeks ? 8 : 7});
            tr.appendChild(this.statusBar); 
            this.updateStatus(); 
          };

          tableHead = document.createElement('thead');
          this.table.appendChild(tableHead);

          tr  = document.createElement('tr');
          setARIARole(tr, "presentation");

          tableHead.appendChild(tr);

          // Title Bar
          this.titleBar = createTH({thClassName:"date-picker-title" + dragEnabledCN, colspan:this.showWeeks ? 8 : 7});

          tr.appendChild(this.titleBar);
          tr = null;

          var span = document.createElement('span');
          span.appendChild(document.createTextNode(nbsp));
          span.className = "month-display" + dragEnabledCN; 
          this.titleBar.appendChild(span);

          span = document.createElement('span');
          span.appendChild(document.createTextNode(nbsp));
          span.className = "year-display" + dragEnabledCN; 
          this.titleBar.appendChild(span);

          span = null;

          tr  = document.createElement('tr');
          setARIARole(tr, "presentation");
          tableHead.appendChild(tr);

          createThAndButton(tr, [
              {className:"prev-but prev-year",  id:"-prev-year-but", text:"\u00AB", title:getTitleTranslation(2) },
              {className:"prev-but prev-month", id:"-prev-month-but", text:"\u2039", title:getTitleTranslation(0) },
              {colspan:this.showWeeks ? 4 : 3, className:"today-but", id:"-today-but", text:getTitleTranslation(4)},
              {className:"next-but next-month", id:"-next-month-but", text:"\u203A", title:getTitleTranslation(1)},
              {className:"next-but next-year",  id:"-next-year-but", text:"\u00BB", title:getTitleTranslation(3) }
            ]);

          tableBody = document.createElement('tbody');
          this.table.appendChild(tableBody);

          var colspanTotal = this.showWeeks ? 8 : 7,
          colOffset    = this.showWeeks ? 0 : -1,
          but, abbr;   

          for(var rows = 0; rows < 7; rows++) {
            row = document.createElement('tr');

            if(rows != 0) {
              // ARIA Grid role
              setARIARole(row, "row");
              tableBody.appendChild(row);   
            } else {
              tableHead.appendChild(row);
            };

            for(var cols = 0; cols < colspanTotal; cols++) {                                                                                
              if(rows === 0 || (this.showWeeks && cols === 0)) {
                col = document.createElement('th');                                                                                              
              } else {
                col = document.createElement('td');                                                                                           
                setARIAProperty(col, "describedby", this.id + "-col-" + cols + (this.showWeeks ? " " + this.id + "-row-" + rows : ""));
                setARIAProperty(col, "selected", "false");                                                 
              };

              /*@cc_on@*/
              /*@if(@_win32)
              col.unselectable = "on";
              /*@end@*/  

              row.appendChild(col);
              if((this.showWeeks && cols > 0 && rows > 0) || (!this.showWeeks && rows > 0)) {                                                
                setARIARole(col, "gridcell"); 
              } else {
                if(rows === 0 && cols > colOffset) {
                  col.className = "date-picker-day-header";
                  col.scope = "col";
                  setARIARole(col, "columnheader"); 
                  col.id = this.id + "-col-" + cols;                                          
                } else {
                  col.className = "date-picker-week-header";
                  col.scope = "row";
                  setARIARole(col, "rowheader");
                  col.id = this.id + "-row-" + rows;
                };
              };
            };
          };

          col = row = null; 

          this.ths = this.table.getElementsByTagName('thead')[0].getElementsByTagName('tr')[2].getElementsByTagName('th');
          for (var y = 0; y < colspanTotal; y++) {
            if(y == 0 && this.showWeeks) {
              this.ths[y].appendChild(document.createTextNode(getTitleTranslation(6)));
              this.ths[y].title = getTitleTranslation(8);
              continue;
            };

            if(y > (this.showWeeks ? 0 : -1)) {
              but = document.createElement("span");
              but.className = "fd-day-header";                                        
              /*@cc_on@*/
              /*@if(@_win32)
              but.unselectable = "on";
              /*@end@*/
              this.ths[y].appendChild(but);
            };
          };

          but = null; 

          this.trs             = this.table.getElementsByTagName('tbody')[0].getElementsByTagName('tr');
          this.tds             = this.table.getElementsByTagName('tbody')[0].getElementsByTagName('td');
          this.butPrevYear     = document.getElementById(this.id + "-prev-year-but");
          this.butPrevMonth    = document.getElementById(this.id + "-prev-month-but");
          this.butToday        = document.getElementById(this.id + "-today-but");
          this.butNextYear     = document.getElementById(this.id + "-next-year-but"); 
          this.butNextMonth    = document.getElementById(this.id + "-next-month-but");

          if(this.noToday) {
            this.butToday.style.display = "none";        
          };

          if(this.showWeeks) {
            this.wkThs = this.table.getElementsByTagName('tbody')[0].getElementsByTagName('th');
            this.div.className += " weeks-displayed";
          };

          tableBody = tableHead = tr = createThAndButton = createTH = null;

          if(this.rangeLow && this.rangeHigh && (this.rangeHigh - this.rangeLow < 7)) { this.equaliseDates(); };                        

          this.updateTableHeaders();
          this.created = true;                                                                    
          this.updateTable();                         

          if(this.staticPos) {                                 
            this.visible = true;
            this.opacity = this.opacityTo = this.finalOpacity;                                                                                              
            this.div.style.visibility = "visible";                       
            this.div.style.display = "block";
            this.noFocus = true;                                                          
            this.fade();
          } else {                                     
            this.reposition();
            this.div.style.visibility = "visible";
            this.fade();
            this.noFocus = true;   
          };   

          this.callback("domcreate", { "id":this.id });                                                   
        };                 
        this.fade = function() {
          window.clearTimeout(o.fadeTimer);
          o.fadeTimer = null;   
          var diff = Math.round(o.opacity + ((o.opacityTo - o.opacity) / 4)); 
          o.setOpacity(diff);  
          if(Math.abs(o.opacityTo - diff) > 3 && !o.noFadeEffect) {                                 
            o.fadeTimer = window.setTimeout(o.fade, 50);
          } else {
            o.setOpacity(o.opacityTo);
            if(o.opacityTo == 0) {
              o.div.style.display    = "none";
              o.div.style.visibility = "hidden";
              setARIAProperty(o.div, "hidden", "true");
              o.visible = false;
            } else {
              setARIAProperty(o.div, "hidden", "false");
              o.visible = true;                                        
            };
          };
        };                  
        this.trackDrag = function(e) {
          e = e || window.event;
          var diffx = (e.pageX?e.pageX:e.clientX?e.clientX:e.x) - o.mx;
          var diffy = (e.pageY?e.pageY:e.clientY?e.clientY:e.Y) - o.my;
          o.div.style.left = Math.round(o.x + diffx) > 0 ? Math.round(o.x + diffx) + 'px' : "0px";
          o.div.style.top  = Math.round(o.y + diffy) > 0 ? Math.round(o.y + diffy) + 'px' : "0px";
          /*@cc_on
          @if(@_jscript_version <= 5.7)                         
          if(o.staticPos || o.isIE7) return;
          o.iePopUp.style.top    = o.div.style.top;
          o.iePopUp.style.left   = o.div.style.left;
          @end
          @*/
        };
        this.stopDrag = function(e) {
          var b = document.getElementsByTagName("body")[0];
          b.className = b.className.replace(/fd-drag-active/g, "");
          removeEvent(document,'mousemove',o.trackDrag, false);
          removeEvent(document,'mouseup',o.stopDrag, false);
          o.div.style.zIndex = 9999;
        }; 
        this.onmousedown = function(e) {
          e = e || document.parentWindow.event;
          var el     = e.target != null ? e.target : e.srcElement,
          origEl = el,
          hideDP = true,
          reg    = new RegExp("^fd-(but-)?" + o.id + "$");

          o.mouseDownElem = null;

          // Are we within the wrapper div or the button    
          while(el) {
            if(el.id && el.id.length && el.id.search(reg) != -1) { 
              hideDP = false;
              break;
            };
            try { el = el.parentNode; } catch(err) { break; };
          };

          // If not, then ...     
          if(hideDP) {                                                        
            hideAll();                                                            
            return true;                                                                  
          };

          if((o.div.className + origEl.className).search('fd-disabled') != -1) { return true; };                                                                                                            

          // We check the mousedown events on the buttons
          if(origEl.id.search(new RegExp("^" + o.id + "(-prev-year-but|-prev-month-but|-next-month-but|-next-year-but)$")) != -1) {

            o.mouseDownElem = origEl;

            addEvent(document, "mouseup", o.clearTimer);
            addEvent(origEl, "mouseout",  o.clearTimer); 

            var incs = {
              "-prev-year-but":[0,-1,0],
              "-prev-month-but":[0,0,-1],
              "-next-year-but":[0,1,0],
              "-next-month-but":[0,0,1]
            },
            check = origEl.id.replace(o.id, ""),
            dateYYYYMM = Number(o.date.getFullYear() + pad(o.date.getMonth()+1));

            o.timerInc      = 800;
            o.timerSet      = true;
            o.dayInc        = incs[check][0];
            o.yearInc       = incs[check][1];
            o.monthInc      = incs[check][2]; 
            o.accellerator  = 1;

            if(!(o.currentYYYYMM == dateYYYYMM)) {
              if((o.currentYYYYMM < dateYYYYMM && (o.yearInc == -1 || o.monthInc == -1)) || (o.currentYYYYMM > dateYYYYMM && (o.yearInc == 1 || o.monthInc == 1))) {
                o.delayedUpdate = false; 
                o.timerInc = 1200;                                                
              } else {
                o.delayedUpdate = true;
                o.timerInc = 800;                                                
              };  
            };

            o.updateTable();    

            return stopEvent(e);

          } else if(el.className.search("drag-enabled") != -1) {                                  
            o.mx = e.pageX ? e.pageX : e.clientX ? e.clientX : e.x;
            o.my = e.pageY ? e.pageY : e.clientY ? e.clientY : e.Y;
            o.x  = parseInt(o.div.style.left);
            o.y  = parseInt(o.div.style.top);
            addEvent(document,'mousemove',o.trackDrag, false);
            addEvent(document,'mouseup',o.stopDrag, false);
            var b = document.getElementsByTagName("body")[0];
            b.className = b.className.replace(/fd-drag-active/g, "") + " fd-drag-active";
            o.div.style.zIndex = 10000;

            return stopEvent(e);
          };
          return true;                                                                      
        }; 
        this.onclick = function(e) {
          if(o.opacity != o.opacityTo || o.disabled) return stopEvent(e);

          e = e || document.parentWindow.event;
          var el = e.target != null ? e.target : e.srcElement;                         

          while(el.parentNode) {
            // Are we within a valid i.e. clickable TD node  
            if(el.tagName && el.tagName.toLowerCase() == "td") {   

              if(el.className.search(/cd-([0-9]{8})/) == -1 || el.className.search(/date-picker-unused|out-of-range|day-disabled|no-selection|not-selectable/) != -1) return stopEvent(e);

              var cellDate = el.className.match(/cd-([0-9]{8})/)[1];                                                                                                                                                                           
              o.date       = new Date(cellDate.substr(0,4),cellDate.substr(4,2)-1,cellDate.substr(6,2));                                                                                
              o.dateSet    = new Date(o.date); 
              o.noFocus    = true;                                                                       
              o.callback("dateset", { "id":o.id, "date":o.dateSet, "dd":o.dateSet.getDate(), "mm":o.dateSet.getMonth() + 1, "yyyy":o.dateSet.getFullYear() });                                          
              o.returnFormattedDate();
              o.hide();                  

              o.stopTimer();

              break;   
              // Today button pressed             
            } else if(el.id && el.id == o.id + "-today-but") {                                 
              o.date = new Date(); 
              o.updateTable();
              o.stopTimer();
              break; 
              // Day headers clicked, change the first day of the week      
            } else if(el.className.search(/date-picker-day-header/) != -1) {
              var cnt = o.showWeeks ? -1 : 0,
              elem = el;

              while(elem.previousSibling) {
                elem = elem.previousSibling;
                if(elem.tagName && elem.tagName.toLowerCase() == "th") cnt++;
              };

              o.firstDayOfWeek = (o.firstDayOfWeek + cnt) % 7;
              o.updateTableHeaders();
              break;     
            };
            try { el = el.parentNode; } catch(err) { break; };
          };

          return stopEvent(e);                                                
        };

        this.show = function(autoFocus) {                         
          if(this.staticPos) { return; };

          var elem, elemID;
          for(elemID in this.formElements) {
            elem = document.getElementById(this.id);
            if(!elem || (elem && elem.disabled)) { return; };   
          };

          this.noFocus = true; 

          // If the datepicker doesn't exist in the dom  
          if(!this.created || !document.getElementById('fd-' + this.id)) {                          
            this.created    = false;
            this.fullCreate = false;                                                                                             
            this.create();                                 
            this.fullCreate = true;                                                            
          } else {                                                        
            this.setDateFromInput();                                                               
            this.reposition();                                 
          };                      

          this.noFocus = !!!autoFocus;                          

          if(this.noFocus) { 
            this.clickActivated = true;
            addEvent(document, "mousedown", this.onmousedown); 
            if(mouseWheel) {
              if (window.addEventListener && !window.devicePixelRatio) window.addEventListener('DOMMouseScroll', this.onmousewheel, false);
              else {
                addEvent(document, "mousewheel", this.onmousewheel);
                addEvent(window,   "mousewheel", this.onmousewheel);
              };
            };     
          } else {
            this.clickActivated = false;
          };    

          this.opacityTo = this.finalOpacity;
          this.div.style.display = "block";                        

          /*@cc_on
          @if(@_jscript_version <= 5.7)                          
          if(!o.isIE7) {
          this.iePopUp.style.width = this.div.offsetWidth + "px";
          this.iePopUp.style.height = this.div.offsetHeight + "px";
          this.iePopUp.style.display = "block";
          };                                
          @end
          @*/                               

          this.setNewFocus(); 
          this.fade();
          var butt = document.getElementById('fd-but-' + this.id);
          if(butt) { butt.className = butt.className.replace("dp-button-active", "") + " dp-button-active"; };                                                
        };
        this.hide = function() {                        
          if(!this.visible || !this.created || !document.getElementById('fd-' + this.id)) return;

          this.kbEvent = false;

          o.div.className = o.div.className.replace("datepicker-focus", "");  

          this.stopTimer();
          this.removeOnFocusEvents();
          this.clickActivated = false;                         

          // Update status bar                                
          if(this.statusBar) { this.updateStatus(getTitleTranslation(9)); };    

          this.noFocus = true;
          this.setNewFocus();

          if(this.staticPos) {                                                                 
            return; 
          };

          var butt = document.getElementById('fd-but-' + this.id);
          if(butt) butt.className = butt.className.replace("dp-button-active", "");

          removeEvent(document, "mousedown", this.onmousedown);

          if(mouseWheel) {
            if (window.addEventListener && !window.devicePixelRatio) {
              try { window.removeEventListener('DOMMouseScroll', this.onmousewheel, false);} catch(err) {};                                 
            } else {
              removeEvent(document, "mousewheel", this.onmousewheel);
              removeEvent(window,   "mousewheel", this.onmousewheel);
            }; 
          };

          /*@cc_on
          @if(@_jscript_version <= 5.7)
          if(!this.isIE7) { this.iePopUp.style.display = "none"; };
          @end
          @*/

          this.opacityTo = 0;
          this.fade();                  
        };
        this.onblur = function(e) {                                                                                                  
          o.hide();
        };
        this.onfocus = function(e) {                                               
          o.noFocus = false; 
          o.div.className = o.div.className.replace("datepicker-focus", "") + " datepicker-focus";                                                                                                      
          o.addOnFocusEvents();                                                                        
        };   
        this.onmousewheel = function(e) {                        
          e = e || document.parentWindow.event;
          var delta = 0;

          if (e.wheelDelta) {
            delta = e.wheelDelta/120;
            if (isOpera && window.opera.version() < 9.2) delta = -delta;
          } else if(e.detail) {
            delta = -e.detail/3;
          };                          

          var n = o.date.getDate(),
          d = new Date(o.date),
          inc = delta > 0 ? 1 : -1;                         

          d.setDate(2);
          d.setMonth(d.getMonth() + inc * 1);
          d.setDate(Math.min(n, daysInMonth(d.getMonth(),d.getFullYear())));

          if(o.outOfRange(d)) { return stopEvent(e); };

          o.date = new Date(d);

          o.updateTable(); 

          if(o.statusBar) { o.updateStatus(printFormattedDate(o.date, o.statusFormat, true)); };

          return stopEvent(e);                                                       
        };                      
        this.onkeydown = function (e) {
          o.stopTimer();
          if(!o.visible) return false;

          e = e || document.parentWindow.event;
          var kc = e.keyCode ? e.keyCode : e.charCode;

          if( kc == 13 ) {
            // RETURN/ENTER: close & select the date
            var td = document.getElementById(o.id + "-date-picker-hover");                                         
            if(!td || td.className.search(/cd-([0-9]{8})/) == -1 || td.className.search(/no-selection|out-of-range|day-disabled/) != -1) {
              return stopEvent(e);
            };
            o.dateSet = new Date(o.date);
            o.callback("dateset", o.createCbArgObj()); 
            o.returnFormattedDate();    
            o.hide();
            return stopEvent(e);
          } else if(kc == 27) {
            // ESC: close, no date selection 
            if(!o.staticPos) {
              o.hide();
              return stopEvent(e);
            };
            return true;
          } else if(kc == 32 || kc == 0) {
            // SPACE: goto today's date 
            o.date = new Date();
            o.updateTable();
            return stopEvent(e);
          } else if(kc == 9) {
            // TAB: close, no date selection & focus on btton - popup only                                      
            if(!o.staticPos) {
              return stopEvent(e);
            };
            return true;                                
          };    

          // Internet Explorer fires the keydown event faster than the JavaScript engine can
          // update the interface. The following attempts to fix this.

          /*@cc_on
          @if(@_win32)                                 
          if(new Date().getTime() - o.interval.getTime() < 50) { return stopEvent(e); }; 
          o.interval = new Date();                                 
          @end
          @*/

          if(isMoz) {
            if(new Date().getTime() - o.interval.getTime() < 50) { return stopEvent(e); }; 
            o.interval = new Date();
          };                                 

          if ((kc > 49 && kc < 56) || (kc > 97 && kc < 104)) {
            if(kc > 96) kc -= (96-48);
            kc -= 49;
            o.firstDayOfWeek = (o.firstDayOfWeek + kc) % 7;
            o.updateTableHeaders();
            return stopEvent(e);
          };

          if ( kc < 33 || kc > 40 ) return true;

          var d = new Date(o.date), tmp, cursorYYYYMM = o.date.getFullYear() + pad(o.date.getMonth()+1); 

          // HOME: Set date to first day of current month
          if(kc == 36) {
            d.setDate(1); 
            // END: Set date to last day of current month                                 
          } else if(kc == 35) {
            d.setDate(daysInMonth(d.getMonth(),d.getFullYear())); 
            // PAGE UP & DOWN                                   
          } else if ( kc == 33 || kc == 34) {
            var inc = (kc == 34) ? 1 : -1; 

            // CTRL + PAGE UP/DOWN: Moves to the same date in the previous/next year
            if(e.ctrlKey) {                                                                                                               
              d.setFullYear(d.getFullYear() + inc * 1);
              // PAGE UP/DOWN: Moves to the same date in the previous/next month                                            
            } else {                                          
              var n = o.date.getDate();                         

              d.setDate(2);
              d.setMonth(d.getMonth() + inc * 1);
              d.setDate(Math.min(n, daysInMonth(d.getMonth(),d.getFullYear())));                                         
            };                                                                    
            // LEFT ARROW                                    
          } else if ( kc == 37 ) {                                         
            d = new Date(o.date.getFullYear(), o.date.getMonth(), o.date.getDate() - 1);                                       
            // RIGHT ARROW
          } else if ( kc == 39 || kc == 34) {                                         
            d = new Date(o.date.getFullYear(), o.date.getMonth(), o.date.getDate() + 1 ); 
            // UP ARROW                                        
          } else if ( kc == 38 ) {                                          
            d = new Date(o.date.getFullYear(), o.date.getMonth(), o.date.getDate() - 7);  
            // DOWN ARROW                                        
          } else if ( kc == 40 ) {                                          
            d = new Date(o.date.getFullYear(), o.date.getMonth(), o.date.getDate() + 7);                                         
          };

          if(o.outOfRange(d)) { return stopEvent(e); };
          o.date = d;

          if(o.statusBar) { 
            o.updateStatus(o.getBespokeTitle(o.date.getFullYear(),o.date.getMonth() + 1,o.date.getDate()) || printFormattedDate(o.date, o.statusFormat, true));                                
          };

          var t = String(o.date.getFullYear()) + pad(o.date.getMonth()+1) + pad(o.date.getDate());

          if(e.ctrlKey || (kc == 33 || kc == 34) || t < o.firstDateShown || t > o.lastDateShown) {                                                                       
            o.updateTable(); 
            /*@cc_on
            @if(@_win32)
            o.interval = new Date();                        
            @end
            @*/                                       
          } else {                                    
            if(!o.noToday) { o.disableTodayButton(); };                                        
            o.removeOldFocus();

            for(var i = 0, td; td = o.tds[i]; i++) {                                                                                             
              if(td.className.search("cd-" + t) == -1) {                                                          
                continue;
              };                                                 
              o.showHideButtons(o.date);
              td.id = o.id + "-date-picker-hover";                                                
              o.setNewFocus();
              break;
            };
          };

          return stopEvent(e);
        }; 
        this.onmouseout = function(e) {
          e = e || document.parentWindow.event;
          var p = e.toElement || e.relatedTarget;
          while (p && p != this) try { p = p.parentNode } catch(e) { p = this; };
          if (p == this) return false;
          if(o.currentTR) {
            o.currentTR.className = ""; 
            o.currentTR = null;
          };

          if(o.statusBar) { 
            o.updateStatus(o.getBespokeTitle(o.date.getFullYear(),o.date.getMonth() + 1,o.date.getDate()) || printFormattedDate(o.date, o.statusFormat, true));                                
          };                          
        };
        this.onmouseover = function(e) {
          e = e || document.parentWindow.event;
          var el = e.target != null ? e.target : e.srcElement;
          while(el.nodeType != 1) { el = el.parentNode; }; 

          if(!el || ! el.tagName) { return; };                              

          var statusText = getTitleTranslation(9);
          switch (el.tagName.toLowerCase()) {
            case "td":                                            
            if(el.className.search(/date-picker-unused|out-of-range/) != -1) {
              statusText = getTitleTranslation(9);
            } if(el.className.search(/cd-([0-9]{8})/) != -1) {                                                                                               
              o.stopTimer();
              var cellDate = el.className.match(/cd-([0-9]{8})/)[1];                                                                                                                          

              o.removeOldFocus();
              el.id = o.id+"-date-picker-hover";
              o.setNewFocus();

              o.date = new Date(+cellDate.substr(0,4),+cellDate.substr(4,2)-1,+cellDate.substr(6,2));                                                
              if(!o.noToday) { o.disableTodayButton(); };

              statusText = o.getBespokeTitle(+cellDate.substr(0,4),+cellDate.substr(4,2),+cellDate.substr(6,2)) || printFormattedDate(o.date, o.statusFormat, true);                                                
            };
            break;
            case "th":
            if(!o.statusBar) { break; };
            if(el.className.search(/drag-enabled/) != -1) {
              statusText = getTitleTranslation(10);
            } else if(el.className.search(/date-picker-week-header/) != -1) {
              var txt = el.firstChild ? el.firstChild.nodeValue : "";
              statusText = txt.search(/^(\d+)$/) != -1 ? getTitleTranslation(7, [txt, txt < 3 && o.date.getMonth() == 11 ? getWeeksInYear(o.date.getFullYear()) + 1 : getWeeksInYear(o.date.getFullYear())]) : getTitleTranslation(9);
            };
            break;
            case "span":
            if(!o.statusBar) { break; };
            if(el.className.search(/drag-enabled/) != -1) {
              statusText = getTitleTranslation(10);
            } else if(el.className.search(/day-([0-6])/) != -1) {
              var day = el.className.match(/day-([0-6])/)[1];
              statusText = getTitleTranslation(11, [getDayTranslation(day, false)]);
            } else if(el.className.search(/prev-year/) != -1) {
              statusText = getTitleTranslation(2);
            } else if(el.className.search(/prev-month/) != -1) {
              statusText = getTitleTranslation(0);
            } else if(el.className.search(/next-year/) != -1) {
              statusText = getTitleTranslation(3);
            } else if(el.className.search(/next-month/) != -1) {
              statusText = getTitleTranslation(1);
            } else if(el.className.search(/today-but/) != -1 && el.className.search(/disabled/) == -1) {
              statusText = getTitleTranslation(12);
            };
            break;
            default:
              statusText = "";
          };
          while(el.parentNode) {
            el = el.parentNode;
            if(el.nodeType == 1 && el.tagName.toLowerCase() == "tr") {                                                  
              if(o.currentTR) {
                if(el == o.currentTR) break;
                o.currentTR.className = ""; 
              };                                                 
              el.className = "dp-row-highlight";
              o.currentTR = el;
              break;
            };
          };                                                          
          if(o.statusBar && statusText) { o.updateStatus(statusText); };                                 
        }; 
        this.clearTimer = function() {
          o.stopTimer();
          o.timerInc      = 800;
          o.yearInc       = 0;
          o.monthInc      = 0;
          o.dayInc        = 0;

          removeEvent(document, "mouseup", o.clearTimer);
          if(o.mouseDownElem != null) {
            removeEvent(o.mouseDownElem, "mouseout",  o.clearTimer);
          };
          o.mouseDownElem = null;
        };    

        var o = this;                 

        this.setDateFromInput();

        if(this.staticPos) {                          
          this.create();                                               
        } else { 
          this.createButton();                                               
        };

        (function() {
            var elemID, elem;

            for(elemID in o.formElements) {                              
              elem = document.getElementById(elemID);
              if(elem && elem.tagName && elem.tagName.search(/select|input/i) != -1) {                                                                     
                addEvent(elem, "change", o.changeHandler);                                
              };

              if(!elem || elem.disabled == true) {
                o.disableDatePicker();
              };                         
            };                                      
        })();   


        // We have fully created the datepicker...
        this.fullCreate = true;


      };
      datePicker.prototype.addButtonEvents = function(but) {
        function buttonEvent (e) {
          e = e || window.event;                      

          var inpId     = this.id.replace('fd-but-',''),
          dpVisible = isVisible(inpId),
          autoFocus = false,
          kbEvent   = datePickers[inpId].kbEvent;

          if(kbEvent) {
            datePickers[inpId].kbEvent = false;
            return;
          };

          if(e.type == "keydown") {
            datePickers[inpId].kbEvent = true;
            var kc = e.keyCode != null ? e.keyCode : e.charCode;
            if(kc != 13) return true; 
            if(dpVisible) {
              this.className = this.className.replace("dp-button-active", "");                                          
              hideAll();
              return stopEvent(e);
            };                                   
            autoFocus = true;
          } else {
            datePickers[inpId].kbEvent = false;
          };

          this.className = this.className.replace("dp-button-active", "");

          if(!dpVisible) {                                 
            this.className += " dp-button-active";
            hideAll(inpId);                                                             
            showDatePicker(inpId, autoFocus);
          } else {
            hideAll();
          };

          return stopEvent(e);
        };

        but.onkeydown = buttonEvent;
        but.onclick = buttonEvent;

        if(!buttonTabIndex || this.bespokeTabIndex === false) {
          but.setAttribute(!/*@cc_on!@*/false ? "tabIndex" : "tabindex", "-1");
          but.tabIndex = -1; 
          but.onkeydown = null; 
          removeEvent(but, "keydown", buttonEvent);
        } else {
          but.setAttribute(!/*@cc_on!@*/false ? "tabIndex" : "tabindex", this.bespokeTabIndex);
          but.tabIndex = this.bespokeTabIndex;
        };                              
      };

      datePicker.prototype.createButton = function() {

        if(this.staticPos || document.getElementById("fd-but-" + this.id)) { return; };

        var inp         = document.getElementById(this.id),
        span        = document.createElement('span'),
        but         = document.createElement('a');

        but.href        = "#" + this.id;
        but.className   = "date-picker-control";
        but.title       = getTitleTranslation(5);
        but.id          = "fd-but-" + this.id;

        span.appendChild(document.createTextNode(nbsp));
        but.appendChild(span);

        span = document.createElement('span');
        span.className = "fd-screen-reader";
        span.appendChild(document.createTextNode(but.title));
        but.appendChild(span);

        // Set the ARIA role to be "button"
        setARIARole(but, "button");                 

        // Set a "haspopup" ARIA property - should this not be a list if ID's????
        setARIAProperty(but, "haspopup", true);

        if(this.positioned && document.getElementById(this.positioned)) {
          document.getElementById(this.positioned).appendChild(but);
        } else {
          inp.parentNode.insertBefore(but, inp.nextSibling);
        };                   

        this.addButtonEvents(but);

        but = null;

        this.callback("dombuttoncreate", {id:this.id});
      };
      datePicker.prototype.setBespokeTitles = function(titles) {                
        this.bespokeTitles = titles;               
      }; 
      datePicker.prototype.addBespokeTitles = function(titles) {                
        for(var dt in titles) {
          this.bespokeTitles[dt] = titles[dt];
        };              
      }; 
      datePicker.prototype.getBespokeTitle = function(y,m,d) {
        var dt, dtFull, yyyymmdd = y + String(pad(m)) + pad(d);

        // Try this datepickers bespoke titles
        for(dt in this.bespokeTitles) {
          dtFull = dt.replace(/^(\*\*\*\*)/, y).replace(/^(\d\d\d\d)(\*\*)/, "$1"+ pad(m));        
          if(dtFull == yyyymmdd) return this.bespokeTitles[dt];
        };

        // Try the generic bespoke titles
        for(dt in bespokeTitles) {
          dtFull = dt.replace(/^(\*\*\*\*)/, y).replace(/^(\d\d\d\d)(\*\*)/, "$1"+ pad(m));        
          if(dtFull == yyyymmdd) return bespokeTitles[dt];
        };

        return false;             
      };
      datePicker.prototype.returnSelectedDate = function() {                
        return this.dateSet;                
      };   
      datePicker.prototype.setRangeLow = function(range) {
        this.rangeLow = (String(range).search(/^(\d\d\d\d)(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])$/) == -1) ? false : range;                                              
        if(!this.inUpdate) this.setDateFromInput();                
      };
      datePicker.prototype.setRangeHigh = function(range) {
        this.rangeHigh = (String(range).search(/^(\d\d\d\d)(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])$/) == -1) ? false : range;                                               
        if(!this.inUpdate) this.setDateFromInput();                
      };
      datePicker.prototype.setDisabledDays = function(dayArray) {
        if(!dayArray.length || dayArray.length != 7 || dayArray.join("").search(/^([0|1]{7})$/) == -1) {
          if(debug) {
            throw "Invalid values located when attempting to call setDisabledDays";
          };
          return false;
        };                
        this.disabledDays = dayArray;                 
        if(!this.inUpdate) this.setDateFromInput();    
      };
      datePicker.prototype.setDisabledDates = function(dateObj) {                               
        this.disabledDates  = {};               
        this.addDisabledDates(dateObj);                
      }; 
      datePicker.prototype.setEnabledDates = function(dateObj) {                               
        this.enabledDates = {};                 
        this.addEnabledDates(dateObj);                
      };         
      datePicker.prototype.addDisabledDates = function(dateObj) {                    
        this.enabledDates  = false; 
        this.disabledDates = this.disabledDates || {};

        var startD;
        for(startD in dateObj) {
          if((String(startD).search(wcDateRegExp) != -1 && dateObj[startD] == 1) || (String(startD).search(rangeRegExp) != -1 && String(dateObj[startD]).search(rangeRegExp) != -1)) {
            this.disabledDates[startD] = dateObj[startD];
          };
        };

        if(!this.inUpdate) this.setDateFromInput();                                                                
      };
      datePicker.prototype.addEnabledDates = function(dateObj) {
        this.disabledDates = false; 
        this.enabledDates  = this.enabledDates || {};

        var startD;
        for(startD in dateObj) {
          if((String(startD).search(wcDateRegExp) != -1 && dateObj[startD] == 1) || (String(startD).search(rangeRegExp) != -1 && String(dateObj[startD]).search(rangeRegExp) != -1)) {
            this.enabledDates[startD] = dateObj[startD];
          };
        };

        if(!this.inUpdate) this.setDateFromInput();                                                                   
      };
      datePicker.prototype.setSelectedDate = function(yyyymmdd) {                                             
        if(String(yyyymmdd).search(wcDateRegExp) == -1) {
          return false;
        };  

        var match = yyyymmdd.match(rangeRegExp),
        dt    = new Date(+match[2],+match[3]-1,+match[4]);

        if(!dt || isNaN(dt) || !this.canDateBeSelected(dt)) {
          return false;
        };

        this.dateSet = new Date(dt);

        if(!this.inUpdate) this.updateTable();

        this.callback("dateset", this.createCbArgObj());
        this.returnFormattedDate();                                         
      };
      datePicker.prototype.checkSelectedDate = function() {                
        if(this.dateSet && !this.canDateBeSelected(this.dateSet)) {                        
          this.dateSet = null;
        };
        if(!this.inUpdate) this.updateTable();
      };
      datePicker.prototype.addOnFocusEvents = function() {                              
        if(this.kbEventsAdded || this.noFocus) {                         
          return;
        };

        addEvent(document, "keypress", this.onkeydown);
        addEvent(document, "mousedown", this.onmousedown);

        /*@cc_on
        @if(@_win32)
        removeEvent(document, "keypress", this.onkeydown);
        addEvent(document, "keydown", this.onkeydown);                 
        @end
        @*/
        if(window.devicePixelRatio) {
          removeEvent(document, "keypress", this.onkeydown);
          addEvent(document, "keydown", this.onkeydown);
        };             
        this.noFocus = false;   
        this.kbEventsAdded = true;                
      };         
      datePicker.prototype.removeOnFocusEvents = function() {

        if(!this.kbEventsAdded) { return; };

        removeEvent(document, "keypress",  this.onkeydown);
        removeEvent(document, "keydown",   this.onkeydown);
        removeEvent(document, "mousedown", this.onmousedown);                 

        this.kbEventsAdded = false;                 
      };         
      datePicker.prototype.stopTimer = function() {
        this.timerSet = false;
        window.clearTimeout(this.timer);
      };
      datePicker.prototype.setOpacity = function(op) {
        this.div.style.opacity = op/100;
        this.div.style.filter = 'alpha(opacity=' + op + ')';
        this.opacity = op;
      };         
      datePicker.prototype.getDates = function(y, m) {                
        var dpm = daysInMonth(m - 1, y),
        obj = {},
        dds = this.getGenericDates(y, m, false), 
        eds = this.getGenericDates(y, m, true), 
        dts = y + pad(m);                

        for(var i = 1; i <= dpm; i++) {
          dt = dts + "" + pad(i);

          if(dds) {                                
            obj[dt] = (dt in dds) ? 0 : 1;                                
          } else if(eds) {
            obj[dt] = (dt in eds) ? 1 : 0;
          } else {
            obj[dt] = 1;
          };                                                    
        };

        return obj;
      }; 
      datePicker.prototype.getGenericDates = function(y, m, enabled) {
        var deDates = enabled ? this.enabledDates : this.disabledDates;                

        if(!deDates) {                        
          return false;
        };

        m = pad(m);                 

        var obj    = {},            
        lower  = this.firstDateShown,
        upper  = this.lastDateShown,                            
        dt1, dt2, rngLower, rngUpper;  

        if(!upper || !lower) {
          lower = this.firstDateShown = y + pad(m) + "01";
          upper = this.lastDateShown  = y + pad(m) + pad(daysInMonth(m, y));                        
        };                 

        for(dt in deDates) {                         
          dt1 = dt.replace(/^(\*\*\*\*)/, y).replace(/^(\d\d\d\d)(\*\*)/, "$1"+m);
          dt2 = deDates[dt];

          if(dt2 == 1) {                                                
            if(Number(dt1.substr(0,6)) >= +String(this.firstDateShown).substr(0,6)
              && 
              Number(dt1.substr(0,6)) <= +String(this.lastDateShown).substr(0,6)) {
              obj[dt1] = 1;                                                              
            };
            continue; 
          };                       

          // Range
          if(+String(this.firstDateShown).substr(0,6) >= Number(dt1.substr(0,6))
            &&
            +String(this.lastDateShown).substr(0,6) <= Number(dt2.substr(0,6))) {                                              
            // Same month
            if(Number(dt1.substr(0,6)) == Number(dt2.substr(0,6))) {
              for(var i = dt1; i <= dt2; i++) {
                obj[i] = 1;                                                                                                
              };
              continue;
            };

            // Different months but we only want this month
            rngLower = Number(dt1.substr(0,6)) == +String(this.firstDateShown).substr(0,6) ? dt1 : lower;
            rngUpper = Number(dt2.substr(0,6)) == +String(this.lastDateShown).substr(0,6) ? dt2 : upper;
            for(var i = +rngLower; i <= +rngUpper; i++) {
              obj[i] = 1;                                                                                    
            };
          };
        };
        return obj;
      };       
      datePicker.prototype.truePosition = function(element) {
        var pos = this.cumulativeOffset(element);
        if(isOpera) { return pos; };
        var iebody      = (document.compatMode && document.compatMode != "BackCompat")? document.documentElement : document.body,
        dsocleft    = document.all ? iebody.scrollLeft : window.pageXOffset,
        dsoctop     = document.all ? iebody.scrollTop  : window.pageYOffset,
        posReal     = this.realOffset(element);
        return [pos[0] - posReal[0] + dsocleft, pos[1] - posReal[1] + dsoctop];
      };
      datePicker.prototype.realOffset = function(element) {
        var t = 0, l = 0;
        do {
          t += element.scrollTop  || 0;
          l += element.scrollLeft || 0;
          element = element.parentNode;
        } while(element);
        return [l, t];
      };
      datePicker.prototype.cumulativeOffset = function(element) {
        var t = 0, l = 0;
        do {
          t += element.offsetTop  || 0;
          l += element.offsetLeft || 0;
          element = element.offsetParent;
        } while(element);
        return [l, t];
      };
      datePicker.prototype.equaliseDates = function() {
        var clearDayFound = false, tmpDate;
        for(var i = this.rangeLow; i <= this.rangeHigh; i++) {
          tmpDate = String(i);
          if(!this.disabledDays[new Date(tmpDate.substr(0,4), tmpDate.substr(6,2), tmpDate.substr(4,2)).getDay() - 1]) {
            clearDayFound = true;
            break;
          };
        };
        if(!clearDayFound) { this.disabledDays = [0,0,0,0,0,0,0] };
      };
      datePicker.prototype.outOfRange = function(tmpDate) {

        if(!this.rangeLow && !this.rangeHigh) { return false; };
        var level = false;
        if(!tmpDate) {
          level   = true;
          tmpDate = this.date;
        };

        var d  = pad(tmpDate.getDate()),
        m  = pad(tmpDate.getMonth() + 1),
        y  = tmpDate.getFullYear(),
        dt = String(y)+String(m)+String(d);

        if(this.rangeLow && +dt < +this.rangeLow) {
          if(!level) { return true; };
          this.date = new Date(this.rangeLow.substr(0,4), this.rangeLow.substr(4,2)-1, this.rangeLow.substr(6,2), 5, 0, 0);
          return false;
        };
        if(this.rangeHigh && +dt > +this.rangeHigh) {
          if(!level) { return true; };
          this.date = new Date(this.rangeHigh.substr(0,4), this.rangeHigh.substr(4,2)-1, this.rangeHigh.substr(6,2), 5, 0, 0);
        };
        return false;
      };  
      datePicker.prototype.canDateBeSelected = function(tmpDate) {
        if(!tmpDate) return false;

        var d  = pad(tmpDate.getDate()),
        m  = pad(tmpDate.getMonth() + 1),
        y  = tmpDate.getFullYear(),
        dt = String(y)+String(m)+String(d),
        dd = this.getDates(y, m),                    
        wd = tmpDate.getDay() == 0 ? 7 : tmpDate.getDay();               

        if((this.rangeLow && +dt < +this.rangeLow) || (this.rangeHigh && +dt > +this.rangeHigh) || (dd[dt] == 0) || this.disabledDays[wd-1]) {
          return false;
        };

        return true;
      };        
      datePicker.prototype.updateStatus = function(msg) {                                
        while(this.statusBar.firstChild) { this.statusBar.removeChild(this.statusBar.firstChild); };

        if(msg && this.statusFormat.search(/-S|S-/) != -1 && msg.search(/([0-9]{1,2})(st|nd|rd|th)/) != -1) {                
          msg = msg.replace(/([0-9]{1,2})(st|nd|rd|th)/, "$1<sup>$2</sup>").split(/<sup>|<\/sup>/);                                                 
          var dc = document.createDocumentFragment();
          for(var i = 0, nd; nd = msg[i]; i++) {
            if(/^(st|nd|rd|th)$/.test(nd)) {
              var sup = document.createElement("sup");
              sup.appendChild(document.createTextNode(nd));
              dc.appendChild(sup);
            } else {
              dc.appendChild(document.createTextNode(nd));
            };
          };
          this.statusBar.appendChild(dc);                        
        } else {                        
          this.statusBar.appendChild(document.createTextNode(msg ? msg : getTitleTranslation(9)));                                                 
        };                                    
      };
      datePicker.prototype.setDateFromInput = function() {
        var origDateSet = this.dateSet,
        m = false,
        dt, elemID, elem, elemFmt, d, y, elemVal;

        this.dateSet = null;

        for(elemID in this.formElements) {
          elem = document.getElementById(elemID);

          if(!elem) {
            return;
          };

          elemVal = String(elem.value);
          elemFmt = this.formElements[elemID];
          dt      = false;

          if(!(elemVal == "")) {                        
            for(var i = 0, fmt; fmt = this.formatMasks[elemID][i]; i++) {                                        
              dt = parseDateString(elemVal, fmt);                                                              
              if(dt) {                                                                                       
                break;
              };                                
            }; 
          };

          if(dt) {
            if(elemFmt.search(new RegExp('[' + dParts + ']')) != -1) {
              //console.log("located d part " + elemFmt + " : " + dt.getDate());
              d = dt.getDate();        
            };
            if(elemFmt.search(new RegExp('[' + mParts + ']')) != -1) { 
              //console.log("located m part " + elemFmt + " : " + dt.getMonth());                                       
              m = dt.getMonth();                                               
            };
            if(elemFmt.search(new RegExp('[' + yParts + ']')) != -1) {
              //console.log("located y part " + elemFmt + " : " + dt.getFullYear());
              y = dt.getFullYear()        
            };                        
          };                                            
        };

        dt = false;

        if(d && !(m === false) && y) {                                            
          if(+d > daysInMonth(+m, +y)) { 
            d  = daysInMonth(+m, +y);
            dt = false;
          } else {
            dt = new Date(+y, +m, +d);
          };
        };

        if(!dt || isNaN(dt)) {                        
          var newDate = new Date(y || new Date().getFullYear(), !(m === false) ? m : new Date().getMonth(), 1);
          this.date = this.cursorDate ? new Date(+this.cursorDate.substr(0,4), +this.cursorDate.substr(4,2) - 1, +this.cursorDate.substr(6,2)) : new Date(newDate.getFullYear(), newDate.getMonth(), Math.min(+d || new Date().getDate(), daysInMonth(newDate.getMonth(), newDate.getFullYear())));

          this.date.setHours(5);
          this.outOfRange();                         
          //this.callback("dateset", this.createCbArgObj());  
          this.updateTable();                         
          return;
        };


        dt.setHours(5);
        this.date = new Date(dt);                            
        this.outOfRange();                 

        if(dt.getTime() == this.date.getTime() && this.canDateBeSelected(this.date)) {                                              
          this.dateSet = new Date(this.date);
        };

        //this.callback("dateset", this.createCbArgObj()); 
        if(this.fullCreate) this.updateTable();
        this.returnFormattedDate(true);
      };
      datePicker.prototype.setSelectIndex = function(elem, indx) {
        for(var opt = elem.options.length-1; opt >= 0; opt--) {
          if(elem.options[opt].value == indx) {
            elem.selectedIndex = opt;
            return;
          };
        };
      };
      datePicker.prototype.returnFormattedDate = function(noFocus) {     
        if(!this.dateSet) {                                
          return;
        };

        var d   = pad(this.dateSet.getDate()),
        m   = pad(this.dateSet.getMonth() + 1),
        y   = this.dateSet.getFullYear(),
        el  = false, 
        elemID, elem, elemFmt, fmtDate;

        noFocus = !!noFocus;

        for(elemID in this.formElements) {
          elem    = document.getElementById(elemID);

          if(!elem) return;

          if(!el) el = elem;

          elemFmt = this.formElements[elemID];

          fmtDate = printFormattedDate(this.dateSet, elemFmt, returnLocaleDate);                   
          if(elem.tagName.toLowerCase() == "input") {
            elem.value = fmtDate; 
          } else {  
            this.setSelectIndex(elem, fmtDate);                              
          };
        };

        if(this.staticPos) { 
          this.noFocus = true;
          this.updateTable(); 
          this.noFocus = false;
        };                         

        if(this.fullCreate) {
          if(el.type && el.type != "hidden" && !noFocus) { el.focus(); };                                                                                                                                             
        };         
      };
      datePicker.prototype.disableDatePicker = function() {
        if(this.disabled) return;

        if(this.staticPos) {
          this.removeOnFocusEvents();
          this.removeOldFocus();
          this.noFocus = true;
          this.div.className = this.div.className.replace(/dp-disabled/, "") + " dp-disabled";  
          this.table.onmouseover = this.table.onclick = this.table.onmouseout = this.table.onmousedown = null;                                      
          removeEvent(document, "mousedown", this.onmousedown);                         
          removeEvent(document, "mouseup",   this.clearTimer);                       
        } else {  
          if(this.visible) this.hide();                        
          var but = document.getElementById("fd-but-" + this.id);
          if(but) {
            but.className = but.className.replace(/dp-disabled/, "") + " dp-disabled";
            // Set a "disabled" ARIA state
            setARIAProperty(but, "disabled", true);                               
            but.onkeydown = but.onclick = function() { return false; }; 
            but.setAttribute(!/*@cc_on!@*/false ? "tabIndex" : "tabindex", "-1");
            but.tabIndex = -1;                
          };                         
        };               

        clearTimeout(this.timer);                
        this.disabled = true;  
      }; 
      datePicker.prototype.enableDatePicker = function() {
        if(!this.disabled) return;

        if(this.staticPos) {
          this.removeOldFocus();
          this.noFocus = true;                        
          this.updateTable();
          this.div.className = this.div.className.replace(/dp-disabled/, "");
          this.disabled = false;                         
          this.table.onmouseover = this.onmouseover;
          this.table.onmouseout  = this.onmouseout;
          this.table.onclick     = this.onclick;                         
          this.table.onmousedown = this.onmousedown;                                                                    
        } else {                         
          var but = document.getElementById("fd-but-" + this.id);
          if(but) {
            but.className = but.className.replace(/dp-disabled/, "");
            // Reset the "disabled" ARIA state
            setARIAProperty(but, "disabled", false);
            this.addButtonEvents(but);                                                
          };                         
        };

        this.disabled = false;                
      };
      datePicker.prototype.disableTodayButton = function() {
        var today = new Date();                     
        this.butToday.className = this.butToday.className.replace("fd-disabled", "");
        if(this.outOfRange(today) || (this.date.getDate() == today.getDate() && this.date.getMonth() == today.getMonth() && this.date.getFullYear() == today.getFullYear())) {
          this.butToday.className += " fd-disabled";                          
        };
      };
      datePicker.prototype.updateTableHeaders = function() {
        var colspanTotal = this.showWeeks ? 8 : 7,
        colOffset    = this.showWeeks ? 1 : 0,
        d, but;

        for(var col = colOffset; col < colspanTotal; col++ ) {
          d = (this.firstDayOfWeek + (col - colOffset)) % 7;
          this.ths[col].title = getDayTranslation(d, false);

          if(col > colOffset) {
            but = this.ths[col].getElementsByTagName("span")[0];
            while(but.firstChild) { but.removeChild(but.firstChild); };
            but.appendChild(document.createTextNode(getDayTranslation(d, true)));
            but.title = this.ths[col].title;
            but.className = but.className.replace(/day-([0-6])/, "") + " day-" + d;
            but = null;
          } else {
            while(this.ths[col].firstChild) { this.ths[col].removeChild(this.ths[col].firstChild); };
            this.ths[col].appendChild(document.createTextNode(getDayTranslation(d, true)));
          };

          this.ths[col].className = this.ths[col].className.replace(/date-picker-highlight/g, "");
          if(this.highlightDays[d]) {
            this.ths[col].className += " date-picker-highlight";
          };
        };

        if(this.created) { this.updateTable(); }
      }; 
      datePicker.prototype.callback = function(type, args) {   
        if(!type || !(type in this.callbacks)) { 
          return false; 
        };

        var ret = false;                   
        for(var func = 0; func < this.callbacks[type].length; func++) {                         
          ret = this.callbacks[type][func](args || this.id);                        
        };                      
        return ret;
      };      
      datePicker.prototype.showHideButtons = function(tmpDate) {
        if(!this.butPrevYear) { return; };

        var tdm = tmpDate.getMonth(),
        tdy = tmpDate.getFullYear();

        if(this.outOfRange(new Date((tdy - 1), tdm, daysInMonth(+tdm, tdy-1)))) {                            
          if(this.butPrevYear.className.search(/fd-disabled/) == -1) {
            this.butPrevYear.className += " fd-disabled";
          };
          if(this.yearInc == -1) this.stopTimer();
        } else {
          this.butPrevYear.className = this.butPrevYear.className.replace(/fd-disabled/g, "");
        };                 

        if(this.outOfRange(new Date(tdy, (+tdm - 1), daysInMonth(+tdm-1, tdy)))) {                           
          if(this.butPrevMonth.className.search(/fd-disabled/) == -1) {
            this.butPrevMonth.className += " fd-disabled";
          };
          if(this.monthInc == -1) this.stopTimer();
        } else {
          this.butPrevMonth.className = this.butPrevMonth.className.replace(/fd-disabled/g, "");
        };

        if(this.outOfRange(new Date((tdy + 1), +tdm, 1))) {                            
          if(this.butNextYear.className.search(/fd-disabled/) == -1) {
            this.butNextYear.className += " fd-disabled";
          };
          if(this.yearInc == 1) this.stopTimer();
        } else {
          this.butNextYear.className = this.butNextYear.className.replace(/fd-disabled/g, "");
        };                

        if(this.outOfRange(new Date(tdy, +tdm + 1, 1))) {
          if(this.butNextMonth.className.search(/fd-disabled/) == -1) {
            this.butNextMonth.className += " fd-disabled";
          };
          if(this.monthInc == 1) this.stopTimer();
        } else {
          this.butNextMonth.className = this.butNextMonth.className.replace(/fd-disabled/g, "");
        };
      };        
      var localeDefaults = {
        fullMonths:["January","February","March","April","May","June","July","August","September","October","November","December"],
        monthAbbrs:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],
        fullDays:  ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],
        dayAbbrs:  ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"],
        titles:    ["Previous month","Next month","Previous year","Next year", "Today", "Show Calendar", "wk", "Week [[%0%]] of [[%1%]]", "Week", "Select a date", "Click \u0026 Drag to move", "Display \u201C[[%0%]]\u201D first", "Go to Today\u2019s date", "Disabled date :"],
        firstDayOfWeek:0,
        imported:  false
      };        
      var joinNodeLists = function() {
        if(!arguments.length) { return []; }
        var nodeList = [];
        for (var i = 0; i < arguments.length; i++) {
          for (var j = 0, item; item = arguments[i][j]; j++) {
            nodeList[nodeList.length] = item;
          };
        };
        return nodeList;
      };
      var cleanUp = function() {
        var dp, fe;
        for(dp in datePickers) {
          for(fe in datePickers[dp].formElements) {
            if(!document.getElementById(fe)) {
              datePickers[dp].destroy();
              datePickers[dp] = null;
              delete datePickers[dp];
              break;
            }
          };
        };
      };         
      var hideAll = function(exception) {
        var dp;
        for(dp in datePickers) {
          if(!datePickers[dp].created || (exception && exception == datePickers[dp].id)) continue;
          datePickers[dp].hide();
        };
      };
      var hideDatePicker = function(inpID) {                
        if(inpID in datePickers) {
          if(!datePickers[inpID].created || datePickers[inpID].staticPos) return;
          datePickers[inpID].hide();
        };
      };
      var showDatePicker = function(inpID, autoFocus) {                
        if(!(inpID in datePickers)) return false;   

        datePickers[inpID].clickActivated = !!!autoFocus;             
        datePickers[inpID].show(autoFocus);
        return true;        
      };
      var destroy = function(e) {
        e = e || window.event;

        // Don't remove datepickers if it's a pagehide/pagecache event (webkit et al)
        if(e.persisted) {
          return;
        };

        for(dp in datePickers) {
          datePickers[dp].destroy();
          datePickers[dp] = null;
          delete datePickers[dp];
        };
        datePickers = null;

        removeEvent(window, 'unload', datePickerController.destroy);
      }; 
      var destroySingleDatePicker = function(id) {
        if(id && (id in datePickers)) {
          datePickers[id].destroy();
          datePickers[id] = null;
          delete datePickers[id];        
        };
      };
      var getTitleTranslation = function(num, replacements) {
        replacements = replacements || [];
        if(localeImport.titles.length > num) {
          var txt = localeImport.titles[num];
          if(replacements && replacements.length) {
            for(var i = 0; i < replacements.length; i++) {
              txt = txt.replace("[[%" + i + "%]]", replacements[i]);
            };
          };
          return txt.replace(/[[%(\d)%]]/g,"");
        };
        return "";
      };
      var getDayTranslation = function(day, abbreviation) {
        var titles = localeImport[abbreviation ? "dayAbbrs" : "fullDays"];
        return titles.length && titles.length > day ? titles[day] : "";
      };
      var getMonthTranslation = function(month, abbreviation) {
        var titles = localeImport[abbreviation ? "monthAbbrs" : "fullMonths"];
        return titles.length && titles.length > month ? titles[month] : "";
      };
      var daysInMonth = function(nMonth, nYear) {
        nMonth = (nMonth + 12) % 12;
        return (((0 == (nYear%4)) && ((0 != (nYear%100)) || (0 == (nYear%400)))) && nMonth == 1) ? 29: [31,28,31,30,31,30,31,31,30,31,30,31][nMonth];
      };

      var getWeeksInYear = function(Y) {
        if(Y in weeksInYearCache) {
          return weeksInYearCache[Y];
        };
        var X1, X2, NW;
        with (X1 = new Date(Y, 0, 4)) {
          setDate(getDate() - (6 + getDay()) % 7);
        };
        with (X2 = new Date(Y, 11, 28)) {
          setDate(getDate() + (7 - getDay()) % 7);
        };
        weeksInYearCache[Y] = Math.round((X2 - X1) / 604800000);
        return weeksInYearCache[Y];
      };

      var getWeekNumber = function(y,m,d) {
        var d = new Date(y, m, d, 0, 0, 0);
        var DoW = d.getDay();
        d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu
        var ms = d.valueOf(); // GMT
        d.setMonth(0);
        d.setDate(4); // Thu in Week 1
        return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1;
      };

      var printFormattedDate = function(date, fmt, useImportedLocale) {
        if(!date || isNaN(date)) { return ""; };                

        var parts = fmt.split("-"),
        str = [],
        d = date.getDate(),
        D = date.getDay(),
        m = date.getMonth(),
        y = date.getFullYear(),
        flags = {
          "sp":" ",
          "dt":".",
          "sl":"/",
          "ds":"-",
          "cc":",",
          "d":pad(d),
          "D":useImportedLocale ? localeImport.dayAbbrs[D == 0 ? 6 : D - 1] : localeDefaults.dayAbbrs[D == 0 ? 6 : D - 1],
          "l":useImportedLocale ? localeImport.fullDays[D == 0 ? 6 : D - 1] : localeDefaults.fullDays[D == 0 ? 6 : D - 1],
          "j":d,
          "N":D == 0 ? 7 : D,
          "w":D,                                
          "W":getWeekNumber(y,m,d),
          "M":useImportedLocale ? localeImport.monthAbbrs[m] : localeDefaults.monthAbbrs[m],
          "F":useImportedLocale ? localeImport.fullMonths[m] : localeDefaults.fullMonths[m],
          "m":pad(m + 1),
          "n":m + 1,
          "t":daysInMonth(m, y),
          "y":String(y).substr(2,2),                                
          "Y":y,
          "S":["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
        };    

        for(var pt = 0, part; part = parts[pt]; pt++) {  
          str.push(!(part in flags) ? "" : flags[part]);
        };

        return str.join("");
      };
      var parseDateString = function(str, fmt) {
        var d     = false,
        m     = false,
        y     = false,
        now   = new Date(),
        parts = fmt.replace(/-sp(-sp)+/g, "-sp").split("-"),
        divds = { "dt":".","sl":"/","ds":"-","cc":"," },
        str   = "" + str;                    

        loopLabel:
        for(var pt = 0, part; part = parts[pt]; pt++) {                        
          if(str.length == 0) { return false; };

          switch(part) {
            // Dividers - be easy on them all i.e. accept them all when parsing...                                
            case "sp":
            case "dt":
            case "sl":
            case "ds":
            case "cc":       
              str = str.replace(/^(\s|\.|\/|,|-){1,}/, "");                                     
              break;
            // DAY
            case "d": // Day of the month, 2 digits with leading zeros (01 - 31)
            case "j": // Day of the month without leading zeros (1 - 31)  
            // Accept both when parsing                                                          
            if(str.search(/^(3[01]|[12][0-9]|0?[1-9])/) != -1) {
              d = +str.match(/^(3[01]|[12][0-9]|0?[1-9])/)[0];
              str = str.substr(str.match(/^(3[01]|[12][0-9]|0?[1-9])/)[0].length);                                                        
              break;
            } else {                                                        
              return "";
            };
            case "D": // A textual representation of a day, three letters (Mon - Sun)
            case "l": // A full textual representation of the day of the week (Monday - Sunday)
            // Accept English & imported locales and both modifiers                                                  
            l = localeDefaults.fullDays.concat(localeDefaults.dayAbbrs);                                                  
            if(localeImport.imported) {
              l = l.concat(localeImport.fullDays).concat(localeImport.dayAbbrs);
            }; 

            for(var i = 0; i < l.length; i++) {
              if(new RegExp("^" + l[i], "i").test(str)) {                                                                
                str = str.substr(l[i].length);
                continue loopLabel;
              };
            };

            break;                                  
            case "N": // ISO-8601 numeric representation of the day of the week (added in PHP 5.1.0) 1 (for Monday) through 7 (for Sunday)
            case "w": // Numeric representation of the day of the week 0 (for Sunday) through 6 (for Saturday)
            if(str.search(part == "N" ? /^([1-7])/ : /^([0-6])/) != -1) {
              str = str.substr(1);

            };
            break;
            case "S": // English ordinal suffix for the day of the month, 2 characters: st, nd, rd or th
            if(str.search(/^(st|nd|rd|th)/i) != -1) {
              str = str.substr(2);                                                        
            };
            break;                                
            // WEEK
            case "W": // ISO-8601 week number of year, weeks starting on Monday (added in PHP 4.1.0): 1 - 53
            if(str.search(/^([1-9]|[1234[0-9]|5[0-3])/) != -1) {
              str = str.substr(str.match(/^([1-9]|[1234[0-9]|5[0-3])/)[0].length);                                                        
            };
            break;
            // MONTH
            case "M": // A short textual representation of a month, three letters
            case "F": // A full textual representation of a month, such as January or March
            // Accept English & imported locales and both modifiers                                                    
            l = localeDefaults.fullMonths.concat(localeDefaults.monthAbbrs);
            if(localeImport.imported) {
              l = l.concat(localeImport.fullMonths).concat(localeImport.monthAbbrs);
            };
            for(var i = 0; i < l.length; i++) {                                                        
              if(str.search(new RegExp("^" + l[i],"i")) != -1) {
                str = str.substr(l[i].length);
                m = ((i + 12) % 12);                                                                 
                continue loopLabel;
              };
            };
            return "";
            case "m": // Numeric representation of a month, with leading zeros
            case "n": // Numeric representation of a month, without leading zeros
            // Accept either when parsing
            l = /^(1[012]|0?[1-9])/;
            if(str.search(l) != -1) {
              m = +str.match(l)[0] - 1;
              str = str.substr(str.match(l)[0].length);
              break;
            } else {                                                        
              return "";
            };
            case "t": // Number of days in the given month: 28 through 31
            if(str.search(/2[89]|3[01]/) != -1) {
              str = str.substr(2);
              break;
            };
            break;
            // YEAR
            case "Y": // A full numeric representation of a year, 4 digits
            if(str.search(/^(\d{4})/) != -1) {
              y = str.substr(0,4);
              str = str.substr(4);
              break;
            } else {                                                        
              return "";
            };
            case "y": // A two digit representation of a year - be easy on four figure dates though                                                
            if(str.search(/^(\d{4})/) != -1) {
              y = str.substr(0,4);
              str = str.substr(4);
              break;
            } else if(str.search(/^(0[0-9]|[1-9][0-9])/) != -1) {
              y = str.substr(0,2);
              y = +y < 50 ? '20' + "" + String(y) : '19' + "" + String(y);
              str = str.substr(2);
              break;
            } else return "";

            default:
              return "";
          };
        };   

        if(!(str == "") || (d === false && m === false && y === false)) {
          return false;
        };                

        m = m === false ? 11                  : m;
        y = y === false ? now.getFullYear()   : y;
        d = d === false ? daysInMonth(+m, +y) : d;

        if(d > daysInMonth(+m, +y)) {
          return false;
        };

        var tmpDate = new Date(y,m,d);

        return !tmpDate || isNaN(tmpDate) ? false : tmpDate;
      };        
      var findLabelForElement = function(element) {
        var label;
        if(element.parentNode && element.parentNode.tagName.toLowerCase() == "label") lebel = element.parentNode;
        else {
          var labelList = document.getElementsByTagName('label');
          // loop through label array attempting to match each 'for' attribute to the id of the current element
          for(var lbl = 0; lbl < labelList.length; lbl++) {
            // Internet Explorer requires the htmlFor test
            if((labelList[lbl]['htmlFor'] && labelList[lbl]['htmlFor'] == element.id) || (labelList[lbl].getAttribute('for') == element.id)) {
              label = labelList[lbl];
              break;
            };
          };
        };

        if(label && !label.id) { label.id = element.id + "_label"; };
        return label;         
      };  
      var updateLanguage = function() {
        if(typeof(window.fdLocale) == "object" ) {                         
          localeImport = {
            titles          : fdLocale.titles,
            fullMonths      : fdLocale.fullMonths,
            monthAbbrs      : fdLocale.monthAbbrs,
            fullDays        : fdLocale.fullDays,
            dayAbbrs        : fdLocale.dayAbbrs,
            firstDayOfWeek  : ("firstDayOfWeek" in fdLocale) ? fdLocale.firstDayOfWeek : 0,
            imported        : true
          };                                               
        } else if(!localeImport) {                        
          localeImport = localeDefaults;
        };    
      };
      var loadLanguage = function() {
        updateLanguage();
        for(dp in datePickers) {
          if(!datePickers[dp].created) continue;
          datePickers[dp].updateTable();
        };   
      };
      var checkElem = function(elem) {                        
        return !(!elem || !elem.tagName || !((elem.tagName.toLowerCase() == "input" && (elem.type == "text" || elem.type == "hidden")) || elem.tagName.toLowerCase() == "select"));                
      };
      var addDatePicker = function(options) {  

        updateLanguage();

        if(!options.formElements) {
          if(debug) throw "No form elements stipulated within initialisation parameters";
          return;
        };

        options.id = (options.id && (options.id in options.formElements)) ? options.id : "";
        options.formatMasks = {};

        var testParts  = [dParts,mParts,yParts],
        partsFound = [0,0,0],
        tmpPartsFound,
        matchedPart,
        newParts,
        indParts,
        fmt,
        fmtBag,
        fmtParts,
        newFormats,
        myMin,
        myMax;               

        for(var elemID in options.formElements) {                
          elem = document.getElementById(elemID);

          if(!checkElem(elem)) {
            if(debug) throw "The element with and id of '" + elemID + "' is of the wrong type or does not exist within the DOM";
            return false;
          };

          if(!options.id) options.id = elemID;

          fmt             = options.formElements[elemID];

          if(!(fmt.match(validFmtRegExp))) {
            if(debug) throw "The element with and id of '" + elemID + "' has the following incorrect date format assigned to it: " + fmt;
            return false;
          };

          fmtBag          = [fmt];

          if(options.dateFormats && (elemID in options.dateFormats) && options.dateFormats[elemID].length) {
            newFormats = [];

            for(var f = 0, bDft; bDft = options.dateFormats[elemID][f]; f++) {                                       
              if(!(bDft.match(validFmtRegExp))) {
                if(debug) throw "The element with and id of '" + elemID + "' has the following incorrect date format assigned to it within the dateFormats parameter: " + bDft;
                return false;
              };  

              newFormats.push(bDft); 
            };

            fmtBag = fmtBag.concat(newFormats);  
          };

          tmpPartsFound   = [0,0,0];                        

          for(var i = 0, testPart; testPart = testParts[i]; i++) {                                
            if(fmt.search(new RegExp('('+testPart+')')) != -1) {
              partsFound[i] = tmpPartsFound[i] = 1;

              // Create the date format strings to check against later for text input elements
              if(elem.tagName.toLowerCase() == "input") {
                matchedPart = fmt.match(new RegExp('('+testPart+')'))[0];
                newParts    = String(matchedPart + "|" + testPart.replace(new RegExp("(" + matchedPart + ")"), "")).replace("||", "|");
                indParts    = newParts.split("|");
                newFormats  = [];

                for(var z = 0, bFmt; bFmt = fmtBag[z]; z++) {
                  for(var x = 0, indPart; indPart = indParts[x]; x++) {
                    if(indPart == matchedPart) continue;
                    newFormats.push(bFmt.replace(new RegExp('(' + testPart + ')(-|$)', 'g'), indPart + "-").replace(/-$/, ""));
                  };
                };

                fmtBag = fmtBag.concat(newFormats);
              };
            };
          };                        

          options.formatMasks[elemID] = fmtBag.concat();

          if(elem.tagName.toLowerCase() == "select") {
            myMin = myMax = 0;

            // If we have a selectList, then try to parse the higher and lower limits 
            var selOptions = elem.options;

            // Check the yyyymmdd 
            if(tmpPartsFound[0] && tmpPartsFound[1] && tmpPartsFound[2]) { 
              var yyyymmdd, 
              cursorDate = false;

              // Remove the disabledDates parameter
              if("disabledDates" in options) {
                delete(options.disabledDates);
              };

              // Dynamically calculate the available "enabled" dates
              options.enabledDates = {};

              for(i = 0; i < selOptions.length; i++) {
                for(var f = 0, fmt; fmt = fmtBag[f]; f++) {
                  dt = parseDateString(selOptions[i].value, fmt /*options.formElements[elemID]*/);
                  if(dt) {
                    yyyymmdd = dt.getFullYear() + "" + pad(dt.getMonth()+1) + "" + pad(dt.getDate());

                    if(!cursorDate) cursorDate = yyyymmdd;

                    options.enabledDates[yyyymmdd] = 1;

                    if(!myMin || Number(yyyymmdd) < myMin) {
                      myMin = yyyymmdd;
                    }; 

                    if(!myMax || Number(yyyymmdd) > myMax) {
                      myMax = yyyymmdd;
                    };

                    break;
                  };                                                
                };                                        
              };  

              // Automatically set cursor to first available date (if no bespoke cursorDate was set);                                        
              if(!options.cursorDate && cursorDate) options.cursorDate = cursorDate;

            } else if(tmpPartsFound[1] && tmpPartsFound[2]) {
              var yyyymm;

              for(i = 0; i < selOptions.length; i++) {
                for(var f = 0, fmt; fmt = fmtBag[f]; f++) {                                                
                  dt = parseDateString(selOptions[i].value, fmt /*options.formElements[elemID]*/);
                  if(dt) {
                    yyyymm = dt.getFullYear() + "" + pad(dt.getMonth()+1);

                    if(!myMin || Number(yyyymm) < myMin) {
                      myMin = yyyymm;
                    }; 

                    if(!myMax || Number(yyyymm) > myMax) {
                      myMax = yyyymm;
                    };   

                    break;                                             
                  }; 
                };                                       
              };                                           

              // Round the min & max values to be used as rangeLow & rangeHigh
              myMin += "" + "01";
              myMax += "" + daysInMonth(+myMax.substr(4,2) - 1, +myMax.substr(0,4));

            } else if(tmpPartsFound[2]) {
              var yyyy;

              for(i = 0; i < selOptions.length; i++) {
                for(var f = 0, fmt; fmt = fmtBag[f]; f++) { 
                  dt = parseDateString(selOptions[i].value, fmt /*options.formElements[elemID]*/);
                  if(dt) {
                    yyyy = dt.getFullYear();                                                        
                    if(!myMin || Number(yyyy) < myMin) {
                      myMin = yyyy;
                    }; 

                    if(!myMax || Number(yyyy) > myMax) {
                      myMax = yyyy;
                    }; 

                    break;
                  };                                               
                };                           
              };  

              // Round the min & max values to be used as rangeLow & rangeHigh
              myMin += "0101";
              myMax += "1231";                                                                                                    
            };

            if(myMin && (!options.rangeLow  || (+options.rangeLow < +myMin)))  options.rangeLow = myMin;
            if(myMax && (!options.rangeHigh || (+options.rangeHigh > +myMin))) options.rangeHigh = myMax;                                
          };
        };

        if(!(partsFound[0] && partsFound[1] && partsFound[2])) {
          if(debug) throw "Could not find all of the required date parts for element: " + elem.id;
          return false;
        }; 

        var opts = {
          formElements:options.formElements,
          // Form element id
          id:options.id,
          // Format masks 
          formatMasks:options.formatMasks,
          // Non popup datepicker required
          staticPos:!!(options.staticPos),
          // Position static datepicker or popup datepicker's button
          positioned:options.positioned && document.getElementById(options.positioned) ? options.positioned : "",
          // Ranges stipulated in YYYYMMDD format       
          rangeLow:options.rangeLow && String(options.rangeLow).search(rangeRegExp) != -1 ? options.rangeLow : "",
          rangeHigh:options.rangeHigh && String(options.rangeHigh).search(rangeRegExp) != -1 ? options.rangeHigh : "",
          // Status bar format
          statusFormat:options.statusFormat && String(options.statusFormat).search(validFmtRegExp) != -1 ? options.statusFormat : "",                                                                                 
          // No fade in/out effect
          noFadeEffect:!!(options.staticPos) ? true : !!(options.noFadeEffect),
          // No drag functionality
          dragDisabled:nodrag || !!(options.staticPos) ? true : !!(options.dragDisabled),
          // Bespoke tabindex for this datePicker (or it's activation button)
          bespokeTabIndex:options.bespokeTabindex && typeof options.bespokeTabindex == 'number' ? parseInt(options.bespokeTabindex, 10) : 0,
          // Bespoke titles
          bespokeTitles:options.bespokeTitles || {},
          // Final opacity 
          finalOpacity:options.finalOpacity && typeof options.finalOpacity == 'number' && (options.finalOpacity > 20 && options.finalOpacity <= 100) ? parseInt(+options.finalOpacity, 10) : (!!(options.staticPos) ? 100 : finalOpacity),
          // Do we hide the form elements on datepicker creation
          hideInput:!!(options.hideInput),
          // Do we hide the "today" button
          noToday:!!(options.noTodayButton),
          // Do we show week numbers
          showWeeks:!!(options.showWeeks),
          // Do we fill the entire grid with dates                                                  
          fillGrid:!!(options.fillGrid),
          // Do we constrain selection of dates outside the current month
          constrainSelection:"constrainSelection" in options ? !!(options.constrainSelection) : true,
          // The date to set the initial cursor to
          cursorDate:options.cursorDate && String(options.cursorDate).search(rangeRegExp) != -1 ? options.cursorDate : "",                        
          // Locate label to set the ARIA labelled-by property
          labelledBy:findLabelForElement(elem),
          // Have we been passed a describedBy to set the ARIA decribed-by property...
          describedBy:(options.describedBy && document.getElementById(options.describedBy)) ? options.describedBy : describedBy && document.getElementById(describedBy) ? describedBy : "",
          // Callback functions
          callbacks:options.callbackFunctions ? options.callbackFunctions : {},
          // Days of the week to highlight (normally the weekend)
          highlightDays:options.highlightDays && options.highlightDays.length && options.highlightDays.length == 7 ? options.highlightDays : [0,0,0,0,0,1,1],
          // Days of the week to disable
          disabledDays:options.disabledDays && options.disabledDays.length && options.disabledDays.length == 7 ? options.disabledDays : [0,0,0,0,0,0,0]                                                                   
        };  

        if(options.disabledDates) {
          if(options.enabledDates) delete(options.enabledDates);
          opts.disabledDates = {};
          var startD;
          for(startD in options.disabledDates) {                                
            if((String(startD).search(wcDateRegExp) != -1 && options.disabledDates[startD] == 1) || (String(startD).search(rangeRegExp) != -1 && String(options.disabledDates[startD]).search(rangeRegExp) != -1)) {
              opts.disabledDates[startD] = options.disabledDates[startD];                                           
            };
          };
        } else if(options.enabledDates) {                        
          var startD;
          opts.enabledDates = {};
          for(startD in options.enabledDates) {                                
            if((String(startD).search(wcDateRegExp) != -1 && options.enabledDates[startD] == 1) || (String(startD).search(rangeRegExp) != -1 && String(options.enabledDates[startD]).search(rangeRegExp) != -1)) {
              opts.enabledDates[startD] = options.enabledDates[startD];                                                                            
            };
          };
        };                

        datePickers[options.id] = new datePicker(opts);               
        datePickers[options.id].callback("create", datePickers[options.id].createCbArgObj());                  
      };

      // Used by the button to dictate whether to open or close the datePicker
      var isVisible = function(id) {
        return (!id || !(id in datePickers)) ? false : datePickers[id].visible;
      };  

      addEvent(window, 'unload', destroy);

      return {
        // General event functions...
        addEvent:               function(obj, type, fn) { return addEvent(obj, type, fn); },
        removeEvent:            function(obj, type, fn) { return removeEvent(obj, type, fn); },
        stopEvent:              function(e) { return stopEvent(e); },
        // Show a single popup datepicker
        show:                   function(inpID) { return showDatePicker(inpID, false); },
        // Hide a popup datepicker
        hide:                   function(inpID) { return hideDatePicker(inpID); },                
        // Create a new datepicker
        createDatePicker:       function(options) { addDatePicker(options); },
        // Destroy a datepicker (remove events and DOM nodes)               
        destroyDatePicker:      function(inpID) { destroySingleDatePicker(inpID); },
        // Check datePicker form elements exist, if not, destroy the datepicker
        cleanUp:                function() { cleanUp(); },                    
        // Pretty print a date object according to the format passed in               
        printFormattedDate:     function(dt, fmt, useImportedLocale) { return printFormattedDate(dt, fmt, useImportedLocale); },
        // Update the internal date using the form element value
        setDateFromInput:       function(inpID) { if(!inpID || !(inpID in datePickers)) return false; datePickers[inpID].setDateFromInput(); },
        // Set low and high date ranges
        setRangeLow:            function(inpID, yyyymmdd) { if(!inpID || !(inpID in datePickers)) { return false; }; datePickers[inpID].setRangeLow(yyyymmdd); },
        setRangeHigh:           function(inpID, yyyymmdd) { if(!inpID || !(inpID in datePickers)) { return false; }; datePickers[inpID].setRangeHigh(yyyymmdd); },
        // Set bespoke titles for a datepicker instance
        setBespokeTitles:       function(inpID, titles) {if(!inpID || !(inpID in datePickers)) { return false; }; datePickers[inpID].setBespokeTitles(titles); },
        // Add bespoke titles for a datepicker instance
        addBespokeTitles:       function(inpID, titles) {if(!inpID || !(inpID in datePickers)) { return false; }; datePickers[inpID].addBespokeTitles(titles); },                
        // Attempt to parse a valid date from a date string using the passed in format
        parseDateString:        function(str, format) { return parseDateString(str, format); },
        // Change global configuration parameters
        setGlobalVars:          function(json) { affectJSON(json); },
        setSelectedDate:        function(inpID, yyyymmdd) { if(!inpID || !(inpID in datePickers)) { return false; }; datePickers[inpID].setSelectedDate(yyyymmdd); },
        // Is the date valid for selection i.e. not outside ranges etc
        dateValidForSelection:  function(inpID, dt) { if(!inpID || !(inpID in datePickers)) return false; return datePickers[inpID].canDateBeSelected(dt); },
        // Add disabled and enabled dates
        addDisabledDates:       function(inpID, dts) { if(!inpID || !(inpID in datePickers)) return false; datePickers[inpID].addDisabledDates(dts); },
        setDisabledDates:       function(inpID, dts) { if(!inpID || !(inpID in datePickers)) return false; datePickers[inpID].setDisabledDates(dts); },
        addEnabledDates:        function(inpID, dts) { if(!inpID || !(inpID in datePickers)) return false; datePickers[inpID].addEnabledDates(dts); },
        setEnabledDates:        function(inpID, dts) { if(!inpID || !(inpID in datePickers)) return false; datePickers[inpID].setEnabledDates(dts); },
        // Disable and enable the datepicker
        disable:                function(inpID) { if(!inpID || !(inpID in datePickers)) return false; datePickers[inpID].disableDatePicker(); },
        enable:                 function(inpID) { if(!inpID || !(inpID in datePickers)) return false; datePickers[inpID].enableDatePicker(); },
        // Set the cursor date
        setCursorDate:          function(inpID, yyyymmdd) { if(!inpID || !(inpID in datePickers)) return false; datePickers[inpID].setCursorDate(yyyymmdd); },
        // Whats the currently selected date
        getSelectedDate:        function(inpID) { return (!inpID || !(inpID in datePickers)) ? false : datePickers[inpID].returnSelectedDate(); },
        // Attempt to update the language (causes a redraw of all datepickers on the page)
        loadLanguage:           function() { loadLanguage(); },
        // Set the debug level i.e. throw errors or fail silently
        setDebug:               function(dbg) { debug = !!(dbg); }                                                           
      }; 
  })();