/*

* jQuery UI Timepicker
*
* Copyright 2010-2013, Francois Gelinas
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://fgelinas.com/code/timepicker
*
* Depends:
*      jquery.ui.core.js
*  jquery.ui.position.js (only if position settings are used)
*
* Change version 0.1.0 - moved the t-rex up here
*
                                                 ____
      ___                                      .-~. /_"-._
     `-._~-.                                  / /_ "~o\  :Y
         \  \                                / : \~x.  ` ')
          ]  Y                              /  |  Y< ~-.__j
         /   !                        _.--~T : l  l<  /.-~
        /   /                 ____.--~ .   ` l /~\ \<|Y
       /   /             .-~~"        /| .    ',-~\ \L|
      /   /             /     .^   \ Y~Y \.^>/l_   "--'
     /   Y           .-"(  .  l__  j_j l_/ /~_.-~    .
    Y    l          /    \  )    ~~~." / `/"~ / \.__/l_
    |     \     _.-"      ~-{__     l  :  l._Z~-.___.--~
    |      ~---~           /   ~~"---\_  ' __[>
    l  .                _.^   ___     _>-y~
     \  \     .      .-~   .-~   ~>--"  /
      \  ~---"            /     ./  _.-'
       "-.,_____.,_  _.--~\     _.-~
                   ~~     (   _}       -Row
                          `. ~(
                            )  \
                           /,`--'~\--'~\
                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                            ->T-Rex<-

*/

(function ($) {

$.extend($.ui, { timepicker: { version: "0.3.3"} });

var PROP_NAME = 'timepicker',
    tpuuid = new Date().getTime();

/* Time picker manager.
Use the singleton instance of this class, $.timepicker, to interact with the time picker.
Settings for (groups of) time pickers are maintained in an instance object,
allowing multiple different settings on the same page. */

function Timepicker() {
    this.debug = true; // Change this to true to start debugging
    this._curInst = null; // The current instance in use
    this._disabledInputs = []; // List of time picker inputs that have been disabled
    this._timepickerShowing = false; // True if the popup picker is showing , false if not
    this._inDialog = false; // True if showing within a "dialog", false if not
    this._dialogClass = 'ui-timepicker-dialog'; // The name of the dialog marker class
    this._mainDivId = 'ui-timepicker-div'; // The ID of the main timepicker division
    this._inlineClass = 'ui-timepicker-inline'; // The name of the inline marker class
    this._currentClass = 'ui-timepicker-current'; // The name of the current hour / minutes marker class
    this._dayOverClass = 'ui-timepicker-days-cell-over'; // The name of the day hover marker class

    this.regional = []; // Available regional settings, indexed by language code
    this.regional[''] = { // Default regional settings
        hourText: 'Hour',           // Display text for hours section
        minuteText: 'Minute',       // Display text for minutes link
        amPmText: ['AM', 'PM'],     // Display text for AM PM
        closeButtonText: 'Done',        // Text for the confirmation button (ok button)
        nowButtonText: 'Now',           // Text for the now button
        deselectButtonText: 'Deselect'  // Text for the deselect button
    };
    this._defaults = { // Global defaults for all the time picker instances
        showOn: 'focus',    // 'focus' for popup on focus,
                            // 'button' for trigger button, or 'both' for either (not yet implemented)
        button: null,                   // 'button' element that will trigger the timepicker
        showAnim: 'fadeIn',             // Name of jQuery animation for popup
        showOptions: {},                // Options for enhanced animations
        appendText: '',                 // Display text following the input box, e.g. showing the format

        beforeShow: null,               // Define a callback function executed before the timepicker is shown
        onSelect: null,                 // Define a callback function when a hour / minutes is selected
        onClose: null,                  // Define a callback function when the timepicker is closed

        timeSeparator: ':',             // The character to use to separate hours and minutes.
        periodSeparator: ' ',           // The character to use to separate the time from the time period.
        showPeriod: false,              // Define whether or not to show AM/PM with selected time
        showPeriodLabels: true,         // Show the AM/PM labels on the left of the time picker
        showLeadingZero: true,          // Define whether or not to show a leading zero for hours < 10. [true/false]
        showMinutesLeadingZero: true,   // Define whether or not to show a leading zero for minutes < 10.
        altField: '',                   // Selector for an alternate field to store selected time into
        defaultTime: 'now',             // Used as default time when input field is empty or for inline timePicker
                                        // (set to 'now' for the current time, '' for no highlighted time)
        myPosition: 'left top',         // Position of the dialog relative to the input.
                                        // see the position utility for more info : http://jqueryui.com/demos/position/
        atPosition: 'left bottom',      // Position of the input element to match
                                        // Note : if the position utility is not loaded, the timepicker will attach left top to left bottom
        //NEW: 2011-02-03
        onHourShow: null,                       // callback for enabling / disabling on selectable hours  ex : function(hour) { return true; }
        onMinuteShow: null,             // callback for enabling / disabling on time selection  ex : function(hour,minute) { return true; }

        hours: {
            starts: 0,                  // first displayed hour
            ends: 23                    // last displayed hour
        },
        minutes: {
            starts: 0,                  // first displayed minute
            ends: 55,                   // last displayed minute
            interval: 5,                // interval of displayed minutes
            manual: []                  // optional extra manual entries for minutes
        },
        rows: 4,                        // number of rows for the input tables, minimum 2, makes more sense if you use multiple of 2
        // 2011-08-05 0.2.4
        showHours: true,                // display the hours section of the dialog
        showMinutes: true,              // display the minute section of the dialog
        optionalMinutes: false,         // optionally parse inputs of whole hours with minutes omitted

        // buttons
        showCloseButton: false,         // shows an OK button to confirm the edit
        showNowButton: false,           // Shows the 'now' button
        showDeselectButton: false,       // Shows the deselect time button

        maxTime: {
            hour: null,
            minute: null
        },
        minTime: {
            hour: null,
            minute: null
        }

    };
    $.extend(this._defaults, this.regional['']);

    this.tpDiv = $('<div id="' + this._mainDivId + '" class="ui-timepicker ui-widget ui-helper-clearfix ui-corner-all " style="display: none"></div>');
}

$.extend(Timepicker.prototype, {
    /* Class name added to elements to indicate already configured with a time picker. */
    markerClassName: 'hasTimepicker',

    /* Debug logging (if enabled). */
    log: function () {
        if (this.debug)
            console.log.apply('', arguments);
    },

    _widgetTimepicker: function () {
        return this.tpDiv;
    },

    /* Override the default settings for all instances of the time picker.
    @param  settings  object - the new settings to use as defaults (anonymous object)
    @return the manager object */
    setDefaults: function (settings) {
        extendRemove(this._defaults, settings || {});
        return this;
    },

    /* Attach the time picker to a jQuery selection.
    @param  target    element - the target input field or division or span
    @param  settings  object - the new settings to use for this time picker instance (anonymous) */
    _attachTimepicker: function (target, settings) {
        // check for settings on the control itself - in namespace 'time:'
        var inlineSettings = null;
        for (var attrName in this._defaults) {
            var attrValue = target.getAttribute('time:' + attrName);
            if (attrValue) {
                inlineSettings = inlineSettings || {};
                try {
                    inlineSettings[attrName] = eval(attrValue);
                } catch (err) {
                    inlineSettings[attrName] = attrValue;
                }
            }
        }
        var nodeName = target.nodeName.toLowerCase();
        var inline = (nodeName == 'div' || nodeName == 'span');

        if (!target.id) {
            this.uuid += 1;
            target.id = 'tp' + this.uuid;
        }
        var inst = this._newInst($(target), inline);
        inst.settings = $.extend({}, settings || {}, inlineSettings || {});
        if (nodeName == 'input') {
            this._connectTimepicker(target, inst);
            // init inst.hours and inst.minutes from the input value
            this._setTimeFromField(inst);
        } else if (inline) {
            this._inlineTimepicker(target, inst);
        }

    },

    /* Create a new instance object. */
    _newInst: function (target, inline) {
        var id = target[0].id.replace(/([^A-Za-z0-9_-])/g, '\\\\$1'); // escape jQuery meta chars
        return {
            id: id, input: target, // associated target
            inline: inline, // is timepicker inline or not :
            tpDiv: (!inline ? this.tpDiv : // presentation div
                $('<div class="' + this._inlineClass + ' ui-timepicker ui-widget  ui-helper-clearfix"></div>'))
        };
    },

    /* Attach the time picker to an input field. */
    _connectTimepicker: function (target, inst) {
        var input = $(target);
        inst.append = $([]);
        inst.trigger = $([]);
        if (input.hasClass(this.markerClassName)) { return; }
        this._attachments(input, inst);
        input.addClass(this.markerClassName).
            keydown(this._doKeyDown).
            keyup(this._doKeyUp).
            bind("setData.timepicker", function (event, key, value) {
                inst.settings[key] = value;
            }).
            bind("getData.timepicker", function (event, key) {
                return this._get(inst, key);
            });
        $.data(target, PROP_NAME, inst);
    },

    /* Handle keystrokes. */
    _doKeyDown: function (event) {
        var inst = $.timepicker._getInst(event.target);
        var handled = true;
        inst._keyEvent = true;
        if ($.timepicker._timepickerShowing) {
            switch (event.keyCode) {
                case 9: $.timepicker._hideTimepicker();
                    handled = false;
                    break; // hide on tab out
                case 13:
                    $.timepicker._updateSelectedValue(inst);
                    $.timepicker._hideTimepicker();

                                            return false; // don't submit the form
                                            break; // select the value on enter
                case 27: $.timepicker._hideTimepicker();
                    break; // hide on escape
                default: handled = false;
            }
        }
        else if (event.keyCode == 36 && event.ctrlKey) { // display the time picker on ctrl+home
            $.timepicker._showTimepicker(this);
        }
        else {
            handled = false;
        }
        if (handled) {
            event.preventDefault();
            event.stopPropagation();
        }
    },

    /* Update selected time on keyUp */
    /* Added verion 0.0.5 */
    _doKeyUp: function (event) {
        var inst = $.timepicker._getInst(event.target);
        $.timepicker._setTimeFromField(inst);
        $.timepicker._updateTimepicker(inst);
    },

    /* Make attachments based on settings. */
    _attachments: function (input, inst) {
        var appendText = this._get(inst, 'appendText');
        var isRTL = this._get(inst, 'isRTL');
        if (inst.append) { inst.append.remove(); }
        if (appendText) {
            inst.append = $('<span class="' + this._appendClass + '">' + appendText + '</span>');
            input[isRTL ? 'before' : 'after'](inst.append);
        }
        input.unbind('focus.timepicker', this._showTimepicker);
        input.unbind('click.timepicker', this._adjustZIndex);

        if (inst.trigger) { inst.trigger.remove(); }

        var showOn = this._get(inst, 'showOn');
        if (showOn == 'focus' || showOn == 'both') { // pop-up time picker when in the marked field
            input.bind("focus.timepicker", this._showTimepicker);
            input.bind("click.timepicker", this._adjustZIndex);
        }
        if (showOn == 'button' || showOn == 'both') { // pop-up time picker when 'button' element is clicked
            var button = this._get(inst, 'button');

            // Add button if button element is not set
            if(button == null) {
                button = $('<button class="ui-timepicker-trigger" type="button">...</button>');
                input.after(button);
            }

            $(button).bind("click.timepicker", function () {
                if ($.timepicker._timepickerShowing && $.timepicker._lastInput == input[0]) {
                    $.timepicker._hideTimepicker();
                } else if (!inst.input.is(':disabled')) {
                    $.timepicker._showTimepicker(input[0]);
                }
                return false;
            });

        }
    },

    /* Attach an inline time picker to a div. */
    _inlineTimepicker: function(target, inst) {
        var divSpan = $(target);
        if (divSpan.hasClass(this.markerClassName))
            return;
        divSpan.addClass(this.markerClassName).append(inst.tpDiv).
            bind("setData.timepicker", function(event, key, value){
                inst.settings[key] = value;
            }).bind("getData.timepicker", function(event, key){
                return this._get(inst, key);
            });
        $.data(target, PROP_NAME, inst);

        this._setTimeFromField(inst);
        this._updateTimepicker(inst);
        inst.tpDiv.show();
    },

    _adjustZIndex: function(input) {
        input = input.target || input;
        var inst = $.timepicker._getInst(input);
        inst.tpDiv.css('zIndex', $.timepicker._getZIndex(input) +1);
    },

    /* Pop-up the time picker for a given input field.
    @param  input  element - the input field attached to the time picker or
    event - if triggered by focus */
    _showTimepicker: function (input) {
        input = input.target || input;
        if (input.nodeName.toLowerCase() != 'input') { input = $('input', input.parentNode)[0]; } // find from button/image trigger

        if ($.timepicker._isDisabledTimepicker(input) || $.timepicker._lastInput == input) { return; } // already here

        // fix v 0.0.8 - close current timepicker before showing another one
        $.timepicker._hideTimepicker();

        var inst = $.timepicker._getInst(input);
        if ($.timepicker._curInst && $.timepicker._curInst != inst) {
            $.timepicker._curInst.tpDiv.stop(true, true);
        }
        var beforeShow = $.timepicker._get(inst, 'beforeShow');
        extendRemove(inst.settings, (beforeShow ? beforeShow.apply(input, [input, inst]) : {}));
        inst.lastVal = null;
        $.timepicker._lastInput = input;

        $.timepicker._setTimeFromField(inst);

        // calculate default position
        if ($.timepicker._inDialog) { input.value = ''; } // hide cursor
        if (!$.timepicker._pos) { // position below input
            $.timepicker._pos = $.timepicker._findPos(input);
            $.timepicker._pos[1] += input.offsetHeight; // add the height
        }
        var isFixed = false;
        $(input).parents().each(function () {
            isFixed |= $(this).css('position') == 'fixed';
            return !isFixed;
        });

        var offset = { left: $.timepicker._pos[0], top: $.timepicker._pos[1] };

        $.timepicker._pos = null;
        // determine sizing offscreen
        inst.tpDiv.css({ position: 'absolute', display: 'block', top: '-1000px' });
        $.timepicker._updateTimepicker(inst);

        // position with the ui position utility, if loaded
        if ( ( ! inst.inline )  && ( typeof $.ui.position == 'object' ) ) {
            inst.tpDiv.position({
                of: inst.input,
                my: $.timepicker._get( inst, 'myPosition' ),
                at: $.timepicker._get( inst, 'atPosition' ),
                // offset: $( "#offset" ).val(),
                // using: using,
                collision: 'flip'
            });
            var offset = inst.tpDiv.offset();
            $.timepicker._pos = [offset.top, offset.left];
        }

        // reset clicked state
        inst._hoursClicked = false;
        inst._minutesClicked = false;

        // fix width for dynamic number of time pickers
        // and adjust position before showing
        offset = $.timepicker._checkOffset(inst, offset, isFixed);
        inst.tpDiv.css({ position: ($.timepicker._inDialog && $.blockUI ?
                        'static' : (isFixed ? 'fixed' : 'absolute')), display: 'none',
            left: offset.left + 'px', top: offset.top + 'px'
        });
        if ( ! inst.inline ) {
            var showAnim = $.timepicker._get(inst, 'showAnim');
            var duration = $.timepicker._get(inst, 'duration');

            var postProcess = function () {
                $.timepicker._timepickerShowing = true;
                var borders = $.timepicker._getBorders(inst.tpDiv);
                inst.tpDiv.find('iframe.ui-timepicker-cover'). // IE6- only
                                    css({ left: -borders[0], top: -borders[1],
                                        width: inst.tpDiv.outerWidth(), height: inst.tpDiv.outerHeight()
                                    });
            };

            // Fixed the zIndex problem for real (I hope) - FG - v 0.2.9
            $.timepicker._adjustZIndex(input);
            //inst.tpDiv.css('zIndex', $.timepicker._getZIndex(input) +1);

            if ($.effects && $.effects[showAnim]) {
                inst.tpDiv.show(showAnim, $.timepicker._get(inst, 'showOptions'), duration, postProcess);
            }
            else {
                inst.tpDiv.show((showAnim ? duration : null), postProcess);
            }
            if (!showAnim || !duration) { postProcess(); }
            if (inst.input.is(':visible') && !inst.input.is(':disabled')) { inst.input.focus(); }
            $.timepicker._curInst = inst;
        }
    },

    // This is an enhanced copy of the zIndex function of UI core 1.8.?? For backward compatibility.
    // Enhancement returns maximum zindex value discovered while traversing parent elements,
    // rather than the first zindex value found. Ensures the timepicker popup will be in front,
    // even in funky scenarios like non-jq dialog containers with large fixed zindex values and
    // nested zindex-influenced elements of their own.
    _getZIndex: function (target) {
        var elem = $(target);
        var maxValue = 0;
        var position, value;
        while (elem.length && elem[0] !== document) {
            position = elem.css("position");
            if (position === "absolute" || position === "relative" || position === "fixed") {
                value = parseInt(elem.css("zIndex"), 10);
                if (!isNaN(value) && value !== 0) {
                    if (value > maxValue) { maxValue = value; }
                }
            }
            elem = elem.parent();
        }

        return maxValue;
    },

    /* Refresh the time picker
       @param   target  element - The target input field or inline container element. */
    _refreshTimepicker: function(target) {
        var inst = this._getInst(target);
        if (inst) {
            this._updateTimepicker(inst);
        }
    },

    /* Generate the time picker content. */
    _updateTimepicker: function (inst) {
        inst.tpDiv.empty().append(this._generateHTML(inst));
        this._rebindDialogEvents(inst);

    },

    _rebindDialogEvents: function (inst) {
        var borders = $.timepicker._getBorders(inst.tpDiv),
            self = this;
        inst.tpDiv
                    .find('iframe.ui-timepicker-cover') // IE6- only
                            .css({ left: -borders[0], top: -borders[1],
                                width: inst.tpDiv.outerWidth(), height: inst.tpDiv.outerHeight()
                            })
                    .end()
        // after the picker html is appended bind the click & double click events (faster in IE this way
        // then letting the browser interpret the inline events)
        // the binding for the minute cells also exists in _updateMinuteDisplay
        .find('.ui-timepicker-minute-cell')
            .unbind()
            .bind("click", { fromDoubleClick:false }, $.proxy($.timepicker.selectMinutes, this))
            .bind("dblclick", { fromDoubleClick:true }, $.proxy($.timepicker.selectMinutes, this))
        .end()
        .find('.ui-timepicker-hour-cell')
            .unbind()
            .bind("click", { fromDoubleClick:false }, $.proxy($.timepicker.selectHours, this))
            .bind("dblclick", { fromDoubleClick:true }, $.proxy($.timepicker.selectHours, this))
        .end()
                    .find('.ui-timepicker td a')
            .unbind()
                            .bind('mouseout', function () {
                                $(this).removeClass('ui-state-hover');
                                if (this.className.indexOf('ui-timepicker-prev') != -1) $(this).removeClass('ui-timepicker-prev-hover');
                                if (this.className.indexOf('ui-timepicker-next') != -1) $(this).removeClass('ui-timepicker-next-hover');
                            })
                            .bind('mouseover', function () {
                                if ( ! self._isDisabledTimepicker(inst.inline ? inst.tpDiv.parent()[0] : inst.input[0])) {
                                    $(this).parents('.ui-timepicker-calendar').find('a').removeClass('ui-state-hover');
                                    $(this).addClass('ui-state-hover');
                                    if (this.className.indexOf('ui-timepicker-prev') != -1) $(this).addClass('ui-timepicker-prev-hover');
                                    if (this.className.indexOf('ui-timepicker-next') != -1) $(this).addClass('ui-timepicker-next-hover');
                                }
                            })
                    .end()
                    .find('.' + this._dayOverClass + ' a')
                            .trigger('mouseover')
                    .end()
        .find('.ui-timepicker-now').bind("click", function(e) {
                $.timepicker.selectNow(e);
        }).end()
        .find('.ui-timepicker-deselect').bind("click",function(e) {
                $.timepicker.deselectTime(e);
        }).end()
        .find('.ui-timepicker-close').bind("click",function(e) {
                $.timepicker._hideTimepicker();
        }).end();
    },

    /* Generate the HTML for the current state of the time picker. */
    _generateHTML: function (inst) {

        var h, m, row, col, html, hoursHtml, minutesHtml = '',
            showPeriod = (this._get(inst, 'showPeriod') == true),
            showPeriodLabels = (this._get(inst, 'showPeriodLabels') == true),
            showLeadingZero = (this._get(inst, 'showLeadingZero') == true),
            showHours = (this._get(inst, 'showHours') == true),
            showMinutes = (this._get(inst, 'showMinutes') == true),
            amPmText = this._get(inst, 'amPmText'),
            rows = this._get(inst, 'rows'),
            amRows = 0,
            pmRows = 0,
            amItems = 0,
            pmItems = 0,
            amFirstRow = 0,
            pmFirstRow = 0,
            hours = Array(),
            hours_options = this._get(inst, 'hours'),
            hoursPerRow = null,
            hourCounter = 0,
            hourLabel = this._get(inst, 'hourText'),
            showCloseButton = this._get(inst, 'showCloseButton'),
            closeButtonText = this._get(inst, 'closeButtonText'),
            showNowButton = this._get(inst, 'showNowButton'),
            nowButtonText = this._get(inst, 'nowButtonText'),
            showDeselectButton = this._get(inst, 'showDeselectButton'),
            deselectButtonText = this._get(inst, 'deselectButtonText'),
            showButtonPanel = showCloseButton || showNowButton || showDeselectButton;

        // prepare all hours and minutes, makes it easier to distribute by rows
        for (h = hours_options.starts; h <= hours_options.ends; h++) {
            hours.push (h);
        }
        hoursPerRow = Math.ceil(hours.length / rows); // always round up

        if (showPeriodLabels) {
            for (hourCounter = 0; hourCounter < hours.length; hourCounter++) {
                if (hours[hourCounter] < 12) {
                    amItems++;
                }
                else {
                    pmItems++;
                }
            }
            hourCounter = 0;

            amRows = Math.floor(amItems / hours.length * rows);
            pmRows = Math.floor(pmItems / hours.length * rows);

            // assign the extra row to the period that is more densely populated
            if (rows != amRows + pmRows) {
                // Make sure: AM Has Items and either PM Does Not, AM has no rows yet, or AM is more dense
                if (amItems && (!pmItems || !amRows || (pmRows && amItems / amRows >= pmItems / pmRows))) {
                    amRows++;
                } else {
                    pmRows++;
                }
            }
            amFirstRow = Math.min(amRows, 1);
            pmFirstRow = amRows + 1;

            if (amRows == 0) {
                hoursPerRow = Math.ceil(pmItems / pmRows);
            } else if (pmRows == 0) {
                hoursPerRow = Math.ceil(amItems / amRows);
            } else {
                hoursPerRow = Math.ceil(Math.max(amItems / amRows, pmItems / pmRows));
            }
        }

        html = '<table class="ui-timepicker-table ui-widget-content ui-corner-all"><tr>';

        if (showHours) {

            html += '<td class="ui-timepicker-hours">' +
                    '<div class="ui-timepicker-title ui-widget-header ui-helper-clearfix ui-corner-all">' +
                    hourLabel +
                    '</div>' +
                    '<table class="ui-timepicker">';

            for (row = 1; row <= rows; row++) {
                html += '<tr>';
                // AM
                if (row == amFirstRow && showPeriodLabels) {
                    html += '<th rowspan="' + amRows.toString() + '" class="periods" scope="row">' + amPmText[0] + '</th>';
                }
                // PM
                if (row == pmFirstRow && showPeriodLabels) {
                    html += '<th rowspan="' + pmRows.toString() + '" class="periods" scope="row">' + amPmText[1] + '</th>';
                }
                for (col = 1; col <= hoursPerRow; col++) {
                    if (showPeriodLabels && row < pmFirstRow && hours[hourCounter] >= 12) {
                        html += this._generateHTMLHourCell(inst, undefined, showPeriod, showLeadingZero);
                    } else {
                        html += this._generateHTMLHourCell(inst, hours[hourCounter], showPeriod, showLeadingZero);
                        hourCounter++;
                    }
                }
                html += '</tr>';
            }
            html += '</table>' + // Close the hours cells table
                    '</td>'; // Close the Hour td
        }

        if (showMinutes) {
            html += '<td class="ui-timepicker-minutes">';
            html += this._generateHTMLMinutes(inst);
            html += '</td>';
        }

        html += '</tr>';

        if (showButtonPanel) {
            var buttonPanel = '<tr><td colspan="3"><div class="ui-timepicker-buttonpane ui-widget-content">';
            if (showNowButton) {
                buttonPanel += '<button type="button" class="ui-timepicker-now ui-state-default ui-corner-all" '
                               + ' data-timepicker-instance-id="#' + inst.id.replace(/\\\\/g,"\\") + '" >'
                               + nowButtonText + '</button>';
            }
            if (showDeselectButton) {
                buttonPanel += '<button type="button" class="ui-timepicker-deselect ui-state-default ui-corner-all" '
                               + ' data-timepicker-instance-id="#' + inst.id.replace(/\\\\/g,"\\") + '" >'
                               + deselectButtonText + '</button>';
            }
            if (showCloseButton) {
                buttonPanel += '<button type="button" class="ui-timepicker-close ui-state-default ui-corner-all" '
                               + ' data-timepicker-instance-id="#' + inst.id.replace(/\\\\/g,"\\") + '" >'
                               + closeButtonText + '</button>';
            }

            html += buttonPanel + '</div></td></tr>';
        }
        html += '</table>';

        return html;
    },

    /* Special function that update the minutes selection in currently visible timepicker
     * called on hour selection when onMinuteShow is defined  */
    _updateMinuteDisplay: function (inst) {
        var newHtml = this._generateHTMLMinutes(inst);
        inst.tpDiv.find('td.ui-timepicker-minutes').html(newHtml);
        this._rebindDialogEvents(inst);
            // after the picker html is appended bind the click & double click events (faster in IE this way
            // then letting the browser interpret the inline events)
            // yes I know, duplicate code, sorry

/* .find('.ui-timepicker-minute-cell')

.bind("click", { fromDoubleClick:false }, $.proxy($.timepicker.selectMinutes, this))
.bind("dblclick", { fromDoubleClick:true }, $.proxy($.timepicker.selectMinutes, this));

*/

    },

    /*
     * Generate the minutes table
     * This is separated from the _generateHTML function because is can be called separately (when hours changes)
     */
    _generateHTMLMinutes: function (inst) {

        var m, row, html = '',
            rows = this._get(inst, 'rows'),
            minutes = Array(),
            minutes_options = this._get(inst, 'minutes'),
            minutesPerRow = null,
            minuteCounter = 0,
            showMinutesLeadingZero = (this._get(inst, 'showMinutesLeadingZero') == true),
            onMinuteShow = this._get(inst, 'onMinuteShow'),
            minuteLabel = this._get(inst, 'minuteText');

        if ( ! minutes_options.starts) {
            minutes_options.starts = 0;
        }
        if ( ! minutes_options.ends) {
            minutes_options.ends = 59;
        }
        if ( ! minutes_options.manual) {
            minutes_options.manual = [];
        }
        for (m = minutes_options.starts; m <= minutes_options.ends; m += minutes_options.interval) {
            minutes.push(m);
        }
        for (i = 0; i < minutes_options.manual.length;i++) {
            var currMin = minutes_options.manual[i];

            // Validate & filter duplicates of manual minute input
            if (typeof currMin != 'number' || currMin < 0 || currMin > 59 || $.inArray(currMin, minutes) >= 0) {
                continue;
            }
            minutes.push(currMin);
        }

        // Sort to get correct order after adding manual minutes
        // Use compare function to sort by number, instead of string (default)
        minutes.sort(function(a, b) {
            return a-b;
        });

        minutesPerRow = Math.round(minutes.length / rows + 0.49); // always round up

        /*
         * The minutes table
         */
        // if currently selected minute is not enabled, we have a problem and need to select a new minute.
        if (onMinuteShow &&
            (onMinuteShow.apply((inst.input ? inst.input[0] : null), [inst.hours , inst.minutes]) == false) ) {
            // loop minutes and select first available
            for (minuteCounter = 0; minuteCounter < minutes.length; minuteCounter += 1) {
                m = minutes[minuteCounter];
                if (onMinuteShow.apply((inst.input ? inst.input[0] : null), [inst.hours, m])) {
                    inst.minutes = m;
                    break;
                }
            }
        }

        html += '<div class="ui-timepicker-title ui-widget-header ui-helper-clearfix ui-corner-all">' +
                minuteLabel +
                '</div>' +
                '<table class="ui-timepicker">';

        minuteCounter = 0;
        for (row = 1; row <= rows; row++) {
            html += '<tr>';
            while (minuteCounter < row * minutesPerRow) {
                var m = minutes[minuteCounter];
                var displayText = '';
                if (m !== undefined ) {
                    displayText = (m < 10) && showMinutesLeadingZero ? "0" + m.toString() : m.toString();
                }
                html += this._generateHTMLMinuteCell(inst, m, displayText);
                minuteCounter++;
            }
            html += '</tr>';
        }

        html += '</table>';

        return html;
    },

    /* Generate the content of a "Hour" cell */
    _generateHTMLHourCell: function (inst, hour, showPeriod, showLeadingZero) {

        var displayHour = hour;
        if ((hour > 12) && showPeriod) {
            displayHour = hour - 12;
        }
        if ((displayHour == 0) && showPeriod) {
            displayHour = 12;
        }
        if ((displayHour < 10) && showLeadingZero) {
            displayHour = '0' + displayHour;
        }

        var html = "";
        var enabled = true;
        var onHourShow = this._get(inst, 'onHourShow');             //custom callback
        var maxTime = this._get(inst, 'maxTime');
        var minTime = this._get(inst, 'minTime');

        if (hour == undefined) {
            html = '<td><span class="ui-state-default ui-state-disabled">&nbsp;</span></td>';
            return html;
        }

        if (onHourShow) {
            enabled = onHourShow.apply((inst.input ? inst.input[0] : null), [hour]);
        }

        if (enabled) {
            if ( !isNaN(parseInt(maxTime.hour)) && hour > maxTime.hour ) enabled = false;
            if ( !isNaN(parseInt(minTime.hour)) && hour < minTime.hour ) enabled = false;
        }

        if (enabled) {
            html = '<td class="ui-timepicker-hour-cell" data-timepicker-instance-id="#' + inst.id.replace(/\\\\/g,"\\") + '" data-hour="' + hour.toString() + '">' +
               '<a class="ui-state-default ' +
               (hour == inst.hours ? 'ui-state-active' : '') +
               '">' +
               displayHour.toString() +
               '</a></td>';
        }
        else {
            html =
                    '<td>' +
                            '<span class="ui-state-default ui-state-disabled ' +
                            (hour == inst.hours ? ' ui-state-active ' : ' ') +
                            '">' +
                            displayHour.toString() +
                            '</span>' +
                        '</td>';
        }
        return html;
    },

    /* Generate the content of a "Hour" cell */
    _generateHTMLMinuteCell: function (inst, minute, displayText) {
         var html = "";
         var enabled = true;
         var hour = inst.hours;
         var onMinuteShow = this._get(inst, 'onMinuteShow');                //custom callback
         var maxTime = this._get(inst, 'maxTime');
         var minTime = this._get(inst, 'minTime');

         if (onMinuteShow) {
             //NEW: 2011-02-03  we should give the hour as a parameter as well!
            enabled = onMinuteShow.apply((inst.input ? inst.input[0] : null), [inst.hours,minute]);         //trigger callback
         }

         if (minute == undefined) {
             html = '<td><span class="ui-state-default ui-state-disabled">&nbsp;</span></td>';
             return html;
         }

        if (enabled && hour !== null) {
            if ( !isNaN(parseInt(maxTime.hour)) && !isNaN(parseInt(maxTime.minute)) && hour >= maxTime.hour && minute > maxTime.minute ) enabled = false;
            if ( !isNaN(parseInt(minTime.hour)) && !isNaN(parseInt(minTime.minute)) && hour <= minTime.hour && minute < minTime.minute ) enabled = false;
        }

         if (enabled) {
                 html = '<td class="ui-timepicker-minute-cell" data-timepicker-instance-id="#' + inst.id.replace(/\\\\/g,"\\") + '" data-minute="' + minute.toString() + '" >' +
                       '<a class="ui-state-default ' +
                       (minute == inst.minutes ? 'ui-state-active' : '') +
                       '" >' +
                       displayText +
                       '</a></td>';
         }
         else {

            html = '<td>' +
                     '<span class="ui-state-default ui-state-disabled" >' +
                            displayText +
                     '</span>' +
             '</td>';
         }
         return html;
    },

    /* Detach a timepicker from its control.
       @param  target    element - the target input field or division or span */
    _destroyTimepicker: function(target) {
        var $target = $(target);
        var inst = $.data(target, PROP_NAME);
        if (!$target.hasClass(this.markerClassName)) {
            return;
        }
        var nodeName = target.nodeName.toLowerCase();
        $.removeData(target, PROP_NAME);
        if (nodeName == 'input') {
            inst.append.remove();
            inst.trigger.remove();
            $target.removeClass(this.markerClassName)
                .unbind('focus.timepicker', this._showTimepicker)
                .unbind('click.timepicker', this._adjustZIndex);
        } else if (nodeName == 'div' || nodeName == 'span')
            $target.removeClass(this.markerClassName).empty();
    },

    /* Enable the date picker to a jQuery selection.
       @param  target    element - the target input field or division or span */
    _enableTimepicker: function(target) {
        var $target = $(target),
            target_id = $target.attr('id'),
            inst = $.data(target, PROP_NAME);

        if (!$target.hasClass(this.markerClassName)) {
            return;
        }
        var nodeName = target.nodeName.toLowerCase();
        if (nodeName == 'input') {
            target.disabled = false;
            var button = this._get(inst, 'button');
            $(button).removeClass('ui-state-disabled').disabled = false;
            inst.trigger.filter('button').
                each(function() { this.disabled = false; }).end();
        }
        else if (nodeName == 'div' || nodeName == 'span') {
            var inline = $target.children('.' + this._inlineClass);
            inline.children().removeClass('ui-state-disabled');
            inline.find('button').each(
                function() { this.disabled = false }
            )
        }
        this._disabledInputs = $.map(this._disabledInputs,
            function(value) { return (value == target_id ? null : value); }); // delete entry
    },

    /* Disable the time picker to a jQuery selection.
       @param  target    element - the target input field or division or span */
    _disableTimepicker: function(target) {
        var $target = $(target);
        var inst = $.data(target, PROP_NAME);
        if (!$target.hasClass(this.markerClassName)) {
            return;
        }
        var nodeName = target.nodeName.toLowerCase();
        if (nodeName == 'input') {
            var button = this._get(inst, 'button');

            $(button).addClass('ui-state-disabled').disabled = true;
            target.disabled = true;

            inst.trigger.filter('button').
                each(function() { this.disabled = true; }).end();

        }
        else if (nodeName == 'div' || nodeName == 'span') {
            var inline = $target.children('.' + this._inlineClass);
            inline.children().addClass('ui-state-disabled');
            inline.find('button').each(
                function() { this.disabled = true }
            )

        }
        this._disabledInputs = $.map(this._disabledInputs,
            function(value) { return (value == target ? null : value); }); // delete entry
        this._disabledInputs[this._disabledInputs.length] = $target.attr('id');
    },

    /* Is the first field in a jQuery collection disabled as a timepicker?
    @param  target_id element - the target input field or division or span
    @return boolean - true if disabled, false if enabled */
    _isDisabledTimepicker: function (target_id) {
        if ( ! target_id) { return false; }
        for (var i = 0; i < this._disabledInputs.length; i++) {
            if (this._disabledInputs[i] == target_id) { return true; }
        }
        return false;
    },

    /* Check positioning to remain on screen. */
    _checkOffset: function (inst, offset, isFixed) {
        var tpWidth = inst.tpDiv.outerWidth();
        var tpHeight = inst.tpDiv.outerHeight();
        var inputWidth = inst.input ? inst.input.outerWidth() : 0;
        var inputHeight = inst.input ? inst.input.outerHeight() : 0;
        var viewWidth = document.documentElement.clientWidth + $(document).scrollLeft();
        var viewHeight = document.documentElement.clientHeight + $(document).scrollTop();

        offset.left -= (this._get(inst, 'isRTL') ? (tpWidth - inputWidth) : 0);
        offset.left -= (isFixed && offset.left == inst.input.offset().left) ? $(document).scrollLeft() : 0;
        offset.top -= (isFixed && offset.top == (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0;

        // now check if timepicker is showing outside window viewport - move to a better place if so.
        offset.left -= Math.min(offset.left, (offset.left + tpWidth > viewWidth && viewWidth > tpWidth) ?
                    Math.abs(offset.left + tpWidth - viewWidth) : 0);
        offset.top -= Math.min(offset.top, (offset.top + tpHeight > viewHeight && viewHeight > tpHeight) ?
                    Math.abs(tpHeight + inputHeight) : 0);

        return offset;
    },

    /* Find an object's position on the screen. */
    _findPos: function (obj) {
        var inst = this._getInst(obj);
        var isRTL = this._get(inst, 'isRTL');
        while (obj && (obj.type == 'hidden' || obj.nodeType != 1)) {
            obj = obj[isRTL ? 'previousSibling' : 'nextSibling'];
        }
        var position = $(obj).offset();
        return [position.left, position.top];
    },

    /* Retrieve the size of left and top borders for an element.
    @param  elem  (jQuery object) the element of interest
    @return  (number[2]) the left and top borders */
    _getBorders: function (elem) {
        var convert = function (value) {
            return { thin: 1, medium: 2, thick: 3}[value] || value;
        };
        return [parseFloat(convert(elem.css('border-left-width'))),
                    parseFloat(convert(elem.css('border-top-width')))];
    },

    /* Close time picker if clicked elsewhere. */
    _checkExternalClick: function (event) {
        if (!$.timepicker._curInst) { return; }
        var $target = $(event.target);
        if ($target[0].id != $.timepicker._mainDivId &&
                            $target.parents('#' + $.timepicker._mainDivId).length == 0 &&
                            !$target.hasClass($.timepicker.markerClassName) &&
                            !$target.hasClass($.timepicker._triggerClass) &&
                            $.timepicker._timepickerShowing && !($.timepicker._inDialog && $.blockUI))
            $.timepicker._hideTimepicker();
    },

    /* Hide the time picker from view.
    @param  input  element - the input field attached to the time picker */
    _hideTimepicker: function (input) {
        var inst = this._curInst;
        if (!inst || (input && inst != $.data(input, PROP_NAME))) { return; }
        if (this._timepickerShowing) {
            var showAnim = this._get(inst, 'showAnim');
            var duration = this._get(inst, 'duration');
            var postProcess = function () {
                $.timepicker._tidyDialog(inst);
                this._curInst = null;
            };
            if ($.effects && $.effects[showAnim]) {
                inst.tpDiv.hide(showAnim, $.timepicker._get(inst, 'showOptions'), duration, postProcess);
            }
            else {
                inst.tpDiv[(showAnim == 'slideDown' ? 'slideUp' :
                                        (showAnim == 'fadeIn' ? 'fadeOut' : 'hide'))]((showAnim ? duration : null), postProcess);
            }
            if (!showAnim) { postProcess(); }

            this._timepickerShowing = false;

            this._lastInput = null;
            if (this._inDialog) {
                this._dialogInput.css({ position: 'absolute', left: '0', top: '-100px' });
                if ($.blockUI) {
                    $.unblockUI();
                    $('body').append(this.tpDiv);
                }
            }
            this._inDialog = false;

            var onClose = this._get(inst, 'onClose');
             if (onClose) {
                 onClose.apply(
                     (inst.input ? inst.input[0] : null),
                                        [(inst.input ? inst.input.val() : ''), inst]);  // trigger custom callback
             }

        }
    },

    /* Tidy up after a dialog display. */
    _tidyDialog: function (inst) {
        inst.tpDiv.removeClass(this._dialogClass).unbind('.ui-timepicker');
    },

    /* Retrieve the instance data for the target control.
    @param  target  element - the target input field or division or span
    @return  object - the associated instance data
    @throws  error if a jQuery problem getting data */
    _getInst: function (target) {
        try {
            return $.data(target, PROP_NAME);
        }
        catch (err) {
            throw 'Missing instance data for this timepicker';
        }
    },

    /* Get a setting value, defaulting if necessary. */
    _get: function (inst, name) {
        return inst.settings[name] !== undefined ?
                    inst.settings[name] : this._defaults[name];
    },

    /* Parse existing time and initialise time picker. */
    _setTimeFromField: function (inst) {
        if (inst.input.val() == inst.lastVal) { return; }
        var defaultTime = this._get(inst, 'defaultTime');

        var timeToParse = defaultTime == 'now' ? this._getCurrentTimeRounded(inst) : defaultTime;
        if ((inst.inline == false) && (inst.input.val() != '')) { timeToParse = inst.input.val() }

        if (timeToParse instanceof Date) {
            inst.hours = timeToParse.getHours();
            inst.minutes = timeToParse.getMinutes();
        } else {
            var timeVal = inst.lastVal = timeToParse;
            if (timeToParse == '') {
                inst.hours = -1;
                inst.minutes = -1;
            } else {
                var time = this.parseTime(inst, timeVal);
                inst.hours = time.hours;
                inst.minutes = time.minutes;
            }
        }

        $.timepicker._updateTimepicker(inst);
    },

    /* Update or retrieve the settings for an existing time picker.
       @param  target  element - the target input field or division or span
       @param  name    object - the new settings to update or
                       string - the name of the setting to change or retrieve,
                       when retrieving also 'all' for all instance settings or
                       'defaults' for all global defaults
       @param  value   any - the new value for the setting
                   (omit if above is an object or to retrieve a value) */
    _optionTimepicker: function(target, name, value) {
        var inst = this._getInst(target);
        if (arguments.length == 2 && typeof name == 'string') {
            return (name == 'defaults' ? $.extend({}, $.timepicker._defaults) :
                (inst ? (name == 'all' ? $.extend({}, inst.settings) :
                this._get(inst, name)) : null));
        }
        var settings = name || {};
        if (typeof name == 'string') {
            settings = {};
            settings[name] = value;
        }
        if (inst) {
            extendRemove(inst.settings, settings);
            if (this._curInst == inst) {
                this._hideTimepicker();
                    this._updateTimepicker(inst);
            }
            if (inst.inline) {
                this._updateTimepicker(inst);
            }
        }
    },

    /* Set the time for a jQuery selection.
        @param  target  element - the target input field or division or span
        @param  time    String - the new time */
        _setTimeTimepicker: function(target, time) {
                var inst = this._getInst(target);
                if (inst) {
                        this._setTime(inst, time);
                    this._updateTimepicker(inst);
                    this._updateAlternate(inst, time);
                }
        },

    /* Set the time directly. */
    _setTime: function(inst, time, noChange) {
        var origHours = inst.hours;
        var origMinutes = inst.minutes;
        if (time instanceof Date) {
            inst.hours = time.getHours();
            inst.minutes = time.getMinutes();
        } else {
            var time = this.parseTime(inst, time);
            inst.hours = time.hours;
            inst.minutes = time.minutes;
        }

        if ((origHours != inst.hours || origMinutes != inst.minutes) && !noChange) {
            inst.input.trigger('change');
        }
        this._updateTimepicker(inst);
        this._updateSelectedValue(inst);
    },

    /* Return the current time, ready to be parsed, rounded to the closest minute by interval */
    _getCurrentTimeRounded: function (inst) {
        var currentTime = new Date(),
            currentMinutes = currentTime.getMinutes(),
            minutes_options = this._get(inst, 'minutes'),
            // round to closest interval
            adjustedMinutes = Math.round(currentMinutes / minutes_options.interval) * minutes_options.interval;
        currentTime.setMinutes(adjustedMinutes);
        return currentTime;
    },

    /*
    * Parse a time string into hours and minutes
    */
    parseTime: function (inst, timeVal) {
        var retVal = new Object();
        retVal.hours = -1;
        retVal.minutes = -1;

        if(!timeVal)
            return '';

        var timeSeparator = this._get(inst, 'timeSeparator'),
            amPmText = this._get(inst, 'amPmText'),
            showHours = this._get(inst, 'showHours'),
            showMinutes = this._get(inst, 'showMinutes'),
            optionalMinutes = this._get(inst, 'optionalMinutes'),
            showPeriod = (this._get(inst, 'showPeriod') == true),
            p = timeVal.indexOf(timeSeparator);

        // check if time separator found
        if (p != -1) {
            retVal.hours = parseInt(timeVal.substr(0, p), 10);
            retVal.minutes = parseInt(timeVal.substr(p + 1), 10);
        }
        // check for hours only
        else if ( (showHours) && ( !showMinutes || optionalMinutes ) ) {
            retVal.hours = parseInt(timeVal, 10);
        }
        // check for minutes only
        else if ( ( ! showHours) && (showMinutes) ) {
            retVal.minutes = parseInt(timeVal, 10);
        }

        if (showHours) {
            var timeValUpper = timeVal.toUpperCase();
            if ((retVal.hours < 12) && (showPeriod) && (timeValUpper.indexOf(amPmText[1].toUpperCase()) != -1)) {
                retVal.hours += 12;
            }
            // fix for 12 AM
            if ((retVal.hours == 12) && (showPeriod) && (timeValUpper.indexOf(amPmText[0].toUpperCase()) != -1)) {
                retVal.hours = 0;
            }
        }

        return retVal;
    },

    selectNow: function(event) {
        var id = $(event.target).attr("data-timepicker-instance-id"),
            $target = $(id),
            inst = this._getInst($target[0]);
        //if (!inst || (input && inst != $.data(input, PROP_NAME))) { return; }
        var currentTime = new Date();
        inst.hours = currentTime.getHours();
        inst.minutes = currentTime.getMinutes();
        this._updateSelectedValue(inst);
        this._updateTimepicker(inst);
        this._hideTimepicker();
    },

    deselectTime: function(event) {
        var id = $(event.target).attr("data-timepicker-instance-id"),
            $target = $(id),
            inst = this._getInst($target[0]);
        inst.hours = -1;
        inst.minutes = -1;
        this._updateSelectedValue(inst);
        this._hideTimepicker();
    },

    selectHours: function (event) {
        var $td = $(event.currentTarget),
            id = $td.attr("data-timepicker-instance-id"),
            newHours = parseInt($td.attr("data-hour")),
            fromDoubleClick = event.data.fromDoubleClick,
            $target = $(id),
            inst = this._getInst($target[0]),
            showMinutes = (this._get(inst, 'showMinutes') == true);

        // don't select if disabled
        if ( $.timepicker._isDisabledTimepicker($target.attr('id')) ) { return false }

        $td.parents('.ui-timepicker-hours:first').find('a').removeClass('ui-state-active');
        $td.children('a').addClass('ui-state-active');
        inst.hours = newHours;

        // added for onMinuteShow callback
        var onMinuteShow = this._get(inst, 'onMinuteShow'),
            maxTime = this._get(inst, 'maxTime'),
            minTime = this._get(inst, 'minTime');
        if (onMinuteShow || maxTime.minute || minTime.minute) {
            // this will trigger a callback on selected hour to make sure selected minute is allowed. 
            this._updateMinuteDisplay(inst);
        }

        this._updateSelectedValue(inst);

        inst._hoursClicked = true;
        if ((inst._minutesClicked) || (fromDoubleClick) || (showMinutes == false)) {
            $.timepicker._hideTimepicker();
        }
        // return false because if used inline, prevent the url to change to a hashtag
        return false;
    },

    selectMinutes: function (event) {
        var $td = $(event.currentTarget),
            id = $td.attr("data-timepicker-instance-id"),
            newMinutes = parseInt($td.attr("data-minute")),
            fromDoubleClick = event.data.fromDoubleClick,
            $target = $(id),
            inst = this._getInst($target[0]),
            showHours = (this._get(inst, 'showHours') == true);

        // don't select if disabled
        if ( $.timepicker._isDisabledTimepicker($target.attr('id')) ) { return false }

        $td.parents('.ui-timepicker-minutes:first').find('a').removeClass('ui-state-active');
        $td.children('a').addClass('ui-state-active');

        inst.minutes = newMinutes;
        this._updateSelectedValue(inst);

        inst._minutesClicked = true;
        if ((inst._hoursClicked) || (fromDoubleClick) || (showHours == false)) {
            $.timepicker._hideTimepicker();
            // return false because if used inline, prevent the url to change to a hashtag
            return false;
        }

        // return false because if used inline, prevent the url to change to a hashtag
        return false;
    },

    _updateSelectedValue: function (inst) {
        var newTime = this._getParsedTime(inst);
        if (inst.input) {
            inst.input.val(newTime);
            inst.input.trigger('change');
        }
        var onSelect = this._get(inst, 'onSelect');
        if (onSelect) { onSelect.apply((inst.input ? inst.input[0] : null), [newTime, inst]); } // trigger custom callback
        this._updateAlternate(inst, newTime);
        return newTime;
    },

    /* this function process selected time and return it parsed according to instance options */
    _getParsedTime: function(inst) {

        if (inst.hours == -1 && inst.minutes == -1) {
            return '';
        }

        // default to 0 AM if hours is not valid
        if ((inst.hours < inst.hours.starts) || (inst.hours > inst.hours.ends )) { inst.hours = 0; }
        // default to 0 minutes if minute is not valid
        if ((inst.minutes < inst.minutes.starts) || (inst.minutes > inst.minutes.ends)) { inst.minutes = 0; }

        var period = "",
            showPeriod = (this._get(inst, 'showPeriod') == true),
            showLeadingZero = (this._get(inst, 'showLeadingZero') == true),
            showHours = (this._get(inst, 'showHours') == true),
            showMinutes = (this._get(inst, 'showMinutes') == true),
            optionalMinutes = (this._get(inst, 'optionalMinutes') == true),
            amPmText = this._get(inst, 'amPmText'),
            selectedHours = inst.hours ? inst.hours : 0,
            selectedMinutes = inst.minutes ? inst.minutes : 0,
            displayHours = selectedHours ? selectedHours : 0,
            parsedTime = '';

        // fix some display problem when hours or minutes are not selected yet
        if (displayHours == -1) { displayHours = 0 }
        if (selectedMinutes == -1) { selectedMinutes = 0 }

        if (showPeriod) {
            if (inst.hours == 0) {
                displayHours = 12;
            }
            if (inst.hours < 12) {
                period = amPmText[0];
            }
            else {
                period = amPmText[1];
                if (displayHours > 12) {
                    displayHours -= 12;
                }
            }
        }

        var h = displayHours.toString();
        if (showLeadingZero && (displayHours < 10)) { h = '0' + h; }

        var m = selectedMinutes.toString();
        if (selectedMinutes < 10) { m = '0' + m; }

        if (showHours) {
            parsedTime += h;
        }
        if (showHours && showMinutes && (!optionalMinutes || m != 0)) {
            parsedTime += this._get(inst, 'timeSeparator');
        }
        if (showMinutes && (!optionalMinutes || m != 0)) {
            parsedTime += m;
        }
        if (showHours) {
            if (period.length > 0) { parsedTime += this._get(inst, 'periodSeparator') + period; }
        }

        return parsedTime;
    },

    /* Update any alternate field to synchronise with the main field. */
    _updateAlternate: function(inst, newTime) {
        var altField = this._get(inst, 'altField');
        if (altField) { // update alternate field too
            $(altField).each(function(i,e) {
                $(e).val(newTime);
            });
        }
    },

    _getTimeAsDateTimepicker: function(input) {
        var inst = this._getInst(input);
        if (inst.hours == -1 && inst.minutes == -1) {
            return '';
        }

        // default to 0 AM if hours is not valid
        if ((inst.hours < inst.hours.starts) || (inst.hours > inst.hours.ends )) { inst.hours = 0; }
        // default to 0 minutes if minute is not valid
        if ((inst.minutes < inst.minutes.starts) || (inst.minutes > inst.minutes.ends)) { inst.minutes = 0; }

        return new Date(0, 0, 0, inst.hours, inst.minutes, 0);
    },
    /* This might look unused but it's called by the $.fn.timepicker function with param getTime */
    /* added v 0.2.3 - gitHub issue #5 - Thanks edanuff */
    _getTimeTimepicker : function(input) {
        var inst = this._getInst(input);
        return this._getParsedTime(inst);
    },
    _getHourTimepicker: function(input) {
        var inst = this._getInst(input);
        if ( inst == undefined) { return -1; }
        return inst.hours;
    },
    _getMinuteTimepicker: function(input) {
        var inst= this._getInst(input);
        if ( inst == undefined) { return -1; }
        return inst.minutes;
    }

});

/* Invoke the timepicker functionality.
@param  options  string - a command, optionally followed by additional parameters or
Object - settings for attaching new timepicker functionality
@return  jQuery object */
$.fn.timepicker = function (options) {
    /* Initialise the time picker. */
    if (!$.timepicker.initialized) {
        $(document).mousedown($.timepicker._checkExternalClick);
        $.timepicker.initialized = true;
    }

     /* Append timepicker main container to body if not exist. */
    if ($("#"+$.timepicker._mainDivId).length === 0) {
        $('body').append($.timepicker.tpDiv);
    }

    var otherArgs = Array.prototype.slice.call(arguments, 1);
    if (typeof options == 'string' && (options == 'getTime' || options == 'getTimeAsDate' || options == 'getHour' || options == 'getMinute' ))
        return $.timepicker['_' + options + 'Timepicker'].
                        apply($.timepicker, [this[0]].concat(otherArgs));
    if (options == 'option' && arguments.length == 2 && typeof arguments[1] == 'string')
        return $.timepicker['_' + options + 'Timepicker'].
            apply($.timepicker, [this[0]].concat(otherArgs));
    return this.each(function () {
        typeof options == 'string' ?
                    $.timepicker['_' + options + 'Timepicker'].
                            apply($.timepicker, [this].concat(otherArgs)) :
                    $.timepicker._attachTimepicker(this, options);
    });
};

/* jQuery extend now ignores nulls! */
function extendRemove(target, props) {
    $.extend(target, props);
    for (var name in props)
        if (props[name] == null || props[name] == undefined)
            target[name] = props[name];
    return target;
};

$.timepicker = new Timepicker(); // singleton instance
$.timepicker.initialized = false;
$.timepicker.uuid = new Date().getTime();
$.timepicker.version = "0.3.3";

// Workaround for #4055
// Add another global to avoid noConflict issues with inline event handlers
window['TP_jQuery_' + tpuuid] = $;

})(jQuery);