/*
        DatePicker v5.1 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",
            tParts = "M|F|D|l",
            formatYM = false,
            formatYMD = false,
            kbEvent = false,
            validFmtRegExp = /^((sp|dt|sl|ds|cc)|([d|D|l|j|N|w|S|W|M|F|m|n|t|Y|o|y|O|p]))(-((sp|dt|sl|ds|cc)|([d|D|l|j|N|w|S|W|M|F|m|n|t|Y|o|y|O|p])))*$/,
            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]))$/;

    void 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;
                    };
            };
        };
    };

    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|split|formats|nodrag/.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 = 90;
        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 = 6;
        this.interval = new Date();
        this.clickActivated = false;
        this.noFocus = true;
        this.kbEvent = false;
        this.disabledDates = {};
        this.enabledDates = {};
        this.delayedUpdate = false;

        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
        @*/

        this.callbacks = {};
        for (var thing in options.callbacks) {
            this.callbacks[thing] = options.callbacks[thing];
        };

        this.formElements = {};
        for (var thing in options.formElements) {
            this.formElements[thing] = options.formElements[thing];
        };

        this.formatMasks = {};
        for (var thing in options.formatMasks) {
            this.formatMasks[thing] = options.formatMasks[thing];
        };

        // Adjust time to stop daylight savings madness on windows
        this.date.setHours(5);

        this.changeHandler = function () {
            o.setDateFromInput();
        };
        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.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];

                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.reposition();
            };

            this.setDateFromInput();

            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(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(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 = 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) {
            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", "0");
            but.tabIndex = 0;
        };
    };

    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.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 = {};

        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];
            };
        };

        //this.checkSelectedDate();

        if (!this.inUpdate) this.setDateFromInput();
    };
    datePicker.prototype.addEnabledDates = function (dateObj) {
        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.enabledDates[startD] = dateObj[startD];
            };
        };

        if (!this.inUpdate) this.setDateFromInput();
    };
    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) {
                obj[dt1] = 1;
                continue;
            };

            // Range
            if (Number(dt1.substr(0, 6)) == +String(this.firstDateShown).substr(0, 6) && Number(dt2.substr(0, 6)) == +String(this.lastDateShown).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);
                elem.onchange();
            };
        };

        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 () {
        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(date),
                        "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": y,
                        "o": y,
                        "Y": String(y).substr(2, 2),
                        "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;
                case "z": // The day of the year (starting from 0): 0 - 365
                    if (str.search(/^([0-9]|[1-9][0-9]|[12][0-9]{2}|3[0-5][0-9]|36[0-5])/) != -1) {
                        str = str.substr(str.match(/^([0-9]|[1-9][0-9]|[12][0-9]{2}|3[0-5][0-9]|36[0-5])/)[0].length);

                    };
                    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
                case "o": // ISO-8601 year number. This has the same value as Y
                    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 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];
            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 - calculated automatically by the script
            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),
            // Final opacity 
            finalOpacity: !!!(options.staticPos) && options.finalOpacity && (options.finalOpacity > 20 && options.finalOpacity <= 100) ? Math.min(+options.finalOpacity, 99) : 90,
            // 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); },
        // 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); },
        // Is the date valid for selection i.e. not outside ranges etc
        dateValidForSelection: function (inpID, dt) { if (!inpID || !(inpID in datePickers)) return false; 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) { if (!inpID || !(inpID in datePickers)) return false; return 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); }
    };
})();
