github.com/apremalal/vamps-core@v1.0.1-0.20161221121535-d430b56ec174/server/webapps/app/base/plugins/bootstrap-datepicker/js/bootstrap-datepicker.js (about)

     1  /* =========================================================
     2   * bootstrap-datepicker.js
     3   * Repo: https://github.com/eternicode/bootstrap-datepicker/
     4   * Demo: http://eternicode.github.io/bootstrap-datepicker/
     5   * Docs: http://bootstrap-datepicker.readthedocs.org/
     6   * Forked from http://www.eyecon.ro/bootstrap-datepicker
     7   * =========================================================
     8   * Started by Stefan Petre; improvements by Andrew Rowls + contributors
     9   *
    10   * Licensed under the Apache License, Version 2.0 (the "License");
    11   * you may not use this file except in compliance with the License.
    12   * You may obtain a copy of the License at
    13   *
    14   * http://www.apache.org/licenses/LICENSE-2.0
    15   *
    16   * Unless required by applicable law or agreed to in writing, software
    17   * distributed under the License is distributed on an "AS IS" BASIS,
    18   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    19   * See the License for the specific language governing permissions and
    20   * limitations under the License.
    21   * ========================================================= */
    22  
    23  (function($, undefined){
    24  
    25  	var $window = $(window);
    26  
    27  	function UTCDate(){
    28  		return new Date(Date.UTC.apply(Date, arguments));
    29  	}
    30  	function UTCToday(){
    31  		var today = new Date();
    32  		return UTCDate(today.getFullYear(), today.getMonth(), today.getDate());
    33  	}
    34  	function alias(method){
    35  		return function(){
    36  			return this[method].apply(this, arguments);
    37  		};
    38  	}
    39  
    40  	var DateArray = (function(){
    41  		var extras = {
    42  			get: function(i){
    43  				return this.slice(i)[0];
    44  			},
    45  			contains: function(d){
    46  				// Array.indexOf is not cross-browser;
    47  				// $.inArray doesn't work with Dates
    48  				var val = d && d.valueOf();
    49  				for (var i=0, l=this.length; i < l; i++)
    50  					if (this[i].valueOf() === val)
    51  						return i;
    52  				return -1;
    53  			},
    54  			remove: function(i){
    55  				this.splice(i,1);
    56  			},
    57  			replace: function(new_array){
    58  				if (!new_array)
    59  					return;
    60  				if (!$.isArray(new_array))
    61  					new_array = [new_array];
    62  				this.clear();
    63  				this.push.apply(this, new_array);
    64  			},
    65  			clear: function(){
    66  				this.splice(0);
    67  			},
    68  			copy: function(){
    69  				var a = new DateArray();
    70  				a.replace(this);
    71  				return a;
    72  			}
    73  		};
    74  
    75  		return function(){
    76  			var a = [];
    77  			a.push.apply(a, arguments);
    78  			$.extend(a, extras);
    79  			return a;
    80  		};
    81  	})();
    82  
    83  
    84  	// Picker object
    85  
    86  	var Datepicker = function(element, options){
    87  		this.dates = new DateArray();
    88  		this.viewDate = UTCToday();
    89  		this.focusDate = null;
    90  
    91  		this._process_options(options);
    92  
    93  		this.element = $(element);
    94  		this.isInline = false;
    95  		this.isInput = this.element.is('input');
    96  		this.component = this.element.is('.date') ? this.element.find('.add-on, .input-group-addon, .btn') : false;
    97  		this.hasInput = this.component && this.element.find('input').length;
    98  		if (this.component && this.component.length === 0)
    99  			this.component = false;
   100  
   101  		this.picker = $(DPGlobal.template);
   102  		this._buildEvents();
   103  		this._attachEvents();
   104  
   105  		if (this.isInline){
   106  			this.picker.addClass('datepicker-inline').appendTo(this.element);
   107  		}
   108  		else {
   109  			this.picker.addClass('datepicker-dropdown dropdown-menu');
   110  		}
   111  
   112  		if (this.o.rtl){
   113  			this.picker.addClass('datepicker-rtl');
   114  			this.picker.find('.prev i, .next i')
   115  						.toggleClass('fa-angle-left fa-angle-right');
   116  		}
   117  
   118  		this.viewMode = this.o.startView;
   119  
   120  		if (this.o.calendarWeeks)
   121  			this.picker.find('tfoot th.today')
   122  						.attr('colspan', function(i, val){
   123  							return parseInt(val) + 1;
   124  						});
   125  
   126  		this._allow_update = false;
   127  
   128  		this.setStartDate(this._o.startDate);
   129  		this.setEndDate(this._o.endDate);
   130  		this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled);
   131  
   132  		this.fillDow();
   133  		this.fillMonths();
   134  
   135  		this._allow_update = true;
   136  
   137  		this.update();
   138  		this.showMode();
   139  
   140  		if (this.isInline){
   141  			this.show();
   142  		}
   143  	};
   144  
   145  	Datepicker.prototype = {
   146  		constructor: Datepicker,
   147  
   148  		_process_options: function(opts){
   149  			// Store raw options for reference
   150  			this._o = $.extend({}, this._o, opts);
   151  			// Processed options
   152  			var o = this.o = $.extend({}, this._o);
   153  
   154  			// Check if "de-DE" style date is available, if not language should
   155  			// fallback to 2 letter code eg "de"
   156  			var lang = o.language;
   157  			if (!dates[lang]){
   158  				lang = lang.split('-')[0];
   159  				if (!dates[lang])
   160  					lang = defaults.language;
   161  			}
   162  			o.language = lang;
   163  
   164  			switch (o.startView){
   165  				case 2:
   166  				case 'decade':
   167  					o.startView = 2;
   168  					break;
   169  				case 1:
   170  				case 'year':
   171  					o.startView = 1;
   172  					break;
   173  				default:
   174  					o.startView = 0;
   175  			}
   176  
   177  			switch (o.minViewMode){
   178  				case 1:
   179  				case 'months':
   180  					o.minViewMode = 1;
   181  					break;
   182  				case 2:
   183  				case 'years':
   184  					o.minViewMode = 2;
   185  					break;
   186  				default:
   187  					o.minViewMode = 0;
   188  			}
   189  
   190  			o.startView = Math.max(o.startView, o.minViewMode);
   191  
   192  			// true, false, or Number > 0
   193  			if (o.multidate !== true){
   194  				o.multidate = Number(o.multidate) || false;
   195  				if (o.multidate !== false)
   196  					o.multidate = Math.max(0, o.multidate);
   197  				else
   198  					o.multidate = 1;
   199  			}
   200  			o.multidateSeparator = String(o.multidateSeparator);
   201  
   202  			o.weekStart %= 7;
   203  			o.weekEnd = ((o.weekStart + 6) % 7);
   204  
   205  			var format = DPGlobal.parseFormat(o.format);
   206  			if (o.startDate !== -Infinity){
   207  				if (!!o.startDate){
   208  					if (o.startDate instanceof Date)
   209  						o.startDate = this._local_to_utc(this._zero_time(o.startDate));
   210  					else
   211  						o.startDate = DPGlobal.parseDate(o.startDate, format, o.language);
   212  				}
   213  				else {
   214  					o.startDate = -Infinity;
   215  				}
   216  			}
   217  			if (o.endDate !== Infinity){
   218  				if (!!o.endDate){
   219  					if (o.endDate instanceof Date)
   220  						o.endDate = this._local_to_utc(this._zero_time(o.endDate));
   221  					else
   222  						o.endDate = DPGlobal.parseDate(o.endDate, format, o.language);
   223  				}
   224  				else {
   225  					o.endDate = Infinity;
   226  				}
   227  			}
   228  
   229  			o.daysOfWeekDisabled = o.daysOfWeekDisabled||[];
   230  			if (!$.isArray(o.daysOfWeekDisabled))
   231  				o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/);
   232  			o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function(d){
   233  				return parseInt(d, 10);
   234  			});
   235  
   236  			var plc = String(o.orientation).toLowerCase().split(/\s+/g),
   237  				_plc = o.orientation.toLowerCase();
   238  			plc = $.grep(plc, function(word){
   239  				return (/^auto|left|right|top|bottom$/).test(word);
   240  			});
   241  			o.orientation = {x: 'auto', y: 'auto'};
   242  			if (!_plc || _plc === 'auto')
   243  				; // no action
   244  			else if (plc.length === 1){
   245  				switch (plc[0]){
   246  					case 'top':
   247  					case 'bottom':
   248  						o.orientation.y = plc[0];
   249  						break;
   250  					case 'left':
   251  					case 'right':
   252  						o.orientation.x = plc[0];
   253  						break;
   254  				}
   255  			}
   256  			else {
   257  				_plc = $.grep(plc, function(word){
   258  					return (/^left|right$/).test(word);
   259  				});
   260  				o.orientation.x = _plc[0] || 'auto';
   261  
   262  				_plc = $.grep(plc, function(word){
   263  					return (/^top|bottom$/).test(word);
   264  				});
   265  				o.orientation.y = _plc[0] || 'auto';
   266  			}
   267  		},
   268  		_events: [],
   269  		_secondaryEvents: [],
   270  		_applyEvents: function(evs){
   271  			for (var i=0, el, ch, ev; i < evs.length; i++){
   272  				el = evs[i][0];
   273  				if (evs[i].length === 2){
   274  					ch = undefined;
   275  					ev = evs[i][1];
   276  				}
   277  				else if (evs[i].length === 3){
   278  					ch = evs[i][1];
   279  					ev = evs[i][2];
   280  				}
   281  				el.on(ev, ch);
   282  			}
   283  		},
   284  		_unapplyEvents: function(evs){
   285  			for (var i=0, el, ev, ch; i < evs.length; i++){
   286  				el = evs[i][0];
   287  				if (evs[i].length === 2){
   288  					ch = undefined;
   289  					ev = evs[i][1];
   290  				}
   291  				else if (evs[i].length === 3){
   292  					ch = evs[i][1];
   293  					ev = evs[i][2];
   294  				}
   295  				el.off(ev, ch);
   296  			}
   297  		},
   298  		_buildEvents: function(){
   299  			if (this.isInput){ // single input
   300  				this._events = [
   301  					[this.element, {
   302  						focus: $.proxy(this.show, this),
   303  						keyup: $.proxy(function(e){
   304  							if ($.inArray(e.keyCode, [27,37,39,38,40,32,13,9]) === -1)
   305  								this.update();
   306  						}, this),
   307  						keydown: $.proxy(this.keydown, this)
   308  					}]
   309  				];
   310  			}
   311  			else if (this.component && this.hasInput){ // component: input + button
   312  				this._events = [
   313  					// For components that are not readonly, allow keyboard nav
   314  					[this.element.find('input'), {
   315  						focus: $.proxy(this.show, this),
   316  						keyup: $.proxy(function(e){
   317  							if ($.inArray(e.keyCode, [27,37,39,38,40,32,13,9]) === -1)
   318  								this.update();
   319  						}, this),
   320  						keydown: $.proxy(this.keydown, this)
   321  					}],
   322  					[this.component, {
   323  						click: $.proxy(this.show, this)
   324  					}]
   325  				];
   326  			}
   327  			else if (this.element.is('div')){  // inline datepicker
   328  				this.isInline = true;
   329  			}
   330  			else {
   331  				this._events = [
   332  					[this.element, {
   333  						click: $.proxy(this.show, this)
   334  					}]
   335  				];
   336  			}
   337  			this._events.push(
   338  				// Component: listen for blur on element descendants
   339  				[this.element, '*', {
   340  					blur: $.proxy(function(e){
   341  						this._focused_from = e.target;
   342  					}, this)
   343  				}],
   344  				// Input: listen for blur on element
   345  				[this.element, {
   346  					blur: $.proxy(function(e){
   347  						this._focused_from = e.target;
   348  					}, this)
   349  				}]
   350  			);
   351  
   352  			this._secondaryEvents = [
   353  				[this.picker, {
   354  					click: $.proxy(this.click, this)
   355  				}],
   356  				[$(window), {
   357  					resize: $.proxy(this.place, this)
   358  				}],
   359  				[$(document), {
   360  					'mousedown touchstart': $.proxy(function(e){
   361  						// Clicked outside the datepicker, hide it
   362  						if (!(
   363  							this.element.is(e.target) ||
   364  							this.element.find(e.target).length ||
   365  							this.picker.is(e.target) ||
   366  							this.picker.find(e.target).length
   367  						)){
   368  							this.hide();
   369  						}
   370  					}, this)
   371  				}]
   372  			];
   373  		},
   374  		_attachEvents: function(){
   375  			this._detachEvents();
   376  			this._applyEvents(this._events);
   377  		},
   378  		_detachEvents: function(){
   379  			this._unapplyEvents(this._events);
   380  		},
   381  		_attachSecondaryEvents: function(){
   382  			this._detachSecondaryEvents();
   383  			this._applyEvents(this._secondaryEvents);
   384  		},
   385  		_detachSecondaryEvents: function(){
   386  			this._unapplyEvents(this._secondaryEvents);
   387  		},
   388  		_trigger: function(event, altdate){
   389  			var date = altdate || this.dates.get(-1),
   390  				local_date = this._utc_to_local(date);
   391  
   392  			this.element.trigger({
   393  				type: event,
   394  				date: local_date,
   395  				dates: $.map(this.dates, this._utc_to_local),
   396  				format: $.proxy(function(ix, format){
   397  					if (arguments.length === 0){
   398  						ix = this.dates.length - 1;
   399  						format = this.o.format;
   400  					}
   401  					else if (typeof ix === 'string'){
   402  						format = ix;
   403  						ix = this.dates.length - 1;
   404  					}
   405  					format = format || this.o.format;
   406  					var date = this.dates.get(ix);
   407  					return DPGlobal.formatDate(date, format, this.o.language);
   408  				}, this)
   409  			});
   410  		},
   411  
   412  		show: function(){
   413  			if (!this.isInline)
   414  				this.picker.appendTo('body');
   415  			this.picker.show();
   416  			this.place();
   417  			this._attachSecondaryEvents();
   418  			this._trigger('show');
   419  		},
   420  
   421  		hide: function(){
   422  			if (this.isInline)
   423  				return;
   424  			if (!this.picker.is(':visible'))
   425  				return;
   426  			this.focusDate = null;
   427  			this.picker.hide().detach();
   428  			this._detachSecondaryEvents();
   429  			this.viewMode = this.o.startView;
   430  			this.showMode();
   431  
   432  			if (
   433  				this.o.forceParse &&
   434  				(
   435  					this.isInput && this.element.val() ||
   436  					this.hasInput && this.element.find('input').val()
   437  				)
   438  			)
   439  				this.setValue();
   440  			this._trigger('hide');
   441  		},
   442  
   443  		remove: function(){
   444  			this.hide();
   445  			this._detachEvents();
   446  			this._detachSecondaryEvents();
   447  			this.picker.remove();
   448  			delete this.element.data().datepicker;
   449  			if (!this.isInput){
   450  				delete this.element.data().date;
   451  			}
   452  		},
   453  
   454  		_utc_to_local: function(utc){
   455  			return utc && new Date(utc.getTime() + (utc.getTimezoneOffset()*60000));
   456  		},
   457  		_local_to_utc: function(local){
   458  			return local && new Date(local.getTime() - (local.getTimezoneOffset()*60000));
   459  		},
   460  		_zero_time: function(local){
   461  			return local && new Date(local.getFullYear(), local.getMonth(), local.getDate());
   462  		},
   463  		_zero_utc_time: function(utc){
   464  			return utc && new Date(Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate()));
   465  		},
   466  
   467  		getDates: function(){
   468  			return $.map(this.dates, this._utc_to_local);
   469  		},
   470  
   471  		getUTCDates: function(){
   472  			return $.map(this.dates, function(d){
   473  				return new Date(d);
   474  			});
   475  		},
   476  
   477  		getDate: function(){
   478  			return this._utc_to_local(this.getUTCDate());
   479  		},
   480  
   481  		getUTCDate: function(){
   482  			return new Date(this.dates.get(-1));
   483  		},
   484  
   485  		setDates: function(){
   486  			var args = $.isArray(arguments[0]) ? arguments[0] : arguments;
   487  			this.update.apply(this, args);
   488  			this._trigger('changeDate');
   489  			this.setValue();
   490  		},
   491  
   492  		setUTCDates: function(){
   493  			var args = $.isArray(arguments[0]) ? arguments[0] : arguments;
   494  			this.update.apply(this, $.map(args, this._utc_to_local));
   495  			this._trigger('changeDate');
   496  			this.setValue();
   497  		},
   498  
   499  		setDate: alias('setDates'),
   500  		setUTCDate: alias('setUTCDates'),
   501  
   502  		setValue: function(){
   503  			var formatted = this.getFormattedDate();
   504  			if (!this.isInput){
   505  				if (this.component){
   506  					this.element.find('input').val(formatted).change();
   507  				}
   508  			}
   509  			else {
   510  				this.element.val(formatted).change();
   511  			}
   512  		},
   513  
   514  		getFormattedDate: function(format){
   515  			if (format === undefined)
   516  				format = this.o.format;
   517  
   518  			var lang = this.o.language;
   519  			return $.map(this.dates, function(d){
   520  				return DPGlobal.formatDate(d, format, lang);
   521  			}).join(this.o.multidateSeparator);
   522  		},
   523  
   524  		setStartDate: function(startDate){
   525  			this._process_options({startDate: startDate});
   526  			this.update();
   527  			this.updateNavArrows();
   528  		},
   529  
   530  		setEndDate: function(endDate){
   531  			this._process_options({endDate: endDate});
   532  			this.update();
   533  			this.updateNavArrows();
   534  		},
   535  
   536  		setDaysOfWeekDisabled: function(daysOfWeekDisabled){
   537  			this._process_options({daysOfWeekDisabled: daysOfWeekDisabled});
   538  			this.update();
   539  			this.updateNavArrows();
   540  		},
   541  
   542  		place: function(){
   543  			if (this.isInline)
   544  				return;
   545  			var calendarWidth = this.picker.outerWidth(),
   546  				calendarHeight = this.picker.outerHeight(),
   547  				visualPadding = 10,
   548  				windowWidth = $window.width(),
   549  				windowHeight = $window.height(),
   550  				scrollTop = $window.scrollTop();
   551  
   552  			var zIndex = parseInt(this.element.parents().filter(function(){
   553  					return $(this).css('z-index') !== 'auto';
   554  				}).first().css('z-index'))+10;
   555  			var offset = this.component ? this.component.parent().offset() : this.element.offset();
   556  			var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(false);
   557  			var width = this.component ? this.component.outerWidth(true) : this.element.outerWidth(false);
   558  			var left = offset.left,
   559  				top = offset.top;
   560  
   561  			this.picker.removeClass(
   562  				'datepicker-orient-top datepicker-orient-bottom '+
   563  				'datepicker-orient-right datepicker-orient-left'
   564  			);
   565  
   566  			if (this.o.orientation.x !== 'auto'){
   567  				this.picker.addClass('datepicker-orient-' + this.o.orientation.x);
   568  				if (this.o.orientation.x === 'right')
   569  					left -= calendarWidth - width;
   570  			}
   571  			// auto x orientation is best-placement: if it crosses a window
   572  			// edge, fudge it sideways
   573  			else {
   574  				// Default to left
   575  				this.picker.addClass('datepicker-orient-left');
   576  				if (offset.left < 0)
   577  					left -= offset.left - visualPadding;
   578  				else if (offset.left + calendarWidth > windowWidth)
   579  					left = windowWidth - calendarWidth - visualPadding;
   580  			}
   581  
   582  			// auto y orientation is best-situation: top or bottom, no fudging,
   583  			// decision based on which shows more of the calendar
   584  			var yorient = this.o.orientation.y,
   585  				top_overflow, bottom_overflow;
   586  			if (yorient === 'auto'){
   587  				top_overflow = -scrollTop + offset.top - calendarHeight;
   588  				bottom_overflow = scrollTop + windowHeight - (offset.top + height + calendarHeight);
   589  				if (Math.max(top_overflow, bottom_overflow) === bottom_overflow)
   590  					yorient = 'top';
   591  				else
   592  					yorient = 'bottom';
   593  			}
   594  			this.picker.addClass('datepicker-orient-' + yorient);
   595  			if (yorient === 'top')
   596  				top += height;
   597  			else
   598  				top -= calendarHeight + parseInt(this.picker.css('padding-top'));
   599  
   600  			this.picker.css({
   601  				top: top,
   602  				left: left,
   603  				zIndex: zIndex
   604  			});
   605  		},
   606  
   607  		_allow_update: true,
   608  		update: function(){
   609  			if (!this._allow_update)
   610  				return;
   611  
   612  			var oldDates = this.dates.copy(),
   613  				dates = [],
   614  				fromArgs = false;
   615  			if (arguments.length){
   616  				$.each(arguments, $.proxy(function(i, date){
   617  					if (date instanceof Date)
   618  						date = this._local_to_utc(date);
   619  					dates.push(date);
   620  				}, this));
   621  				fromArgs = true;
   622  			}
   623  			else {
   624  				dates = this.isInput
   625  						? this.element.val()
   626  						: this.element.data('date') || this.element.find('input').val();
   627  				if (dates && this.o.multidate)
   628  					dates = dates.split(this.o.multidateSeparator);
   629  				else
   630  					dates = [dates];
   631  				delete this.element.data().date;
   632  			}
   633  
   634  			dates = $.map(dates, $.proxy(function(date){
   635  				return DPGlobal.parseDate(date, this.o.format, this.o.language);
   636  			}, this));
   637  			dates = $.grep(dates, $.proxy(function(date){
   638  				return (
   639  					date < this.o.startDate ||
   640  					date > this.o.endDate ||
   641  					!date
   642  				);
   643  			}, this), true);
   644  			this.dates.replace(dates);
   645  
   646  			if (this.dates.length)
   647  				this.viewDate = new Date(this.dates.get(-1));
   648  			else if (this.viewDate < this.o.startDate)
   649  				this.viewDate = new Date(this.o.startDate);
   650  			else if (this.viewDate > this.o.endDate)
   651  				this.viewDate = new Date(this.o.endDate);
   652  
   653  			if (fromArgs){
   654  				// setting date by clicking
   655  				this.setValue();
   656  			}
   657  			else if (dates.length){
   658  				// setting date by typing
   659  				if (String(oldDates) !== String(this.dates))
   660  					this._trigger('changeDate');
   661  			}
   662  			if (!this.dates.length && oldDates.length)
   663  				this._trigger('clearDate');
   664  
   665  			this.fill();
   666  		},
   667  
   668  		fillDow: function(){
   669  			var dowCnt = this.o.weekStart,
   670  				html = '<tr>';
   671  			if (this.o.calendarWeeks){
   672  				var cell = '<th class="cw">&nbsp;</th>';
   673  				html += cell;
   674  				this.picker.find('.datepicker-days thead tr:first-child').prepend(cell);
   675  			}
   676  			while (dowCnt < this.o.weekStart + 7){
   677  				html += '<th class="dow">'+dates[this.o.language].daysMin[(dowCnt++)%7]+'</th>';
   678  			}
   679  			html += '</tr>';
   680  			this.picker.find('.datepicker-days thead').append(html);
   681  		},
   682  
   683  		fillMonths: function(){
   684  			var html = '',
   685  			i = 0;
   686  			while (i < 12){
   687  				html += '<span class="month">'+dates[this.o.language].monthsShort[i++]+'</span>';
   688  			}
   689  			this.picker.find('.datepicker-months td').html(html);
   690  		},
   691  
   692  		setRange: function(range){
   693  			if (!range || !range.length)
   694  				delete this.range;
   695  			else
   696  				this.range = $.map(range, function(d){
   697  					return d.valueOf();
   698  				});
   699  			this.fill();
   700  		},
   701  
   702  		getClassNames: function(date){
   703  			var cls = [],
   704  				year = this.viewDate.getUTCFullYear(),
   705  				month = this.viewDate.getUTCMonth(),
   706  				today = new Date();
   707  			if (date.getUTCFullYear() < year || (date.getUTCFullYear() === year && date.getUTCMonth() < month)){
   708  				cls.push('old');
   709  			}
   710  			else if (date.getUTCFullYear() > year || (date.getUTCFullYear() === year && date.getUTCMonth() > month)){
   711  				cls.push('new');
   712  			}
   713  			if (this.focusDate && date.valueOf() === this.focusDate.valueOf())
   714  				cls.push('focused');
   715  			// Compare internal UTC date with local today, not UTC today
   716  			if (this.o.todayHighlight &&
   717  				date.getUTCFullYear() === today.getFullYear() &&
   718  				date.getUTCMonth() === today.getMonth() &&
   719  				date.getUTCDate() === today.getDate()){
   720  				cls.push('today');
   721  			}
   722  			if (this.dates.contains(date) !== -1)
   723  				cls.push('active');
   724  			if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate ||
   725  				$.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1){
   726  				cls.push('disabled');
   727  			}
   728  			if (this.range){
   729  				if (date > this.range[0] && date < this.range[this.range.length-1]){
   730  					cls.push('range');
   731  				}
   732  				if ($.inArray(date.valueOf(), this.range) !== -1){
   733  					cls.push('selected');
   734  				}
   735  			}
   736  			return cls;
   737  		},
   738  
   739  		fill: function(){
   740  			var d = new Date(this.viewDate),
   741  				year = d.getUTCFullYear(),
   742  				month = d.getUTCMonth(),
   743  				startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity,
   744  				startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity,
   745  				endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity,
   746  				endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity,
   747  				todaytxt = dates[this.o.language].today || dates['en'].today || '',
   748  				cleartxt = dates[this.o.language].clear || dates['en'].clear || '',
   749  				tooltip;
   750  			this.picker.find('.datepicker-days thead th.datepicker-switch')
   751  						.text(dates[this.o.language].months[month]+' '+year);
   752  			this.picker.find('tfoot th.today')
   753  						.text(todaytxt)
   754  						.toggle(this.o.todayBtn !== false);
   755  			this.picker.find('tfoot th.clear')
   756  						.text(cleartxt)
   757  						.toggle(this.o.clearBtn !== false);
   758  			this.updateNavArrows();
   759  			this.fillMonths();
   760  			var prevMonth = UTCDate(year, month-1, 28),
   761  				day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
   762  			prevMonth.setUTCDate(day);
   763  			prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7);
   764  			var nextMonth = new Date(prevMonth);
   765  			nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
   766  			nextMonth = nextMonth.valueOf();
   767  			var html = [];
   768  			var clsName;
   769  			while (prevMonth.valueOf() < nextMonth){
   770  				if (prevMonth.getUTCDay() === this.o.weekStart){
   771  					html.push('<tr>');
   772  					if (this.o.calendarWeeks){
   773  						// ISO 8601: First week contains first thursday.
   774  						// ISO also states week starts on Monday, but we can be more abstract here.
   775  						var
   776  							// Start of current week: based on weekstart/current date
   777  							ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5),
   778  							// Thursday of this week
   779  							th = new Date(Number(ws) + (7 + 4 - ws.getUTCDay()) % 7 * 864e5),
   780  							// First Thursday of year, year from thursday
   781  							yth = new Date(Number(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5),
   782  							// Calendar week: ms between thursdays, div ms per day, div 7 days
   783  							calWeek =  (th - yth) / 864e5 / 7 + 1;
   784  						html.push('<td class="cw">'+ calWeek +'</td>');
   785  
   786  					}
   787  				}
   788  				clsName = this.getClassNames(prevMonth);
   789  				clsName.push('day');
   790  
   791  				if (this.o.beforeShowDay !== $.noop){
   792  					var before = this.o.beforeShowDay(this._utc_to_local(prevMonth));
   793  					if (before === undefined)
   794  						before = {};
   795  					else if (typeof(before) === 'boolean')
   796  						before = {enabled: before};
   797  					else if (typeof(before) === 'string')
   798  						before = {classes: before};
   799  					if (before.enabled === false)
   800  						clsName.push('disabled');
   801  					if (before.classes)
   802  						clsName = clsName.concat(before.classes.split(/\s+/));
   803  					if (before.tooltip)
   804  						tooltip = before.tooltip;
   805  				}
   806  
   807  				clsName = $.unique(clsName);
   808  				html.push('<td class="'+clsName.join(' ')+'"' + (tooltip ? ' title="'+tooltip+'"' : '') + '>'+prevMonth.getUTCDate() + '</td>');
   809  				if (prevMonth.getUTCDay() === this.o.weekEnd){
   810  					html.push('</tr>');
   811  				}
   812  				prevMonth.setUTCDate(prevMonth.getUTCDate()+1);
   813  			}
   814  			this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
   815  
   816  			var months = this.picker.find('.datepicker-months')
   817  						.find('th:eq(1)')
   818  							.text(year)
   819  							.end()
   820  						.find('span').removeClass('active');
   821  
   822  			$.each(this.dates, function(i, d){
   823  				if (d.getUTCFullYear() === year)
   824  					months.eq(d.getUTCMonth()).addClass('active');
   825  			});
   826  
   827  			if (year < startYear || year > endYear){
   828  				months.addClass('disabled');
   829  			}
   830  			if (year === startYear){
   831  				months.slice(0, startMonth).addClass('disabled');
   832  			}
   833  			if (year === endYear){
   834  				months.slice(endMonth+1).addClass('disabled');
   835  			}
   836  
   837  			html = '';
   838  			year = parseInt(year/10, 10) * 10;
   839  			var yearCont = this.picker.find('.datepicker-years')
   840  								.find('th:eq(1)')
   841  									.text(year + '-' + (year + 9))
   842  									.end()
   843  								.find('td');
   844  			year -= 1;
   845  			var years = $.map(this.dates, function(d){
   846  					return d.getUTCFullYear();
   847  				}),
   848  				classes;
   849  			for (var i = -1; i < 11; i++){
   850  				classes = ['year'];
   851  				if (i === -1)
   852  					classes.push('old');
   853  				else if (i === 10)
   854  					classes.push('new');
   855  				if ($.inArray(year, years) !== -1)
   856  					classes.push('active');
   857  				if (year < startYear || year > endYear)
   858  					classes.push('disabled');
   859  				html += '<span class="' + classes.join(' ') + '">'+year+'</span>';
   860  				year += 1;
   861  			}
   862  			yearCont.html(html);
   863  		},
   864  
   865  		updateNavArrows: function(){
   866  			if (!this._allow_update)
   867  				return;
   868  
   869  			var d = new Date(this.viewDate),
   870  				year = d.getUTCFullYear(),
   871  				month = d.getUTCMonth();
   872  			switch (this.viewMode){
   873  				case 0:
   874  					if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()){
   875  						this.picker.find('.prev').css({visibility: 'hidden'});
   876  					}
   877  					else {
   878  						this.picker.find('.prev').css({visibility: 'visible'});
   879  					}
   880  					if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()){
   881  						this.picker.find('.next').css({visibility: 'hidden'});
   882  					}
   883  					else {
   884  						this.picker.find('.next').css({visibility: 'visible'});
   885  					}
   886  					break;
   887  				case 1:
   888  				case 2:
   889  					if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear()){
   890  						this.picker.find('.prev').css({visibility: 'hidden'});
   891  					}
   892  					else {
   893  						this.picker.find('.prev').css({visibility: 'visible'});
   894  					}
   895  					if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear()){
   896  						this.picker.find('.next').css({visibility: 'hidden'});
   897  					}
   898  					else {
   899  						this.picker.find('.next').css({visibility: 'visible'});
   900  					}
   901  					break;
   902  			}
   903  		},
   904  
   905  		click: function(e){
   906  			e.preventDefault();
   907  			var target = $(e.target).closest('span, td, th'),
   908  				year, month, day;
   909  			if (target.length === 1){
   910  				switch (target[0].nodeName.toLowerCase()){
   911  					case 'th':
   912  						switch (target[0].className){
   913  							case 'datepicker-switch':
   914  								this.showMode(1);
   915  								break;
   916  							case 'prev':
   917  							case 'next':
   918  								var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1);
   919  								switch (this.viewMode){
   920  									case 0:
   921  										this.viewDate = this.moveMonth(this.viewDate, dir);
   922  										this._trigger('changeMonth', this.viewDate);
   923  										break;
   924  									case 1:
   925  									case 2:
   926  										this.viewDate = this.moveYear(this.viewDate, dir);
   927  										if (this.viewMode === 1)
   928  											this._trigger('changeYear', this.viewDate);
   929  										break;
   930  								}
   931  								this.fill();
   932  								break;
   933  							case 'today':
   934  								var date = new Date();
   935  								date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
   936  
   937  								this.showMode(-2);
   938  								var which = this.o.todayBtn === 'linked' ? null : 'view';
   939  								this._setDate(date, which);
   940  								break;
   941  							case 'clear':
   942  								var element;
   943  								if (this.isInput)
   944  									element = this.element;
   945  								else if (this.component)
   946  									element = this.element.find('input');
   947  								if (element)
   948  									element.val("").change();
   949  								this.update();
   950  								this._trigger('changeDate');
   951  								if (this.o.autoclose)
   952  									this.hide();
   953  								break;
   954  						}
   955  						break;
   956  					case 'span':
   957  						if (!target.is('.disabled')){
   958  							this.viewDate.setUTCDate(1);
   959  							if (target.is('.month')){
   960  								day = 1;
   961  								month = target.parent().find('span').index(target);
   962  								year = this.viewDate.getUTCFullYear();
   963  								this.viewDate.setUTCMonth(month);
   964  								this._trigger('changeMonth', this.viewDate);
   965  								if (this.o.minViewMode === 1){
   966  									this._setDate(UTCDate(year, month, day));
   967  								}
   968  							}
   969  							else {
   970  								day = 1;
   971  								month = 0;
   972  								year = parseInt(target.text(), 10)||0;
   973  								this.viewDate.setUTCFullYear(year);
   974  								this._trigger('changeYear', this.viewDate);
   975  								if (this.o.minViewMode === 2){
   976  									this._setDate(UTCDate(year, month, day));
   977  								}
   978  							}
   979  							this.showMode(-1);
   980  							this.fill();
   981  						}
   982  						break;
   983  					case 'td':
   984  						if (target.is('.day') && !target.is('.disabled')){
   985  							day = parseInt(target.text(), 10)||1;
   986  							year = this.viewDate.getUTCFullYear();
   987  							month = this.viewDate.getUTCMonth();
   988  							if (target.is('.old')){
   989  								if (month === 0){
   990  									month = 11;
   991  									year -= 1;
   992  								}
   993  								else {
   994  									month -= 1;
   995  								}
   996  							}
   997  							else if (target.is('.new')){
   998  								if (month === 11){
   999  									month = 0;
  1000  									year += 1;
  1001  								}
  1002  								else {
  1003  									month += 1;
  1004  								}
  1005  							}
  1006  							this._setDate(UTCDate(year, month, day));
  1007  						}
  1008  						break;
  1009  				}
  1010  			}
  1011  			if (this.picker.is(':visible') && this._focused_from){
  1012  				$(this._focused_from).focus();
  1013  			}
  1014  			delete this._focused_from;
  1015  		},
  1016  
  1017  		_toggle_multidate: function(date){
  1018  			var ix = this.dates.contains(date);
  1019  			if (!date){
  1020  				this.dates.clear();
  1021  			}
  1022  			else if (ix !== -1){
  1023  				this.dates.remove(ix);
  1024  			}
  1025  			else {
  1026  				this.dates.push(date);
  1027  			}
  1028  			if (typeof this.o.multidate === 'number')
  1029  				while (this.dates.length > this.o.multidate)
  1030  					this.dates.remove(0);
  1031  		},
  1032  
  1033  		_setDate: function(date, which){
  1034  			if (!which || which === 'date')
  1035  				this._toggle_multidate(date && new Date(date));
  1036  			if (!which || which  === 'view')
  1037  				this.viewDate = date && new Date(date);
  1038  
  1039  			this.fill();
  1040  			this.setValue();
  1041  			this._trigger('changeDate');
  1042  			var element;
  1043  			if (this.isInput){
  1044  				element = this.element;
  1045  			}
  1046  			else if (this.component){
  1047  				element = this.element.find('input');
  1048  			}
  1049  			if (element){
  1050  				element.change();
  1051  			}
  1052  			if (this.o.autoclose && (!which || which === 'date')){
  1053  				this.hide();
  1054  			}
  1055  		},
  1056  
  1057  		moveMonth: function(date, dir){
  1058  			if (!date)
  1059  				return undefined;
  1060  			if (!dir)
  1061  				return date;
  1062  			var new_date = new Date(date.valueOf()),
  1063  				day = new_date.getUTCDate(),
  1064  				month = new_date.getUTCMonth(),
  1065  				mag = Math.abs(dir),
  1066  				new_month, test;
  1067  			dir = dir > 0 ? 1 : -1;
  1068  			if (mag === 1){
  1069  				test = dir === -1
  1070  					// If going back one month, make sure month is not current month
  1071  					// (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
  1072  					? function(){
  1073  						return new_date.getUTCMonth() === month;
  1074  					}
  1075  					// If going forward one month, make sure month is as expected
  1076  					// (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
  1077  					: function(){
  1078  						return new_date.getUTCMonth() !== new_month;
  1079  					};
  1080  				new_month = month + dir;
  1081  				new_date.setUTCMonth(new_month);
  1082  				// Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
  1083  				if (new_month < 0 || new_month > 11)
  1084  					new_month = (new_month + 12) % 12;
  1085  			}
  1086  			else {
  1087  				// For magnitudes >1, move one month at a time...
  1088  				for (var i=0; i < mag; i++)
  1089  					// ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
  1090  					new_date = this.moveMonth(new_date, dir);
  1091  				// ...then reset the day, keeping it in the new month
  1092  				new_month = new_date.getUTCMonth();
  1093  				new_date.setUTCDate(day);
  1094  				test = function(){
  1095  					return new_month !== new_date.getUTCMonth();
  1096  				};
  1097  			}
  1098  			// Common date-resetting loop -- if date is beyond end of month, make it
  1099  			// end of month
  1100  			while (test()){
  1101  				new_date.setUTCDate(--day);
  1102  				new_date.setUTCMonth(new_month);
  1103  			}
  1104  			return new_date;
  1105  		},
  1106  
  1107  		moveYear: function(date, dir){
  1108  			return this.moveMonth(date, dir*12);
  1109  		},
  1110  
  1111  		dateWithinRange: function(date){
  1112  			return date >= this.o.startDate && date <= this.o.endDate;
  1113  		},
  1114  
  1115  		keydown: function(e){
  1116  			if (this.picker.is(':not(:visible)')){
  1117  				if (e.keyCode === 27) // allow escape to hide and re-show picker
  1118  					this.show();
  1119  				return;
  1120  			}
  1121  			var dateChanged = false,
  1122  				dir, newDate, newViewDate,
  1123  				focusDate = this.focusDate || this.viewDate;
  1124  			switch (e.keyCode){
  1125  				case 27: // escape
  1126  					if (this.focusDate){
  1127  						this.focusDate = null;
  1128  						this.viewDate = this.dates.get(-1) || this.viewDate;
  1129  						this.fill();
  1130  					}
  1131  					else
  1132  						this.hide();
  1133  					e.preventDefault();
  1134  					break;
  1135  				case 37: // left
  1136  				case 39: // right
  1137  					if (!this.o.keyboardNavigation)
  1138  						break;
  1139  					dir = e.keyCode === 37 ? -1 : 1;
  1140  					if (e.ctrlKey){
  1141  						newDate = this.moveYear(this.dates.get(-1) || UTCToday(), dir);
  1142  						newViewDate = this.moveYear(focusDate, dir);
  1143  						this._trigger('changeYear', this.viewDate);
  1144  					}
  1145  					else if (e.shiftKey){
  1146  						newDate = this.moveMonth(this.dates.get(-1) || UTCToday(), dir);
  1147  						newViewDate = this.moveMonth(focusDate, dir);
  1148  						this._trigger('changeMonth', this.viewDate);
  1149  					}
  1150  					else {
  1151  						newDate = new Date(this.dates.get(-1) || UTCToday());
  1152  						newDate.setUTCDate(newDate.getUTCDate() + dir);
  1153  						newViewDate = new Date(focusDate);
  1154  						newViewDate.setUTCDate(focusDate.getUTCDate() + dir);
  1155  					}
  1156  					if (this.dateWithinRange(newDate)){
  1157  						this.focusDate = this.viewDate = newViewDate;
  1158  						this.setValue();
  1159  						this.fill();
  1160  						e.preventDefault();
  1161  					}
  1162  					break;
  1163  				case 38: // up
  1164  				case 40: // down
  1165  					if (!this.o.keyboardNavigation)
  1166  						break;
  1167  					dir = e.keyCode === 38 ? -1 : 1;
  1168  					if (e.ctrlKey){
  1169  						newDate = this.moveYear(this.dates.get(-1) || UTCToday(), dir);
  1170  						newViewDate = this.moveYear(focusDate, dir);
  1171  						this._trigger('changeYear', this.viewDate);
  1172  					}
  1173  					else if (e.shiftKey){
  1174  						newDate = this.moveMonth(this.dates.get(-1) || UTCToday(), dir);
  1175  						newViewDate = this.moveMonth(focusDate, dir);
  1176  						this._trigger('changeMonth', this.viewDate);
  1177  					}
  1178  					else {
  1179  						newDate = new Date(this.dates.get(-1) || UTCToday());
  1180  						newDate.setUTCDate(newDate.getUTCDate() + dir * 7);
  1181  						newViewDate = new Date(focusDate);
  1182  						newViewDate.setUTCDate(focusDate.getUTCDate() + dir * 7);
  1183  					}
  1184  					if (this.dateWithinRange(newDate)){
  1185  						this.focusDate = this.viewDate = newViewDate;
  1186  						this.setValue();
  1187  						this.fill();
  1188  						e.preventDefault();
  1189  					}
  1190  					break;
  1191  				case 32: // spacebar
  1192  					// Spacebar is used in manually typing dates in some formats.
  1193  					// As such, its behavior should not be hijacked.
  1194  					break;
  1195  				case 13: // enter
  1196  					focusDate = this.focusDate || this.dates.get(-1) || this.viewDate;
  1197  					this._toggle_multidate(focusDate);
  1198  					dateChanged = true;
  1199  					this.focusDate = null;
  1200  					this.viewDate = this.dates.get(-1) || this.viewDate;
  1201  					this.setValue();
  1202  					this.fill();
  1203  					if (this.picker.is(':visible')){
  1204  						e.preventDefault();
  1205  						if (this.o.autoclose)
  1206  							this.hide();
  1207  					}
  1208  					break;
  1209  				case 9: // tab
  1210  					this.focusDate = null;
  1211  					this.viewDate = this.dates.get(-1) || this.viewDate;
  1212  					this.fill();
  1213  					this.hide();
  1214  					break;
  1215  			}
  1216  			if (dateChanged){
  1217  				if (this.dates.length)
  1218  					this._trigger('changeDate');
  1219  				else
  1220  					this._trigger('clearDate');
  1221  				var element;
  1222  				if (this.isInput){
  1223  					element = this.element;
  1224  				}
  1225  				else if (this.component){
  1226  					element = this.element.find('input');
  1227  				}
  1228  				if (element){
  1229  					element.change();
  1230  				}
  1231  			}
  1232  		},
  1233  
  1234  		showMode: function(dir){
  1235  			if (dir){
  1236  				this.viewMode = Math.max(this.o.minViewMode, Math.min(2, this.viewMode + dir));
  1237  			}
  1238  			this.picker
  1239  				.find('>div')
  1240  				.hide()
  1241  				.filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName)
  1242  					.css('display', 'block');
  1243  			this.updateNavArrows();
  1244  		}
  1245  	};
  1246  
  1247  	var DateRangePicker = function(element, options){
  1248  		this.element = $(element);
  1249  		this.inputs = $.map(options.inputs, function(i){
  1250  			return i.jquery ? i[0] : i;
  1251  		});
  1252  		delete options.inputs;
  1253  
  1254  		$(this.inputs)
  1255  			.datepicker(options)
  1256  			.bind('changeDate', $.proxy(this.dateUpdated, this));
  1257  
  1258  		this.pickers = $.map(this.inputs, function(i){
  1259  			return $(i).data('datepicker');
  1260  		});
  1261  		this.updateDates();
  1262  	};
  1263  	DateRangePicker.prototype = {
  1264  		updateDates: function(){
  1265  			this.dates = $.map(this.pickers, function(i){
  1266  				return i.getUTCDate();
  1267  			});
  1268  			this.updateRanges();
  1269  		},
  1270  		updateRanges: function(){
  1271  			var range = $.map(this.dates, function(d){
  1272  				return d.valueOf();
  1273  			});
  1274  			$.each(this.pickers, function(i, p){
  1275  				p.setRange(range);
  1276  			});
  1277  		},
  1278  		dateUpdated: function(e){
  1279  			// `this.updating` is a workaround for preventing infinite recursion
  1280  			// between `changeDate` triggering and `setUTCDate` calling.  Until
  1281  			// there is a better mechanism.
  1282  			if (this.updating)
  1283  				return;
  1284  			this.updating = true;
  1285  
  1286  			var dp = $(e.target).data('datepicker'),
  1287  				new_date = dp.getUTCDate(),
  1288  				i = $.inArray(e.target, this.inputs),
  1289  				l = this.inputs.length;
  1290  			if (i === -1)
  1291  				return;
  1292  
  1293  			$.each(this.pickers, function(i, p){
  1294  				if (!p.getUTCDate())
  1295  					p.setUTCDate(new_date);
  1296  			});
  1297  
  1298  			if (new_date < this.dates[i]){
  1299  				// Date being moved earlier/left
  1300  				while (i >= 0 && new_date < this.dates[i]){
  1301  					this.pickers[i--].setUTCDate(new_date);
  1302  				}
  1303  			}
  1304  			else if (new_date > this.dates[i]){
  1305  				// Date being moved later/right
  1306  				while (i < l && new_date > this.dates[i]){
  1307  					this.pickers[i++].setUTCDate(new_date);
  1308  				}
  1309  			}
  1310  			this.updateDates();
  1311  
  1312  			delete this.updating;
  1313  		},
  1314  		remove: function(){
  1315  			$.map(this.pickers, function(p){ p.remove(); });
  1316  			delete this.element.data().datepicker;
  1317  		}
  1318  	};
  1319  
  1320  	function opts_from_el(el, prefix){
  1321  		// Derive options from element data-attrs
  1322  		var data = $(el).data(),
  1323  			out = {}, inkey,
  1324  			replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])');
  1325  		prefix = new RegExp('^' + prefix.toLowerCase());
  1326  		function re_lower(_,a){
  1327  			return a.toLowerCase();
  1328  		}
  1329  		for (var key in data)
  1330  			if (prefix.test(key)){
  1331  				inkey = key.replace(replace, re_lower);
  1332  				out[inkey] = data[key];
  1333  			}
  1334  		return out;
  1335  	}
  1336  
  1337  	function opts_from_locale(lang){
  1338  		// Derive options from locale plugins
  1339  		var out = {};
  1340  		// Check if "de-DE" style date is available, if not language should
  1341  		// fallback to 2 letter code eg "de"
  1342  		if (!dates[lang]){
  1343  			lang = lang.split('-')[0];
  1344  			if (!dates[lang])
  1345  				return;
  1346  		}
  1347  		var d = dates[lang];
  1348  		$.each(locale_opts, function(i,k){
  1349  			if (k in d)
  1350  				out[k] = d[k];
  1351  		});
  1352  		return out;
  1353  	}
  1354  
  1355  	var old = $.fn.datepicker;
  1356  	$.fn.datepicker = function(option){
  1357  		var args = Array.apply(null, arguments);
  1358  		args.shift();
  1359  		var internal_return;
  1360  		this.each(function(){
  1361  			var $this = $(this),
  1362  				data = $this.data('datepicker'),
  1363  				options = typeof option === 'object' && option;
  1364  			if (!data){
  1365  				var elopts = opts_from_el(this, 'date'),
  1366  					// Preliminary otions
  1367  					xopts = $.extend({}, defaults, elopts, options),
  1368  					locopts = opts_from_locale(xopts.language),
  1369  					// Options priority: js args, data-attrs, locales, defaults
  1370  					opts = $.extend({}, defaults, locopts, elopts, options);
  1371  				if ($this.is('.input-daterange') || opts.inputs){
  1372  					var ropts = {
  1373  						inputs: opts.inputs || $this.find('input').toArray()
  1374  					};
  1375  					$this.data('datepicker', (data = new DateRangePicker(this, $.extend(opts, ropts))));
  1376  				}
  1377  				else {
  1378  					$this.data('datepicker', (data = new Datepicker(this, opts)));
  1379  				}
  1380  			}
  1381  			if (typeof option === 'string' && typeof data[option] === 'function'){
  1382  				internal_return = data[option].apply(data, args);
  1383  				if (internal_return !== undefined)
  1384  					return false;
  1385  			}
  1386  		});
  1387  		if (internal_return !== undefined)
  1388  			return internal_return;
  1389  		else
  1390  			return this;
  1391  	};
  1392  
  1393  	var defaults = $.fn.datepicker.defaults = {
  1394  		autoclose: false,
  1395  		beforeShowDay: $.noop,
  1396  		calendarWeeks: false,
  1397  		clearBtn: false,
  1398  		daysOfWeekDisabled: [],
  1399  		endDate: Infinity,
  1400  		forceParse: true,
  1401  		format: 'mm/dd/yyyy',
  1402  		keyboardNavigation: true,
  1403  		language: 'en',
  1404  		minViewMode: 0,
  1405  		multidate: false,
  1406  		multidateSeparator: ',',
  1407  		orientation: "auto",
  1408  		rtl: false,
  1409  		startDate: -Infinity,
  1410  		startView: 0,
  1411  		todayBtn: false,
  1412  		todayHighlight: false,
  1413  		weekStart: 0
  1414  	};
  1415  	var locale_opts = $.fn.datepicker.locale_opts = [
  1416  		'format',
  1417  		'rtl',
  1418  		'weekStart'
  1419  	];
  1420  	$.fn.datepicker.Constructor = Datepicker;
  1421  	var dates = $.fn.datepicker.dates = {
  1422  		en: {
  1423  			days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
  1424  			daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
  1425  			daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
  1426  			months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
  1427  			monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
  1428  			today: "Today",
  1429  			clear: "Clear"
  1430  		}
  1431  	};
  1432  
  1433  	var DPGlobal = {
  1434  		modes: [
  1435  			{
  1436  				clsName: 'days',
  1437  				navFnc: 'Month',
  1438  				navStep: 1
  1439  			},
  1440  			{
  1441  				clsName: 'months',
  1442  				navFnc: 'FullYear',
  1443  				navStep: 1
  1444  			},
  1445  			{
  1446  				clsName: 'years',
  1447  				navFnc: 'FullYear',
  1448  				navStep: 10
  1449  		}],
  1450  		isLeapYear: function(year){
  1451  			return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0));
  1452  		},
  1453  		getDaysInMonth: function(year, month){
  1454  			return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
  1455  		},
  1456  		validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g,
  1457  		nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g,
  1458  		parseFormat: function(format){
  1459  			// IE treats \0 as a string end in inputs (truncating the value),
  1460  			// so it's a bad format delimiter, anyway
  1461  			var separators = format.replace(this.validParts, '\0').split('\0'),
  1462  				parts = format.match(this.validParts);
  1463  			if (!separators || !separators.length || !parts || parts.length === 0){
  1464  				throw new Error("Invalid date format.");
  1465  			}
  1466  			return {separators: separators, parts: parts};
  1467  		},
  1468  		parseDate: function(date, format, language){
  1469  			if (!date)
  1470  				return undefined;
  1471  			if (date instanceof Date)
  1472  				return date;
  1473  			if (typeof format === 'string')
  1474  				format = DPGlobal.parseFormat(format);
  1475  			var part_re = /([\-+]\d+)([dmwy])/,
  1476  				parts = date.match(/([\-+]\d+)([dmwy])/g),
  1477  				part, dir, i;
  1478  			if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)){
  1479  				date = new Date();
  1480  				for (i=0; i < parts.length; i++){
  1481  					part = part_re.exec(parts[i]);
  1482  					dir = parseInt(part[1]);
  1483  					switch (part[2]){
  1484  						case 'd':
  1485  							date.setUTCDate(date.getUTCDate() + dir);
  1486  							break;
  1487  						case 'm':
  1488  							date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir);
  1489  							break;
  1490  						case 'w':
  1491  							date.setUTCDate(date.getUTCDate() + dir * 7);
  1492  							break;
  1493  						case 'y':
  1494  							date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir);
  1495  							break;
  1496  					}
  1497  				}
  1498  				return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0);
  1499  			}
  1500  			parts = date && date.match(this.nonpunctuation) || [];
  1501  			date = new Date();
  1502  			var parsed = {},
  1503  				setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'],
  1504  				setters_map = {
  1505  					yyyy: function(d,v){
  1506  						return d.setUTCFullYear(v);
  1507  					},
  1508  					yy: function(d,v){
  1509  						return d.setUTCFullYear(2000+v);
  1510  					},
  1511  					m: function(d,v){
  1512  						if (isNaN(d))
  1513  							return d;
  1514  						v -= 1;
  1515  						while (v < 0) v += 12;
  1516  						v %= 12;
  1517  						d.setUTCMonth(v);
  1518  						while (d.getUTCMonth() !== v)
  1519  							d.setUTCDate(d.getUTCDate()-1);
  1520  						return d;
  1521  					},
  1522  					d: function(d,v){
  1523  						return d.setUTCDate(v);
  1524  					}
  1525  				},
  1526  				val, filtered;
  1527  			setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
  1528  			setters_map['dd'] = setters_map['d'];
  1529  			date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
  1530  			var fparts = format.parts.slice();
  1531  			// Remove noop parts
  1532  			if (parts.length !== fparts.length){
  1533  				fparts = $(fparts).filter(function(i,p){
  1534  					return $.inArray(p, setters_order) !== -1;
  1535  				}).toArray();
  1536  			}
  1537  			// Process remainder
  1538  			function match_part(){
  1539  				var m = this.slice(0, parts[i].length),
  1540  					p = parts[i].slice(0, m.length);
  1541  				return m === p;
  1542  			}
  1543  			if (parts.length === fparts.length){
  1544  				var cnt;
  1545  				for (i=0, cnt = fparts.length; i < cnt; i++){
  1546  					val = parseInt(parts[i], 10);
  1547  					part = fparts[i];
  1548  					if (isNaN(val)){
  1549  						switch (part){
  1550  							case 'MM':
  1551  								filtered = $(dates[language].months).filter(match_part);
  1552  								val = $.inArray(filtered[0], dates[language].months) + 1;
  1553  								break;
  1554  							case 'M':
  1555  								filtered = $(dates[language].monthsShort).filter(match_part);
  1556  								val = $.inArray(filtered[0], dates[language].monthsShort) + 1;
  1557  								break;
  1558  						}
  1559  					}
  1560  					parsed[part] = val;
  1561  				}
  1562  				var _date, s;
  1563  				for (i=0; i < setters_order.length; i++){
  1564  					s = setters_order[i];
  1565  					if (s in parsed && !isNaN(parsed[s])){
  1566  						_date = new Date(date);
  1567  						setters_map[s](_date, parsed[s]);
  1568  						if (!isNaN(_date))
  1569  							date = _date;
  1570  					}
  1571  				}
  1572  			}
  1573  			return date;
  1574  		},
  1575  		formatDate: function(date, format, language){
  1576  			if (!date)
  1577  				return '';
  1578  			if (typeof format === 'string')
  1579  				format = DPGlobal.parseFormat(format);
  1580  			var val = {
  1581  				d: date.getUTCDate(),
  1582  				D: dates[language].daysShort[date.getUTCDay()],
  1583  				DD: dates[language].days[date.getUTCDay()],
  1584  				m: date.getUTCMonth() + 1,
  1585  				M: dates[language].monthsShort[date.getUTCMonth()],
  1586  				MM: dates[language].months[date.getUTCMonth()],
  1587  				yy: date.getUTCFullYear().toString().substring(2),
  1588  				yyyy: date.getUTCFullYear()
  1589  			};
  1590  			val.dd = (val.d < 10 ? '0' : '') + val.d;
  1591  			val.mm = (val.m < 10 ? '0' : '') + val.m;
  1592  			date = [];
  1593  			var seps = $.extend([], format.separators);
  1594  			for (var i=0, cnt = format.parts.length; i <= cnt; i++){
  1595  				if (seps.length)
  1596  					date.push(seps.shift());
  1597  				date.push(val[format.parts[i]]);
  1598  			}
  1599  			return date.join('');
  1600  		},
  1601  		headTemplate: '<thead>'+
  1602  							'<tr>'+
  1603  								'<th class="prev"><i class="fa fa-angle-left"></i></th>'+
  1604  								'<th colspan="5" class="datepicker-switch"></th>'+
  1605  								'<th class="next"><i class="fa fa-angle-right"></i></th>'+
  1606  							'</tr>'+
  1607  						'</thead>',
  1608  		contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',
  1609  		footTemplate: '<tfoot>'+
  1610  							'<tr>'+
  1611  								'<th colspan="7" class="today"></th>'+
  1612  							'</tr>'+
  1613  							'<tr>'+
  1614  								'<th colspan="7" class="clear"></th>'+
  1615  							'</tr>'+
  1616  						'</tfoot>'
  1617  	};
  1618  	DPGlobal.template = '<div class="datepicker">'+
  1619  							'<div class="datepicker-days">'+
  1620  								'<table class=" table-condensed">'+
  1621  									DPGlobal.headTemplate+
  1622  									'<tbody></tbody>'+
  1623  									DPGlobal.footTemplate+
  1624  								'</table>'+
  1625  							'</div>'+
  1626  							'<div class="datepicker-months">'+
  1627  								'<table class="table-condensed">'+
  1628  									DPGlobal.headTemplate+
  1629  									DPGlobal.contTemplate+
  1630  									DPGlobal.footTemplate+
  1631  								'</table>'+
  1632  							'</div>'+
  1633  							'<div class="datepicker-years">'+
  1634  								'<table class="table-condensed">'+
  1635  									DPGlobal.headTemplate+
  1636  									DPGlobal.contTemplate+
  1637  									DPGlobal.footTemplate+
  1638  								'</table>'+
  1639  							'</div>'+
  1640  						'</div>';
  1641  
  1642  	$.fn.datepicker.DPGlobal = DPGlobal;
  1643  
  1644  
  1645  	/* DATEPICKER NO CONFLICT
  1646  	* =================== */
  1647  
  1648  	$.fn.datepicker.noConflict = function(){
  1649  		$.fn.datepicker = old;
  1650  		return this;
  1651  	};
  1652  
  1653  
  1654  	/* DATEPICKER DATA-API
  1655  	* ================== */
  1656  
  1657  	$(document).on(
  1658  		'focus.datepicker.data-api click.datepicker.data-api',
  1659  		'[data-provide="datepicker"]',
  1660  		function(e){
  1661  			var $this = $(this);
  1662  			if ($this.data('datepicker'))
  1663  				return;
  1664  			e.preventDefault();
  1665  			// component click requires us to explicitly show it
  1666  			$this.datepicker('show');
  1667  		}
  1668  	);
  1669  	$(function(){
  1670  		$('[data-provide="datepicker-inline"]').datepicker();
  1671  	});
  1672  
  1673  }(window.jQuery));