Merge remote-tracking branch 'origin/new/bug_8408'
[koha.git] / koha-tmpl / intranet-tmpl / prog / en / lib / jquery / plugins / jquery-ui-timepicker-addon.js
1 /*
2 * jQuery timepicker addon
3 * By: Trent Richardson [http://trentrichardson.com]
4 * Version 1.0.1
5 * Last Modified: 07/01/2012
6 *
7 * Copyright 2012 Trent Richardson
8 * You may use this project under MIT or GPL licenses.
9 * http://trentrichardson.com/Impromptu/GPL-LICENSE.txt
10 * http://trentrichardson.com/Impromptu/MIT-LICENSE.txt
11 *
12 * HERES THE CSS:
13 * .ui-timepicker-div .ui-widget-header { margin-bottom: 8px; }
14 * .ui-timepicker-div dl { text-align: left; }
15 * .ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; }
16 * .ui-timepicker-div dl dd { margin: 0 10px 10px 65px; }
17 * .ui-timepicker-div td { font-size: 90%; }
18 * .ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; }
19 */
20
21 /*jslint evil: true, maxlen: 300, white: false, undef: false, nomen: false, onevar: false */
22
23 (function($) {
24
25 // Prevent "Uncaught RangeError: Maximum call stack size exceeded"
26 $.ui.timepicker = $.ui.timepicker || {};
27 if ($.ui.timepicker.version) {
28     return;
29 }
30
31 $.extend($.ui, { timepicker: { version: "1.0.1" } });
32
33 /* Time picker manager.
34    Use the singleton instance of this class, $.timepicker, to interact with the time picker.
35    Settings for (groups of) time pickers are maintained in an instance object,
36    allowing multiple different settings on the same page. */
37
38 function Timepicker() {
39     this.regional = []; // Available regional settings, indexed by language code
40     this.regional[''] = { // Default regional settings
41         currentText: 'Now',
42         closeText: 'Done',
43         ampm: false,
44         amNames: ['AM', 'A'],
45         pmNames: ['PM', 'P'],
46         timeFormat: 'hh:mm tt',
47         timeSuffix: '',
48         timeOnlyTitle: 'Choose Time',
49         timeText: 'Time',
50         hourText: 'Hour',
51         minuteText: 'Minute',
52         secondText: 'Second',
53         millisecText: 'Millisecond',
54         timezoneText: 'Time Zone'
55     };
56     this._defaults = { // Global defaults for all the datetime picker instances
57         showButtonPanel: true,
58         timeOnly: false,
59         showHour: true,
60         showMinute: true,
61         showSecond: false,
62         showMillisec: false,
63         showTimezone: false,
64         showTime: true,
65         stepHour: 1,
66         stepMinute: 1,
67         stepSecond: 1,
68         stepMillisec: 1,
69         hour: 0,
70         minute: 0,
71         second: 0,
72         millisec: 0,
73         timezone: null,
74         useLocalTimezone: false,
75         defaultTimezone: "+0000",
76         hourMin: 0,
77         minuteMin: 0,
78         secondMin: 0,
79         millisecMin: 0,
80         hourMax: 23,
81         minuteMax: 59,
82         secondMax: 59,
83         millisecMax: 999,
84         minDateTime: null,
85         maxDateTime: null,
86         onSelect: null,
87         hourGrid: 0,
88         minuteGrid: 0,
89         secondGrid: 0,
90         millisecGrid: 0,
91         alwaysSetTime: true,
92         separator: ' ',
93         altFieldTimeOnly: true,
94         showTimepicker: true,
95         timezoneIso8601: false,
96         timezoneList: null,
97         addSliderAccess: false,
98         sliderAccessArgs: null
99     };
100     $.extend(this._defaults, this.regional['']);
101 }
102
103 $.extend(Timepicker.prototype, {
104     $input: null,
105     $altInput: null,
106     $timeObj: null,
107     inst: null,
108     hour_slider: null,
109     minute_slider: null,
110     second_slider: null,
111     millisec_slider: null,
112     timezone_select: null,
113     hour: 0,
114     minute: 0,
115     second: 0,
116     millisec: 0,
117     timezone: null,
118     defaultTimezone: "+0000",
119     hourMinOriginal: null,
120     minuteMinOriginal: null,
121     secondMinOriginal: null,
122     millisecMinOriginal: null,
123     hourMaxOriginal: null,
124     minuteMaxOriginal: null,
125     secondMaxOriginal: null,
126     millisecMaxOriginal: null,
127     ampm: '',
128     formattedDate: '',
129     formattedTime: '',
130     formattedDateTime: '',
131     timezoneList: null,
132
133     /* Override the default settings for all instances of the time picker.
134        @param  settings  object - the new settings to use as defaults (anonymous object)
135        @return the manager object */
136     setDefaults: function(settings) {
137         extendRemove(this._defaults, settings || {});
138         return this;
139     },
140
141     //########################################################################
142     // Create a new Timepicker instance
143     //########################################################################
144     _newInst: function($input, o) {
145         var tp_inst = new Timepicker(),
146             inlineSettings = {};
147
148         for (var attrName in this._defaults) {
149             var attrValue = $input.attr('time:' + attrName);
150             if (attrValue) {
151                 try {
152                     inlineSettings[attrName] = eval(attrValue);
153                 } catch (err) {
154                     inlineSettings[attrName] = attrValue;
155                 }
156             }
157         }
158         tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, o, {
159             beforeShow: function(input, dp_inst) {
160                 if ($.isFunction(o.beforeShow)) {
161                     return o.beforeShow(input, dp_inst, tp_inst);
162                 }
163             },
164             onChangeMonthYear: function(year, month, dp_inst) {
165                 // Update the time as well : this prevents the time from disappearing from the $input field.
166                 tp_inst._updateDateTime(dp_inst);
167                 if ($.isFunction(o.onChangeMonthYear)) {
168                     o.onChangeMonthYear.call($input[0], year, month, dp_inst, tp_inst);
169                 }
170             },
171             onClose: function(dateText, dp_inst) {
172                 if (tp_inst.timeDefined === true && $input.val() !== '') {
173                     tp_inst._updateDateTime(dp_inst);
174                 }
175                 if ($.isFunction(o.onClose)) {
176                     o.onClose.call($input[0], dateText, dp_inst, tp_inst);
177                 }
178             },
179             timepicker: tp_inst // add timepicker as a property of datepicker: $.datepicker._get(dp_inst, 'timepicker');
180         });
181         tp_inst.amNames = $.map(tp_inst._defaults.amNames, function(val) { return val.toUpperCase(); });
182         tp_inst.pmNames = $.map(tp_inst._defaults.pmNames, function(val) { return val.toUpperCase(); });
183
184         if (tp_inst._defaults.timezoneList === null) {
185             var timezoneList = [];
186             for (var i = -11; i <= 12; i++) {
187                 timezoneList.push((i >= 0 ? '+' : '-') + ('0' + Math.abs(i).toString()).slice(-2) + '00');
188             }
189             if (tp_inst._defaults.timezoneIso8601) {
190                 timezoneList = $.map(timezoneList, function(val) {
191                     return val == '+0000' ? 'Z' : (val.substring(0, 3) + ':' + val.substring(3));
192                 });
193             }
194             tp_inst._defaults.timezoneList = timezoneList;
195         }
196
197         tp_inst.timezone = tp_inst._defaults.timezone;
198         tp_inst.hour = tp_inst._defaults.hour;
199         tp_inst.minute = tp_inst._defaults.minute;
200         tp_inst.second = tp_inst._defaults.second;
201         tp_inst.millisec = tp_inst._defaults.millisec;
202         tp_inst.ampm = '';
203         tp_inst.$input = $input;
204
205         if (o.altField) {
206             tp_inst.$altInput = $(o.altField)
207                 .css({ cursor: 'pointer' })
208                 .focus(function(){ $input.trigger("focus"); });
209         }
210
211         if(tp_inst._defaults.minDate===0 || tp_inst._defaults.minDateTime===0)
212         {
213             tp_inst._defaults.minDate=new Date();
214         }
215         if(tp_inst._defaults.maxDate===0 || tp_inst._defaults.maxDateTime===0)
216         {
217             tp_inst._defaults.maxDate=new Date();
218         }
219
220         // datepicker needs minDate/maxDate, timepicker needs minDateTime/maxDateTime..
221         if(tp_inst._defaults.minDate !== undefined && tp_inst._defaults.minDate instanceof Date) {
222             tp_inst._defaults.minDateTime = new Date(tp_inst._defaults.minDate.getTime());
223         }
224         if(tp_inst._defaults.minDateTime !== undefined && tp_inst._defaults.minDateTime instanceof Date) {
225             tp_inst._defaults.minDate = new Date(tp_inst._defaults.minDateTime.getTime());
226         }
227         if(tp_inst._defaults.maxDate !== undefined && tp_inst._defaults.maxDate instanceof Date) {
228             tp_inst._defaults.maxDateTime = new Date(tp_inst._defaults.maxDate.getTime());
229         }
230         if(tp_inst._defaults.maxDateTime !== undefined && tp_inst._defaults.maxDateTime instanceof Date) {
231             tp_inst._defaults.maxDate = new Date(tp_inst._defaults.maxDateTime.getTime());
232         }
233         return tp_inst;
234     },
235
236     //########################################################################
237     // add our sliders to the calendar
238     //########################################################################
239     _addTimePicker: function(dp_inst) {
240         var currDT = (this.$altInput && this._defaults.altFieldTimeOnly) ?
241                 this.$input.val() + ' ' + this.$altInput.val() :
242                 this.$input.val();
243
244         this.timeDefined = this._parseTime(currDT);
245         this._limitMinMaxDateTime(dp_inst, false);
246         this._injectTimePicker();
247     },
248
249     //########################################################################
250     // parse the time string from input value or _setTime
251     //########################################################################
252     _parseTime: function(timeString, withDate) {
253         if (!this.inst) {
254             this.inst = $.datepicker._getInst(this.$input[0]);
255         }
256
257         if (withDate || !this._defaults.timeOnly)
258         {
259             var dp_dateFormat = $.datepicker._get(this.inst, 'dateFormat');
260             try {
261                 var parseRes = parseDateTimeInternal(dp_dateFormat, this._defaults.timeFormat, timeString, $.datepicker._getFormatConfig(this.inst), this._defaults);
262                 if (!parseRes.timeObj) { return false; }
263                 $.extend(this, parseRes.timeObj);
264             } catch (err)
265             {
266                 return false;
267             }
268             return true;
269         }
270         else
271         {
272             var timeObj = $.datepicker.parseTime(this._defaults.timeFormat, timeString, this._defaults);
273             if(!timeObj) { return false; }
274             $.extend(this, timeObj);
275             return true;
276         }
277     },
278
279     //########################################################################
280     // generate and inject html for timepicker into ui datepicker
281     //########################################################################
282     _injectTimePicker: function() {
283         var $dp = this.inst.dpDiv,
284             o = this._defaults,
285             tp_inst = this,
286             // Added by Peter Medeiros:
287             // - Figure out what the hour/minute/second max should be based on the step values.
288             // - Example: if stepMinute is 15, then minMax is 45.
289             hourMax = parseInt((o.hourMax - ((o.hourMax - o.hourMin) % o.stepHour)) ,10),
290             minMax  = parseInt((o.minuteMax - ((o.minuteMax - o.minuteMin) % o.stepMinute)) ,10),
291             secMax  = parseInt((o.secondMax - ((o.secondMax - o.secondMin) % o.stepSecond)) ,10),
292             millisecMax  = parseInt((o.millisecMax - ((o.millisecMax - o.millisecMin) % o.stepMillisec)) ,10),
293             dp_id = this.inst.id.toString().replace(/([^A-Za-z0-9_])/g, '');
294
295         // Prevent displaying twice
296         //if ($dp.find("div#ui-timepicker-div-"+ dp_id).length === 0) {
297         if ($dp.find("div#ui-timepicker-div-"+ dp_id).length === 0 && o.showTimepicker) {
298             var noDisplay = ' style="display:none;"',
299                 html =  '<div class="ui-timepicker-div" id="ui-timepicker-div-' + dp_id + '"><dl>' +
300                         '<dt class="ui_tpicker_time_label" id="ui_tpicker_time_label_' + dp_id + '"' +
301                         ((o.showTime) ? '' : noDisplay) + '>' + o.timeText + '</dt>' +
302                         '<dd class="ui_tpicker_time" id="ui_tpicker_time_' + dp_id + '"' +
303                         ((o.showTime) ? '' : noDisplay) + '></dd>' +
304                         '<dt class="ui_tpicker_hour_label" id="ui_tpicker_hour_label_' + dp_id + '"' +
305                         ((o.showHour) ? '' : noDisplay) + '>' + o.hourText + '</dt>',
306                 hourGridSize = 0,
307                 minuteGridSize = 0,
308                 secondGridSize = 0,
309                 millisecGridSize = 0,
310                 size = null;
311
312             // Hours
313             html += '<dd class="ui_tpicker_hour"><div id="ui_tpicker_hour_' + dp_id + '"' +
314                         ((o.showHour) ? '' : noDisplay) + '></div>';
315             if (o.showHour && o.hourGrid > 0) {
316                 html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>';
317
318                 for (var h = o.hourMin; h <= hourMax; h += parseInt(o.hourGrid,10)) {
319                     hourGridSize++;
320                     var tmph = (o.ampm && h > 12) ? h-12 : h;
321                     if (tmph < 10) { tmph = '0' + tmph; }
322                     if (o.ampm) {
323                         if (h === 0) {
324                             tmph = 12 +'a';
325                         } else {
326                             if (h < 12) { tmph += 'a'; }
327                             else { tmph += 'p'; }
328                         }
329                     }
330                     html += '<td>' + tmph + '</td>';
331                 }
332
333                 html += '</tr></table></div>';
334             }
335             html += '</dd>';
336
337             // Minutes
338             html += '<dt class="ui_tpicker_minute_label" id="ui_tpicker_minute_label_' + dp_id + '"' +
339                     ((o.showMinute) ? '' : noDisplay) + '>' + o.minuteText + '</dt>'+
340                     '<dd class="ui_tpicker_minute"><div id="ui_tpicker_minute_' + dp_id + '"' +
341                             ((o.showMinute) ? '' : noDisplay) + '></div>';
342
343             if (o.showMinute && o.minuteGrid > 0) {
344                 html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>';
345
346                 for (var m = o.minuteMin; m <= minMax; m += parseInt(o.minuteGrid,10)) {
347                     minuteGridSize++;
348                     html += '<td>' + ((m < 10) ? '0' : '') + m + '</td>';
349                 }
350
351                 html += '</tr></table></div>';
352             }
353             html += '</dd>';
354
355             // Seconds
356             html += '<dt class="ui_tpicker_second_label" id="ui_tpicker_second_label_' + dp_id + '"' +
357                     ((o.showSecond) ? '' : noDisplay) + '>' + o.secondText + '</dt>'+
358                     '<dd class="ui_tpicker_second"><div id="ui_tpicker_second_' + dp_id + '"'+
359                             ((o.showSecond) ? '' : noDisplay) + '></div>';
360
361             if (o.showSecond && o.secondGrid > 0) {
362                 html += '<div style="padding-left: 1px"><table><tr>';
363
364                 for (var s = o.secondMin; s <= secMax; s += parseInt(o.secondGrid,10)) {
365                     secondGridSize++;
366                     html += '<td>' + ((s < 10) ? '0' : '') + s + '</td>';
367                 }
368
369                 html += '</tr></table></div>';
370             }
371             html += '</dd>';
372
373             // Milliseconds
374             html += '<dt class="ui_tpicker_millisec_label" id="ui_tpicker_millisec_label_' + dp_id + '"' +
375                     ((o.showMillisec) ? '' : noDisplay) + '>' + o.millisecText + '</dt>'+
376                     '<dd class="ui_tpicker_millisec"><div id="ui_tpicker_millisec_' + dp_id + '"'+
377                             ((o.showMillisec) ? '' : noDisplay) + '></div>';
378
379             if (o.showMillisec && o.millisecGrid > 0) {
380                 html += '<div style="padding-left: 1px"><table><tr>';
381
382                 for (var l = o.millisecMin; l <= millisecMax; l += parseInt(o.millisecGrid,10)) {
383                     millisecGridSize++;
384                     html += '<td>' + ((l < 10) ? '0' : '') + l + '</td>';
385                 }
386
387                 html += '</tr></table></div>';
388             }
389             html += '</dd>';
390
391             // Timezone
392             html += '<dt class="ui_tpicker_timezone_label" id="ui_tpicker_timezone_label_' + dp_id + '"' +
393                     ((o.showTimezone) ? '' : noDisplay) + '>' + o.timezoneText + '</dt>';
394             html += '<dd class="ui_tpicker_timezone" id="ui_tpicker_timezone_' + dp_id + '"'    +
395                             ((o.showTimezone) ? '' : noDisplay) + '></dd>';
396
397             html += '</dl></div>';
398             var $tp = $(html);
399
400                 // if we only want time picker...
401             if (o.timeOnly === true) {
402                 $tp.prepend(
403                     '<div class="ui-widget-header ui-helper-clearfix ui-corner-all">' +
404                         '<div class="ui-datepicker-title">' + o.timeOnlyTitle + '</div>' +
405                     '</div>');
406                 $dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide();
407             }
408
409             this.hour_slider = $tp.find('#ui_tpicker_hour_'+ dp_id).slider({
410                 orientation: "horizontal",
411                 value: this.hour,
412                 min: o.hourMin,
413                 max: hourMax,
414                 step: o.stepHour,
415                 slide: function(event, ui) {
416                     tp_inst.hour_slider.slider( "option", "value", ui.value);
417                     tp_inst._onTimeChange();
418                 }
419             });
420
421
422             // Updated by Peter Medeiros:
423             // - Pass in Event and UI instance into slide function
424             this.minute_slider = $tp.find('#ui_tpicker_minute_'+ dp_id).slider({
425                 orientation: "horizontal",
426                 value: this.minute,
427                 min: o.minuteMin,
428                 max: minMax,
429                 step: o.stepMinute,
430                 slide: function(event, ui) {
431                     tp_inst.minute_slider.slider( "option", "value", ui.value);
432                     tp_inst._onTimeChange();
433                 }
434             });
435
436             this.second_slider = $tp.find('#ui_tpicker_second_'+ dp_id).slider({
437                 orientation: "horizontal",
438                 value: this.second,
439                 min: o.secondMin,
440                 max: secMax,
441                 step: o.stepSecond,
442                 slide: function(event, ui) {
443                     tp_inst.second_slider.slider( "option", "value", ui.value);
444                     tp_inst._onTimeChange();
445                 }
446             });
447
448             this.millisec_slider = $tp.find('#ui_tpicker_millisec_'+ dp_id).slider({
449                 orientation: "horizontal",
450                 value: this.millisec,
451                 min: o.millisecMin,
452                 max: millisecMax,
453                 step: o.stepMillisec,
454                 slide: function(event, ui) {
455                     tp_inst.millisec_slider.slider( "option", "value", ui.value);
456                     tp_inst._onTimeChange();
457                 }
458             });
459
460             this.timezone_select = $tp.find('#ui_tpicker_timezone_'+ dp_id).append('<select></select>').find("select");
461             $.fn.append.apply(this.timezone_select,
462                 $.map(o.timezoneList, function(val, idx) {
463                     return $("<option />")
464                         .val(typeof val == "object" ? val.value : val)
465                         .text(typeof val == "object" ? val.label : val);
466                 })
467             );
468             if (typeof(this.timezone) != "undefined" && this.timezone !== null && this.timezone !== "") {
469                 var local_date = new Date(this.inst.selectedYear, this.inst.selectedMonth, this.inst.selectedDay, 12);
470                 var local_timezone = timeZoneString(local_date);
471                 if (local_timezone == this.timezone) {
472                     selectLocalTimeZone(tp_inst);
473                 } else {
474                     this.timezone_select.val(this.timezone);
475                 }
476             } else {
477                 if (typeof(this.hour) != "undefined" && this.hour !== null && this.hour !== "") {
478                     this.timezone_select.val(o.defaultTimezone);
479                 } else {
480                     selectLocalTimeZone(tp_inst);
481                 }
482             }
483             this.timezone_select.change(function() {
484                 tp_inst._defaults.useLocalTimezone = false;
485                 tp_inst._onTimeChange();
486             });
487
488             // Add grid functionality
489             if (o.showHour && o.hourGrid > 0) {
490                 size = 100 * hourGridSize * o.hourGrid / (hourMax - o.hourMin);
491
492                 $tp.find(".ui_tpicker_hour table").css({
493                     width: size + "%",
494                     marginLeft: (size / (-2 * hourGridSize)) + "%",
495                     borderCollapse: 'collapse'
496                 }).find("td").each( function(index) {
497                     $(this).click(function() {
498                         var h = $(this).html();
499                         if(o.ampm)      {
500                             var ap = h.substring(2).toLowerCase(),
501                                 aph = parseInt(h.substring(0,2), 10);
502                             if (ap == 'a') {
503                                 if (aph == 12) { h = 0; }
504                                 else { h = aph; }
505                             } else if (aph == 12) { h = 12; }
506                             else { h = aph + 12; }
507                         }
508                         tp_inst.hour_slider.slider("option", "value", h);
509                         tp_inst._onTimeChange();
510                         tp_inst._onSelectHandler();
511                     }).css({
512                         cursor: 'pointer',
513                         width: (100 / hourGridSize) + '%',
514                         textAlign: 'center',
515                         overflow: 'hidden'
516                     });
517                 });
518             }
519
520             if (o.showMinute && o.minuteGrid > 0) {
521                 size = 100 * minuteGridSize * o.minuteGrid / (minMax - o.minuteMin);
522                 $tp.find(".ui_tpicker_minute table").css({
523                     width: size + "%",
524                     marginLeft: (size / (-2 * minuteGridSize)) + "%",
525                     borderCollapse: 'collapse'
526                 }).find("td").each(function(index) {
527                     $(this).click(function() {
528                         tp_inst.minute_slider.slider("option", "value", $(this).html());
529                         tp_inst._onTimeChange();
530                         tp_inst._onSelectHandler();
531                     }).css({
532                         cursor: 'pointer',
533                         width: (100 / minuteGridSize) + '%',
534                         textAlign: 'center',
535                         overflow: 'hidden'
536                     });
537                 });
538             }
539
540             if (o.showSecond && o.secondGrid > 0) {
541                 $tp.find(".ui_tpicker_second table").css({
542                     width: size + "%",
543                     marginLeft: (size / (-2 * secondGridSize)) + "%",
544                     borderCollapse: 'collapse'
545                 }).find("td").each(function(index) {
546                     $(this).click(function() {
547                         tp_inst.second_slider.slider("option", "value", $(this).html());
548                         tp_inst._onTimeChange();
549                         tp_inst._onSelectHandler();
550                     }).css({
551                         cursor: 'pointer',
552                         width: (100 / secondGridSize) + '%',
553                         textAlign: 'center',
554                         overflow: 'hidden'
555                     });
556                 });
557             }
558
559             if (o.showMillisec && o.millisecGrid > 0) {
560                 $tp.find(".ui_tpicker_millisec table").css({
561                     width: size + "%",
562                     marginLeft: (size / (-2 * millisecGridSize)) + "%",
563                     borderCollapse: 'collapse'
564                 }).find("td").each(function(index) {
565                     $(this).click(function() {
566                         tp_inst.millisec_slider.slider("option", "value", $(this).html());
567                         tp_inst._onTimeChange();
568                         tp_inst._onSelectHandler();
569                     }).css({
570                         cursor: 'pointer',
571                         width: (100 / millisecGridSize) + '%',
572                         textAlign: 'center',
573                         overflow: 'hidden'
574                     });
575                 });
576             }
577
578             var $buttonPanel = $dp.find('.ui-datepicker-buttonpane');
579             if ($buttonPanel.length) { $buttonPanel.before($tp); }
580             else { $dp.append($tp); }
581
582             this.$timeObj = $tp.find('#ui_tpicker_time_'+ dp_id);
583
584             if (this.inst !== null) {
585                 var timeDefined = this.timeDefined;
586                 this._onTimeChange();
587                 this.timeDefined = timeDefined;
588             }
589
590             //Emulate datepicker onSelect behavior. Call on slidestop.
591             var onSelectDelegate = function() {
592                 tp_inst._onSelectHandler();
593             };
594             this.hour_slider.bind('slidestop',onSelectDelegate);
595             this.minute_slider.bind('slidestop',onSelectDelegate);
596             this.second_slider.bind('slidestop',onSelectDelegate);
597             this.millisec_slider.bind('slidestop',onSelectDelegate);
598
599             // slideAccess integration: http://trentrichardson.com/2011/11/11/jquery-ui-sliders-and-touch-accessibility/
600             if (this._defaults.addSliderAccess){
601                 var sliderAccessArgs = this._defaults.sliderAccessArgs;
602                 setTimeout(function(){ // fix for inline mode
603                     if($tp.find('.ui-slider-access').length === 0){
604                         $tp.find('.ui-slider:visible').sliderAccess(sliderAccessArgs);
605
606                         // fix any grids since sliders are shorter
607                         var sliderAccessWidth = $tp.find('.ui-slider-access:eq(0)').outerWidth(true);
608                         if(sliderAccessWidth){
609                             $tp.find('table:visible').each(function(){
610                                 var $g = $(this),
611                                     oldWidth = $g.outerWidth(),
612                                     oldMarginLeft = $g.css('marginLeft').toString().replace('%',''),
613                                     newWidth = oldWidth - sliderAccessWidth,
614                                     newMarginLeft = ((oldMarginLeft * newWidth)/oldWidth) + '%';
615
616                                 $g.css({ width: newWidth, marginLeft: newMarginLeft });
617                             });
618                         }
619                     }
620                 },0);
621             }
622             // end slideAccess integration
623
624         }
625     },
626
627     //########################################################################
628     // This function tries to limit the ability to go outside the
629     // min/max date range
630     //########################################################################
631     _limitMinMaxDateTime: function(dp_inst, adjustSliders){
632         var o = this._defaults,
633             dp_date = new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay);
634
635         if(!this._defaults.showTimepicker) { return; } // No time so nothing to check here
636
637         if($.datepicker._get(dp_inst, 'minDateTime') !== null && $.datepicker._get(dp_inst, 'minDateTime') !== undefined && dp_date){
638             var minDateTime = $.datepicker._get(dp_inst, 'minDateTime'),
639                 minDateTimeDate = new Date(minDateTime.getFullYear(), minDateTime.getMonth(), minDateTime.getDate(), 0, 0, 0, 0);
640
641             if(this.hourMinOriginal === null || this.minuteMinOriginal === null || this.secondMinOriginal === null || this.millisecMinOriginal === null){
642                 this.hourMinOriginal = o.hourMin;
643                 this.minuteMinOriginal = o.minuteMin;
644                 this.secondMinOriginal = o.secondMin;
645                 this.millisecMinOriginal = o.millisecMin;
646             }
647
648             if(dp_inst.settings.timeOnly || minDateTimeDate.getTime() == dp_date.getTime()) {
649                 this._defaults.hourMin = minDateTime.getHours();
650                 if (this.hour <= this._defaults.hourMin) {
651                     this.hour = this._defaults.hourMin;
652                     this._defaults.minuteMin = minDateTime.getMinutes();
653                     if (this.minute <= this._defaults.minuteMin) {
654                         this.minute = this._defaults.minuteMin;
655                         this._defaults.secondMin = minDateTime.getSeconds();
656                     } else if (this.second <= this._defaults.secondMin){
657                         this.second = this._defaults.secondMin;
658                         this._defaults.millisecMin = minDateTime.getMilliseconds();
659                     } else {
660                         if(this.millisec < this._defaults.millisecMin) {
661                             this.millisec = this._defaults.millisecMin;
662                         }
663                         this._defaults.millisecMin = this.millisecMinOriginal;
664                     }
665                 } else {
666                     this._defaults.minuteMin = this.minuteMinOriginal;
667                     this._defaults.secondMin = this.secondMinOriginal;
668                     this._defaults.millisecMin = this.millisecMinOriginal;
669                 }
670             }else{
671                 this._defaults.hourMin = this.hourMinOriginal;
672                 this._defaults.minuteMin = this.minuteMinOriginal;
673                 this._defaults.secondMin = this.secondMinOriginal;
674                 this._defaults.millisecMin = this.millisecMinOriginal;
675             }
676         }
677
678         if($.datepicker._get(dp_inst, 'maxDateTime') !== null && $.datepicker._get(dp_inst, 'maxDateTime') !== undefined && dp_date){
679             var maxDateTime = $.datepicker._get(dp_inst, 'maxDateTime'),
680                 maxDateTimeDate = new Date(maxDateTime.getFullYear(), maxDateTime.getMonth(), maxDateTime.getDate(), 0, 0, 0, 0);
681
682             if(this.hourMaxOriginal === null || this.minuteMaxOriginal === null || this.secondMaxOriginal === null){
683                 this.hourMaxOriginal = o.hourMax;
684                 this.minuteMaxOriginal = o.minuteMax;
685                 this.secondMaxOriginal = o.secondMax;
686                 this.millisecMaxOriginal = o.millisecMax;
687             }
688
689             if(dp_inst.settings.timeOnly || maxDateTimeDate.getTime() == dp_date.getTime()){
690                 this._defaults.hourMax = maxDateTime.getHours();
691                 if (this.hour >= this._defaults.hourMax) {
692                     this.hour = this._defaults.hourMax;
693                     this._defaults.minuteMax = maxDateTime.getMinutes();
694                     if (this.minute >= this._defaults.minuteMax) {
695                         this.minute = this._defaults.minuteMax;
696                         this._defaults.secondMax = maxDateTime.getSeconds();
697                     } else if (this.second >= this._defaults.secondMax) {
698                         this.second = this._defaults.secondMax;
699                         this._defaults.millisecMax = maxDateTime.getMilliseconds();
700                     } else {
701                         if(this.millisec > this._defaults.millisecMax) { this.millisec = this._defaults.millisecMax; }
702                         this._defaults.millisecMax = this.millisecMaxOriginal;
703                     }
704                 } else {
705                     this._defaults.minuteMax = this.minuteMaxOriginal;
706                     this._defaults.secondMax = this.secondMaxOriginal;
707                     this._defaults.millisecMax = this.millisecMaxOriginal;
708                 }
709             }else{
710                 this._defaults.hourMax = this.hourMaxOriginal;
711                 this._defaults.minuteMax = this.minuteMaxOriginal;
712                 this._defaults.secondMax = this.secondMaxOriginal;
713                 this._defaults.millisecMax = this.millisecMaxOriginal;
714             }
715         }
716
717         if(adjustSliders !== undefined && adjustSliders === true){
718             var hourMax = parseInt((this._defaults.hourMax - ((this._defaults.hourMax - this._defaults.hourMin) % this._defaults.stepHour)) ,10),
719                 minMax  = parseInt((this._defaults.minuteMax - ((this._defaults.minuteMax - this._defaults.minuteMin) % this._defaults.stepMinute)) ,10),
720                 secMax  = parseInt((this._defaults.secondMax - ((this._defaults.secondMax - this._defaults.secondMin) % this._defaults.stepSecond)) ,10),
721                 millisecMax  = parseInt((this._defaults.millisecMax - ((this._defaults.millisecMax - this._defaults.millisecMin) % this._defaults.stepMillisec)) ,10);
722
723             if(this.hour_slider) {
724                 this.hour_slider.slider("option", { min: this._defaults.hourMin, max: hourMax }).slider('value', this.hour);
725             }
726             if(this.minute_slider) {
727                 this.minute_slider.slider("option", { min: this._defaults.minuteMin, max: minMax }).slider('value', this.minute);
728             }
729             if(this.second_slider){
730                 this.second_slider.slider("option", { min: this._defaults.secondMin, max: secMax }).slider('value', this.second);
731             }
732             if(this.millisec_slider) {
733                 this.millisec_slider.slider("option", { min: this._defaults.millisecMin, max: millisecMax }).slider('value', this.millisec);
734             }
735         }
736
737     },
738
739
740     //########################################################################
741     // when a slider moves, set the internal time...
742     // on time change is also called when the time is updated in the text field
743     //########################################################################
744     _onTimeChange: function() {
745         var hour   = (this.hour_slider) ? this.hour_slider.slider('value') : false,
746             minute = (this.minute_slider) ? this.minute_slider.slider('value') : false,
747             second = (this.second_slider) ? this.second_slider.slider('value') : false,
748             millisec = (this.millisec_slider) ? this.millisec_slider.slider('value') : false,
749             timezone = (this.timezone_select) ? this.timezone_select.val() : false,
750             o = this._defaults;
751
752         if (typeof(hour) == 'object') { hour = false; }
753         if (typeof(minute) == 'object') { minute = false; }
754         if (typeof(second) == 'object') { second = false; }
755         if (typeof(millisec) == 'object') { millisec = false; }
756         if (typeof(timezone) == 'object') { timezone = false; }
757
758         if (hour !== false) { hour = parseInt(hour,10); }
759         if (minute !== false) { minute = parseInt(minute,10); }
760         if (second !== false) { second = parseInt(second,10); }
761         if (millisec !== false) { millisec = parseInt(millisec,10); }
762
763         var ampm = o[hour < 12 ? 'amNames' : 'pmNames'][0];
764
765         // If the update was done in the input field, the input field should not be updated.
766         // If the update was done using the sliders, update the input field.
767         var hasChanged = (hour != this.hour || minute != this.minute ||
768                 second != this.second || millisec != this.millisec ||
769                 (this.ampm.length > 0 &&
770                     (hour < 12) != ($.inArray(this.ampm.toUpperCase(), this.amNames) !== -1)) ||
771                 timezone != this.timezone);
772
773         if (hasChanged) {
774
775             if (hour !== false) { this.hour = hour; }
776             if (minute !== false) { this.minute = minute; }
777             if (second !== false) { this.second = second; }
778             if (millisec !== false) { this.millisec = millisec; }
779             if (timezone !== false) { this.timezone = timezone; }
780
781             if (!this.inst) { this.inst = $.datepicker._getInst(this.$input[0]); }
782
783             this._limitMinMaxDateTime(this.inst, true);
784         }
785         if (o.ampm) { this.ampm = ampm; }
786
787         //this._formatTime();
788         this.formattedTime = $.datepicker.formatTime(this._defaults.timeFormat, this, this._defaults);
789         if (this.$timeObj) { this.$timeObj.text(this.formattedTime + o.timeSuffix); }
790         this.timeDefined = true;
791         if (hasChanged) { this._updateDateTime(); }
792     },
793
794     //########################################################################
795     // call custom onSelect.
796     // bind to sliders slidestop, and grid click.
797     //########################################################################
798     _onSelectHandler: function() {
799         var onSelect = this._defaults.onSelect;
800         var inputEl = this.$input ? this.$input[0] : null;
801         if (onSelect && inputEl) {
802             onSelect.apply(inputEl, [this.formattedDateTime, this]);
803         }
804     },
805
806     //########################################################################
807     // left for any backwards compatibility
808     //########################################################################
809     _formatTime: function(time, format) {
810         time = time || { hour: this.hour, minute: this.minute, second: this.second, millisec: this.millisec, ampm: this.ampm, timezone: this.timezone };
811         var tmptime = (format || this._defaults.timeFormat).toString();
812
813         tmptime = $.datepicker.formatTime(tmptime, time, this._defaults);
814
815         if (arguments.length) { return tmptime; }
816         else { this.formattedTime = tmptime; }
817     },
818
819     //########################################################################
820     // update our input with the new date time..
821     //########################################################################
822     _updateDateTime: function(dp_inst) {
823         dp_inst = this.inst || dp_inst;
824         var dt = $.datepicker._daylightSavingAdjust(new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay)),
825             dateFmt = $.datepicker._get(dp_inst, 'dateFormat'),
826             formatCfg = $.datepicker._getFormatConfig(dp_inst),
827             timeAvailable = dt !== null && this.timeDefined;
828         this.formattedDate = $.datepicker.formatDate(dateFmt, (dt === null ? new Date() : dt), formatCfg);
829         var formattedDateTime = this.formattedDate;
830         // remove following lines to force every changes in date picker to change the input value
831         // Bug descriptions: when an input field has a default value, and click on the field to pop up the date picker.
832         // If the user manually empty the value in the input field, the date picker will never change selected value.
833         //if (dp_inst.lastVal !== undefined && (dp_inst.lastVal.length > 0 && this.$input.val().length === 0)) {
834         //      return;
835         //}
836
837         if (this._defaults.timeOnly === true) {
838             formattedDateTime = this.formattedTime;
839         } else if (this._defaults.timeOnly !== true && (this._defaults.alwaysSetTime || timeAvailable)) {
840             formattedDateTime += this._defaults.separator + this.formattedTime + this._defaults.timeSuffix;
841         }
842
843         this.formattedDateTime = formattedDateTime;
844
845         if(!this._defaults.showTimepicker) {
846             this.$input.val(this.formattedDate);
847         } else if (this.$altInput && this._defaults.altFieldTimeOnly === true) {
848             this.$altInput.val(this.formattedTime);
849             this.$input.val(this.formattedDate);
850         } else if(this.$altInput) {
851             this.$altInput.val(formattedDateTime);
852             this.$input.val(formattedDateTime);
853         } else {
854             this.$input.val(formattedDateTime);
855         }
856
857         this.$input.trigger("change");
858     }
859
860 });
861
862 $.fn.extend({
863     //########################################################################
864     // shorthand just to use timepicker..
865     //########################################################################
866     timepicker: function(o) {
867         o = o || {};
868         var tmp_args = arguments;
869
870         if (typeof o == 'object') { tmp_args[0] = $.extend(o, { timeOnly: true }); }
871
872         return $(this).each(function() {
873             $.fn.datetimepicker.apply($(this), tmp_args);
874         });
875     },
876
877     //########################################################################
878     // extend timepicker to datepicker
879     //########################################################################
880     datetimepicker: function(o) {
881         o = o || {};
882         var tmp_args = arguments;
883
884         if (typeof(o) == 'string'){
885             if(o == 'getDate') {
886                 return $.fn.datepicker.apply($(this[0]), tmp_args);
887             }
888             else {
889                 return this.each(function() {
890                     var $t = $(this);
891                     $t.datepicker.apply($t, tmp_args);
892                 });
893             }
894         }
895         else {
896             return this.each(function() {
897                 var $t = $(this);
898                 $t.datepicker($.timepicker._newInst($t, o)._defaults);
899             });
900         }
901     }
902 });
903
904 $.datepicker.parseDateTime = function(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings) {
905     var parseRes = parseDateTimeInternal(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings);
906     if (parseRes.timeObj)
907     {
908         var t = parseRes.timeObj;
909         parseRes.date.setHours(t.hour, t.minute, t.second, t.millisec);
910     }
911
912     return parseRes.date;
913 };
914
915 $.datepicker.parseTime = function(timeFormat, timeString, options) {
916
917     //########################################################################
918     // pattern for standard and localized AM/PM markers
919     //########################################################################
920     var getPatternAmpm = function(amNames, pmNames) {
921         var markers = [];
922         if (amNames) {
923             $.merge(markers, amNames);
924         }
925         if (pmNames) {
926             $.merge(markers, pmNames);
927         }
928         markers = $.map(markers, function(val) { return val.replace(/[.*+?|()\[\]{}\\]/g, '\\$&'); });
929         return '(' + markers.join('|') + ')?';
930     };
931
932     //########################################################################
933     // figure out position of time elements.. cause js cant do named captures
934     //########################################################################
935     var getFormatPositions = function( timeFormat ) {
936         var finds = timeFormat.toLowerCase().match(/(h{1,2}|m{1,2}|s{1,2}|l{1}|t{1,2}|z)/g),
937             orders = { h: -1, m: -1, s: -1, l: -1, t: -1, z: -1 };
938
939         if (finds) {
940             for (var i = 0; i < finds.length; i++) {
941                 if (orders[finds[i].toString().charAt(0)] == -1) {
942                     orders[finds[i].toString().charAt(0)] = i + 1;
943                 }
944             }
945         }
946         return orders;
947     };
948
949     var o = extendRemove(extendRemove({}, $.timepicker._defaults), options || {});
950
951     var regstr = '^' + timeFormat.toString()
952             .replace(/h{1,2}/ig, '(\\d?\\d)')
953             .replace(/m{1,2}/ig, '(\\d?\\d)')
954             .replace(/s{1,2}/ig, '(\\d?\\d)')
955             .replace(/l{1}/ig, '(\\d?\\d?\\d)')
956             .replace(/t{1,2}/ig, getPatternAmpm(o.amNames, o.pmNames))
957             .replace(/z{1}/ig, '(z|[-+]\\d\\d:?\\d\\d)?')
958             .replace(/\s/g, '\\s?') + o.timeSuffix + '$',
959         order = getFormatPositions(timeFormat),
960         ampm = '',
961         treg;
962
963     treg = timeString.match(new RegExp(regstr, 'i'));
964
965     var resTime = {hour: 0, minute: 0, second: 0, millisec: 0};
966
967     if (treg) {
968         if (order.t !== -1) {
969             if (treg[order.t] === undefined || treg[order.t].length === 0) {
970                 ampm = '';
971                 resTime.ampm = '';
972             } else {
973                 ampm = $.inArray(treg[order.t], o.amNames) !== -1 ? 'AM' : 'PM';
974                 resTime.ampm = o[ampm == 'AM' ? 'amNames' : 'pmNames'][0];
975             }
976         }
977
978         if (order.h !== -1) {
979             if (ampm == 'AM' && treg[order.h] == '12') {
980                 resTime.hour = 0; // 12am = 0 hour
981             } else {
982                 if (ampm == 'PM' && treg[order.h] != '12') {
983                     resTime.hour = parseInt(treg[order.h],10) + 12; // 12pm = 12 hour, any other pm = hour + 12
984                 }
985                 else { resTime.hour = Number(treg[order.h]); }
986             }
987         }
988
989         if (order.m !== -1) { resTime.minute = Number(treg[order.m]); }
990         if (order.s !== -1) { resTime.second = Number(treg[order.s]); }
991         if (order.l !== -1) { resTime.millisec = Number(treg[order.l]); }
992         if (order.z !== -1 && treg[order.z] !== undefined) {
993             var tz = treg[order.z].toUpperCase();
994             switch (tz.length) {
995                 case 1: // Z
996                     tz = o.timezoneIso8601 ? 'Z' : '+0000';
997                     break;
998                 case 5: // +hhmm
999                     if (o.timezoneIso8601) {
1000                         tz = tz.substring(1) == '0000' ?
1001                             'Z' :
1002                             tz.substring(0, 3) + ':' + tz.substring(3);
1003                     }
1004                     break;
1005                 case 6: // +hh:mm
1006                     if (!o.timezoneIso8601) {
1007                         tz = tz == 'Z' || tz.substring(1) == '00:00' ?
1008                             '+0000' :
1009                             tz.replace(/:/, '');
1010                     } else {
1011                         if (tz.substring(1) == '00:00') {
1012                             tz = 'Z';
1013                         }
1014                     }
1015                     break;
1016             }
1017             resTime.timezone = tz;
1018         }
1019
1020
1021         return resTime;
1022     }
1023
1024     return false;
1025 };
1026
1027 //########################################################################
1028 // format the time all pretty...
1029 // format = string format of the time
1030 // time = a {}, not a Date() for timezones
1031 // options = essentially the regional[].. amNames, pmNames, ampm
1032 //########################################################################
1033 $.datepicker.formatTime = function(format, time, options) {
1034     options = options || {};
1035     options = $.extend($.timepicker._defaults, options);
1036     time = $.extend({hour:0, minute:0, second:0, millisec:0, timezone:'+0000'}, time);
1037
1038     var tmptime = format;
1039     var ampmName = options.amNames[0];
1040
1041     var hour = parseInt(time.hour, 10);
1042     if (options.ampm) {
1043         if (hour > 11){
1044             ampmName = options.pmNames[0];
1045             if(hour > 12) {
1046                 hour = hour % 12;
1047             }
1048         }
1049         if (hour === 0) {
1050             hour = 12;
1051         }
1052     }
1053     tmptime = tmptime.replace(/(?:hh?|mm?|ss?|[tT]{1,2}|[lz])/g, function(match) {
1054         switch (match.toLowerCase()) {
1055             case 'hh': return ('0' + hour).slice(-2);
1056             case 'h':  return hour;
1057             case 'mm': return ('0' + time.minute).slice(-2);
1058             case 'm':  return time.minute;
1059             case 'ss': return ('0' + time.second).slice(-2);
1060             case 's':  return time.second;
1061             case 'l':  return ('00' + time.millisec).slice(-3);
1062             case 'z':  return time.timezone;
1063             case 't': case 'tt':
1064                 if (options.ampm) {
1065                     if (match.length == 1) {
1066                         ampmName = ampmName.charAt(0);
1067                     }
1068                     return match.charAt(0) == 'T' ? ampmName.toUpperCase() : ampmName.toLowerCase();
1069                 }
1070                 return '';
1071         }
1072     });
1073
1074     tmptime = $.trim(tmptime);
1075     return tmptime;
1076 };
1077
1078 //########################################################################
1079 // the bad hack :/ override datepicker so it doesnt close on select
1080 // inspired: http://stackoverflow.com/questions/1252512/jquery-datepicker-prevent-closing-picker-when-clicking-a-date/1762378#1762378
1081 //########################################################################
1082 $.datepicker._base_selectDate = $.datepicker._selectDate;
1083 $.datepicker._selectDate = function (id, dateStr) {
1084     var inst = this._getInst($(id)[0]),
1085         tp_inst = this._get(inst, 'timepicker');
1086
1087     if (tp_inst) {
1088         tp_inst._limitMinMaxDateTime(inst, true);
1089         inst.inline = inst.stay_open = true;
1090         //This way the onSelect handler called from calendarpicker get the full dateTime
1091         this._base_selectDate(id, dateStr);
1092         inst.inline = inst.stay_open = false;
1093         this._notifyChange(inst);
1094         this._updateDatepicker(inst);
1095     }
1096     else { this._base_selectDate(id, dateStr); }
1097 };
1098
1099 //#############################################################################################
1100 // second bad hack :/ override datepicker so it triggers an event when changing the input field
1101 // and does not redraw the datepicker on every selectDate event
1102 //#############################################################################################
1103 $.datepicker._base_updateDatepicker = $.datepicker._updateDatepicker;
1104 $.datepicker._updateDatepicker = function(inst) {
1105
1106     // don't popup the datepicker if there is another instance already opened
1107     var input = inst.input[0];
1108     if($.datepicker._curInst &&
1109        $.datepicker._curInst != inst &&
1110        $.datepicker._datepickerShowing &&
1111        $.datepicker._lastInput != input) {
1112         return;
1113     }
1114
1115     if (typeof(inst.stay_open) !== 'boolean' || inst.stay_open === false) {
1116
1117         this._base_updateDatepicker(inst);
1118
1119         // Reload the time control when changing something in the input text field.
1120         var tp_inst = this._get(inst, 'timepicker');
1121         if(tp_inst) {
1122             tp_inst._addTimePicker(inst);
1123
1124             if (tp_inst._defaults.useLocalTimezone) { //checks daylight saving with the new date.
1125                 var date = new Date(inst.selectedYear, inst.selectedMonth, inst.selectedDay, 12);
1126                 selectLocalTimeZone(tp_inst, date);
1127                 tp_inst._onTimeChange();
1128             }
1129         }
1130     }
1131 };
1132
1133 //#######################################################################################
1134 // third bad hack :/ override datepicker so it allows spaces and colon in the input field
1135 //#######################################################################################
1136 $.datepicker._base_doKeyPress = $.datepicker._doKeyPress;
1137 $.datepicker._doKeyPress = function(event) {
1138     var inst = $.datepicker._getInst(event.target),
1139         tp_inst = $.datepicker._get(inst, 'timepicker');
1140
1141     if (tp_inst) {
1142         if ($.datepicker._get(inst, 'constrainInput')) {
1143             var ampm = tp_inst._defaults.ampm,
1144                 dateChars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat')),
1145                 datetimeChars = tp_inst._defaults.timeFormat.toString()
1146                                 .replace(/[hms]/g, '')
1147                                 .replace(/TT/g, ampm ? 'APM' : '')
1148                                 .replace(/Tt/g, ampm ? 'AaPpMm' : '')
1149                                 .replace(/tT/g, ampm ? 'AaPpMm' : '')
1150                                 .replace(/T/g, ampm ? 'AP' : '')
1151                                 .replace(/tt/g, ampm ? 'apm' : '')
1152                                 .replace(/t/g, ampm ? 'ap' : '') +
1153                                 " " +
1154                                 tp_inst._defaults.separator +
1155                                 tp_inst._defaults.timeSuffix +
1156                                 (tp_inst._defaults.showTimezone ? tp_inst._defaults.timezoneList.join('') : '') +
1157                                 (tp_inst._defaults.amNames.join('')) +
1158                                 (tp_inst._defaults.pmNames.join('')) +
1159                                 dateChars,
1160                 chr = String.fromCharCode(event.charCode === undefined ? event.keyCode : event.charCode);
1161             return event.ctrlKey || (chr < ' ' || !dateChars || datetimeChars.indexOf(chr) > -1);
1162         }
1163     }
1164
1165     return $.datepicker._base_doKeyPress(event);
1166 };
1167
1168 //#######################################################################################
1169 // Override key up event to sync manual input changes.
1170 //#######################################################################################
1171 $.datepicker._base_doKeyUp = $.datepicker._doKeyUp;
1172 $.datepicker._doKeyUp = function (event) {
1173     var inst = $.datepicker._getInst(event.target),
1174         tp_inst = $.datepicker._get(inst, 'timepicker');
1175
1176     if (tp_inst) {
1177         if (tp_inst._defaults.timeOnly && (inst.input.val() != inst.lastVal)) {
1178             try {
1179                 $.datepicker._updateDatepicker(inst);
1180             }
1181             catch (err) {
1182                 $.datepicker.log(err);
1183             }
1184         }
1185     }
1186
1187     return $.datepicker._base_doKeyUp(event);
1188 };
1189
1190 //#######################################################################################
1191 // override "Today" button to also grab the time.
1192 //#######################################################################################
1193 $.datepicker._base_gotoToday = $.datepicker._gotoToday;
1194 $.datepicker._gotoToday = function(id) {
1195     var inst = this._getInst($(id)[0]),
1196         $dp = inst.dpDiv;
1197     this._base_gotoToday(id);
1198     var tp_inst = this._get(inst, 'timepicker');
1199     selectLocalTimeZone(tp_inst);
1200     var now = new Date();
1201     this._setTime(inst, now);
1202     $( '.ui-datepicker-today', $dp).click();
1203 };
1204
1205 //#######################################################################################
1206 // Disable & enable the Time in the datetimepicker
1207 //#######################################################################################
1208 $.datepicker._disableTimepickerDatepicker = function(target) {
1209     var inst = this._getInst(target);
1210     if (!inst) { return; }
1211
1212     var tp_inst = this._get(inst, 'timepicker');
1213     $(target).datepicker('getDate'); // Init selected[Year|Month|Day]
1214     if (tp_inst) {
1215         tp_inst._defaults.showTimepicker = false;
1216         tp_inst._updateDateTime(inst);
1217     }
1218 };
1219
1220 $.datepicker._enableTimepickerDatepicker = function(target) {
1221     var inst = this._getInst(target);
1222     if (!inst) { return; }
1223
1224     var tp_inst = this._get(inst, 'timepicker');
1225     $(target).datepicker('getDate'); // Init selected[Year|Month|Day]
1226     if (tp_inst) {
1227         tp_inst._defaults.showTimepicker = true;
1228         tp_inst._addTimePicker(inst); // Could be disabled on page load
1229         tp_inst._updateDateTime(inst);
1230     }
1231 };
1232
1233 //#######################################################################################
1234 // Create our own set time function
1235 //#######################################################################################
1236 $.datepicker._setTime = function(inst, date) {
1237     var tp_inst = this._get(inst, 'timepicker');
1238     if (tp_inst) {
1239         var defaults = tp_inst._defaults,
1240             // calling _setTime with no date sets time to defaults
1241             hour = date ? date.getHours() : defaults.hour,
1242             minute = date ? date.getMinutes() : defaults.minute,
1243             second = date ? date.getSeconds() : defaults.second,
1244             millisec = date ? date.getMilliseconds() : defaults.millisec;
1245         //check if within min/max times..
1246         // correct check if within min/max times.
1247         // Rewritten by Scott A. Woodward
1248         var hourEq = hour === defaults.hourMin,
1249             minuteEq = minute === defaults.minuteMin,
1250             secondEq = second === defaults.secondMin;
1251         var reset = false;
1252         if(hour < defaults.hourMin || hour > defaults.hourMax)
1253             reset = true;
1254         else if( (minute < defaults.minuteMin || minute > defaults.minuteMax) && hourEq)
1255             reset = true;
1256         else if( (second < defaults.secondMin || second > defaults.secondMax ) && hourEq && minuteEq)
1257             reset = true;
1258         else if( (millisec < defaults.millisecMin || millisec > defaults.millisecMax) && hourEq && minuteEq && secondEq)
1259             reset = true;
1260         if(reset) {
1261             hour = defaults.hourMin;
1262             minute = defaults.minuteMin;
1263             second = defaults.secondMin;
1264             millisec = defaults.millisecMin;
1265         }
1266         tp_inst.hour = hour;
1267         tp_inst.minute = minute;
1268         tp_inst.second = second;
1269         tp_inst.millisec = millisec;
1270         if (tp_inst.hour_slider) tp_inst.hour_slider.slider('value', hour);
1271         if (tp_inst.minute_slider) tp_inst.minute_slider.slider('value', minute);
1272         if (tp_inst.second_slider) tp_inst.second_slider.slider('value', second);
1273         if (tp_inst.millisec_slider) tp_inst.millisec_slider.slider('value', millisec);
1274
1275         tp_inst._onTimeChange();
1276         tp_inst._updateDateTime(inst);
1277     }
1278 };
1279
1280 //#######################################################################################
1281 // Create new public method to set only time, callable as $().datepicker('setTime', date)
1282 //#######################################################################################
1283 $.datepicker._setTimeDatepicker = function(target, date, withDate) {
1284     var inst = this._getInst(target);
1285     if (!inst) { return; }
1286
1287     var tp_inst = this._get(inst, 'timepicker');
1288
1289     if (tp_inst) {
1290         this._setDateFromField(inst);
1291         var tp_date;
1292         if (date) {
1293             if (typeof date == "string") {
1294                 tp_inst._parseTime(date, withDate);
1295                 tp_date = new Date();
1296                 tp_date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec);
1297             }
1298             else { tp_date = new Date(date.getTime()); }
1299             if (tp_date.toString() == 'Invalid Date') { tp_date = undefined; }
1300             this._setTime(inst, tp_date);
1301         }
1302     }
1303
1304 };
1305
1306 //#######################################################################################
1307 // override setDate() to allow setting time too within Date object
1308 //#######################################################################################
1309 $.datepicker._base_setDateDatepicker = $.datepicker._setDateDatepicker;
1310 $.datepicker._setDateDatepicker = function(target, date) {
1311     var inst = this._getInst(target);
1312     if (!inst) { return; }
1313
1314     var tp_date = (date instanceof Date) ? new Date(date.getTime()) : date;
1315
1316     this._updateDatepicker(inst);
1317     this._base_setDateDatepicker.apply(this, arguments);
1318     this._setTimeDatepicker(target, tp_date, true);
1319 };
1320
1321 //#######################################################################################
1322 // override getDate() to allow getting time too within Date object
1323 //#######################################################################################
1324 $.datepicker._base_getDateDatepicker = $.datepicker._getDateDatepicker;
1325 $.datepicker._getDateDatepicker = function(target, noDefault) {
1326     var inst = this._getInst(target);
1327     if (!inst) { return; }
1328
1329     var tp_inst = this._get(inst, 'timepicker');
1330
1331     if (tp_inst) {
1332         this._setDateFromField(inst, noDefault);
1333         var date = this._getDate(inst);
1334         if (date && tp_inst._parseTime($(target).val(), tp_inst.timeOnly)) { date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec); }
1335         return date;
1336     }
1337     return this._base_getDateDatepicker(target, noDefault);
1338 };
1339
1340 //#######################################################################################
1341 // override parseDate() because UI 1.8.14 throws an error about "Extra characters"
1342 // An option in datapicker to ignore extra format characters would be nicer.
1343 //#######################################################################################
1344 $.datepicker._base_parseDate = $.datepicker.parseDate;
1345 $.datepicker.parseDate = function(format, value, settings) {
1346     var splitRes = splitDateTime(format, value, settings);
1347     return $.datepicker._base_parseDate(format, splitRes[0], settings);
1348 };
1349
1350 //#######################################################################################
1351 // override formatDate to set date with time to the input
1352 //#######################################################################################
1353 $.datepicker._base_formatDate = $.datepicker._formatDate;
1354 $.datepicker._formatDate = function(inst, day, month, year){
1355     var tp_inst = this._get(inst, 'timepicker');
1356     if(tp_inst) {
1357         tp_inst._updateDateTime(inst);
1358         return tp_inst.$input.val();
1359     }
1360     return this._base_formatDate(inst);
1361 };
1362
1363 //#######################################################################################
1364 // override options setter to add time to maxDate(Time) and minDate(Time). MaxDate
1365 //#######################################################################################
1366 $.datepicker._base_optionDatepicker = $.datepicker._optionDatepicker;
1367 $.datepicker._optionDatepicker = function(target, name, value) {
1368     var inst = this._getInst(target);
1369     if (!inst) { return null; }
1370
1371     var tp_inst = this._get(inst, 'timepicker');
1372     if (tp_inst) {
1373         var min = null, max = null, onselect = null;
1374         if (typeof name == 'string') { // if min/max was set with the string
1375             if (name === 'minDate' || name === 'minDateTime' ) {
1376                 min = value;
1377             }
1378             else {
1379                 if (name === 'maxDate' || name === 'maxDateTime') {
1380                     max = value;
1381                 }
1382                 else {
1383                     if (name === 'onSelect') {
1384                         onselect = value;
1385                     }
1386                 }
1387             }
1388         } else {
1389             if (typeof name == 'object') { //if min/max was set with the JSON
1390                 if (name.minDate) {
1391                     min = name.minDate;
1392                 } else {
1393                     if (name.minDateTime) {
1394                         min = name.minDateTime;
1395                     } else {
1396                         if (name.maxDate) {
1397                             max = name.maxDate;
1398                         } else {
1399                             if (name.maxDateTime) {
1400                                 max = name.maxDateTime;
1401                             }
1402                         }
1403                     }
1404                 }
1405             }
1406         }
1407         if(min) { //if min was set
1408             if (min === 0) {
1409                 min = new Date();
1410             } else {
1411                 min = new Date(min);
1412             }
1413
1414             tp_inst._defaults.minDate = min;
1415             tp_inst._defaults.minDateTime = min;
1416         } else if (max) { //if max was set
1417             if(max===0) {
1418                 max=new Date();
1419             } else {
1420                 max= new Date(max);
1421             }
1422             tp_inst._defaults.maxDate = max;
1423             tp_inst._defaults.maxDateTime = max;
1424         } else if (onselect) {
1425             tp_inst._defaults.onSelect = onselect;
1426         }
1427     }
1428     if (value === undefined) {
1429         return this._base_optionDatepicker(target, name);
1430     }
1431     return this._base_optionDatepicker(target, name, value);
1432 };
1433
1434 //#######################################################################################
1435 // jQuery extend now ignores nulls!
1436 //#######################################################################################
1437 function extendRemove(target, props) {
1438     $.extend(target, props);
1439     for (var name in props) {
1440         if (props[name] === null || props[name] === undefined) {
1441             target[name] = props[name];
1442         }
1443     }
1444     return target;
1445 }
1446
1447 //#######################################################################################
1448 // Splits datetime string into date ans time substrings.
1449 // Throws exception when date can't be parsed
1450 // If only date is present, time substring eill be ''
1451 //#######################################################################################
1452 var splitDateTime = function(dateFormat, dateTimeString, dateSettings)
1453 {
1454     try {
1455         var date = $.datepicker._base_parseDate(dateFormat, dateTimeString, dateSettings);
1456     } catch (err) {
1457         if (err.indexOf(":") >= 0) {
1458             // Hack!  The error message ends with a colon, a space, and
1459             // the "extra" characters.  We rely on that instead of
1460             // attempting to perfectly reproduce the parsing algorithm.
1461             var dateStringLength = dateTimeString.length-(err.length-err.indexOf(':')-2);
1462             var timeString = dateTimeString.substring(dateStringLength);
1463
1464             return [dateTimeString.substring(0, dateStringLength), dateTimeString.substring(dateStringLength)];
1465
1466         } else {
1467             throw err;
1468         }
1469     }
1470     return [dateTimeString, ''];
1471 };
1472
1473 //#######################################################################################
1474 // Internal function to parse datetime interval
1475 // Returns: {date: Date, timeObj: Object}, where
1476 //   date - parsed date without time (type Date)
1477 //   timeObj = {hour: , minute: , second: , millisec: } - parsed time. Optional
1478 //#######################################################################################
1479 var parseDateTimeInternal = function(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings)
1480 {
1481     var date;
1482     var splitRes = splitDateTime(dateFormat, dateTimeString, dateSettings);
1483     date = $.datepicker._base_parseDate(dateFormat, splitRes[0], dateSettings);
1484     if (splitRes[1] !== '')
1485     {
1486         var timeString = splitRes[1];
1487         var separator = timeSettings && timeSettings.separator ? timeSettings.separator : $.timepicker._defaults.separator;
1488         if ( timeString.indexOf(separator) !== 0) {
1489             throw 'Missing time separator';
1490         }
1491         timeString = timeString.substring(separator.length);
1492         var parsedTime = $.datepicker.parseTime(timeFormat, timeString, timeSettings);
1493         if (parsedTime === null) {
1494             throw 'Wrong time format';
1495         }
1496         return {date: date, timeObj: parsedTime};
1497     } else {
1498         return {date: date};
1499     }
1500 };
1501
1502 //#######################################################################################
1503 // Internal function to set timezone_select to the local timezone
1504 //#######################################################################################
1505 var selectLocalTimeZone = function(tp_inst, date)
1506 {
1507     if (tp_inst && tp_inst.timezone_select) {
1508         tp_inst._defaults.useLocalTimezone = true;
1509         var now = typeof date !== 'undefined' ? date : new Date();
1510         var tzoffset = timeZoneString(now);
1511         if (tp_inst._defaults.timezoneIso8601) {
1512             tzoffset = tzoffset.substring(0, 3) + ':' + tzoffset.substring(3);
1513         }
1514         tp_inst.timezone_select.val(tzoffset);
1515     }
1516 };
1517
1518 // Input: Date Object
1519 // Output: String with timezone offset, e.g. '+0100'
1520 var timeZoneString = function(date)
1521 {
1522     var off = date.getTimezoneOffset() * -10100 / 60;
1523     var timezone = (off >= 0 ? '+' : '-') + Math.abs(off).toString().substr(1);
1524     return timezone;
1525 };
1526
1527 $.timepicker = new Timepicker(); // singleton instance
1528 $.timepicker.version = "1.0.1";
1529
1530 })(jQuery);