text: export default function(window, $, undefined) {
  	(function() {
  		var VERSION = '2.2.3',
  			pluginName = 'datepicker',
  			autoInitSelector = '.datepicker-here',
  			$body,
  			$datepickersContainer,
  			containerBuilt = false,
  			baseTemplate =
  				'' +
  				'<div class="datepicker">' +
  				'<i class="datepicker--pointer"></i>' +
  				'<nav class="datepicker--nav"></nav>' +
  				'<div class="datepicker--content"></div>' +
  				'</div>',
  			defaults = {
  				classes: '',
  				inline: false,
  				language: 'ru',
  				startDate: new Date(),
  				firstDay: '',
  				weekends: [ 6, 0 ],
  				dateFormat: '',
  				altField: '',
  				altFieldDateFormat: '@',
  				toggleSelected: true,
  				keyboardNav: true,
  
  				position: 'bottom left',
  				offset: 12,
  
  				view: 'days',
  				minView: 'days',
  
  				showOtherMonths: true,
  				selectOtherMonths: true,
  				moveToOtherMonthsOnSelect: true,
  
  				showOtherYears: true,
  				selectOtherYears: true,
  				moveToOtherYearsOnSelect: true,
  
  				minDate: '',
  				maxDate: '',
  				disableNavWhenOutOfRange: true,
  
  				multipleDates: false, // Boolean or Number
  				multipleDatesSeparator: ',',
  				range: false,
  
  				todayButton: false,
  				clearButton: false,
  
  				showEvent: 'focus',
  				autoClose: false,
  
  				// navigation
  				monthsField: 'monthsShort',
  				prevHtml: '<svg><path d="M 17,12 l -5,5 l 5,5"></path></svg>',
  				nextHtml: '<svg><path d="M 14,12 l 5,5 l -5,5"></path></svg>',
  				navTitles: {
  					days: 'MM, <i>yyyy</i>',
  					months: 'yyyy',
  					years: 'yyyy1 - yyyy2'
  				},
  
  				// timepicker
  				timepicker: false,
  				onlyTimepicker: false,
  				dateTimeSeparator: ' ',
  				timeFormat: '',
  				minHours: 0,
  				maxHours: 24,
  				minMinutes: 0,
  				maxMinutes: 59,
  				hoursStep: 1,
  				minutesStep: 1,
  
  				// events
  				onSelect: '',
  				onShow: '',
  				onHide: '',
  				onChangeMonth: '',
  				onChangeYear: '',
  				onChangeDecade: '',
  				onChangeView: '',
  				onRenderCell: ''
  			},
  			hotKeys = {
  				ctrlRight: [ 17, 39 ],
  				ctrlUp: [ 17, 38 ],
  				ctrlLeft: [ 17, 37 ],
  				ctrlDown: [ 17, 40 ],
  				shiftRight: [ 16, 39 ],
  				shiftUp: [ 16, 38 ],
  				shiftLeft: [ 16, 37 ],
  				shiftDown: [ 16, 40 ],
  				altUp: [ 18, 38 ],
  				altRight: [ 18, 39 ],
  				altLeft: [ 18, 37 ],
  				altDown: [ 18, 40 ],
  				ctrlShiftUp: [ 16, 17, 38 ]
  			},
  			datepicker;
  
  		var Datepicker = function(el, options) {
  			this.el = el;
  			this.$el = $(el);
  
  			this.opts = $.extend(true, {}, defaults, options, this.$el.data());
  
  			if ($body == undefined) {
  				$body = $('body');
  			}
  
  			if (!this.opts.startDate) {
  				this.opts.startDate = new Date();
  			}
  
  			if (this.el.nodeName == 'INPUT') {
  				this.elIsInput = true;
  			}
  
  			if (this.opts.altField) {
  				this.$altField = typeof this.opts.altField == 'string' ? $(this.opts.altField) : this.opts.altField;
  			}
  
  			this.inited = false;
  			this.visible = false;
  			this.silent = false; // Need to prevent unnecessary rendering
  
  			this.currentDate = this.opts.startDate;
  			this.currentView = this.opts.view;
  			this._createShortCuts();
  			this.selectedDates = [];
  			this.views = {};
  			this.keys = [];
  			this.minRange = '';
  			this.maxRange = '';
  			this._prevOnSelectValue = '';
  
  			this.init();
  		};
  
  		datepicker = Datepicker;
  
  		datepicker.prototype = {
  			VERSION: VERSION,
  			viewIndexes: [ 'days', 'months', 'years' ],
  
  			init: function() {
  				if (!containerBuilt && !this.opts.inline && this.elIsInput) {
  					this._buildDatepickersContainer();
  				}
  				this._buildBaseHtml();
  				this._defineLocale(this.opts.language);
  				this._syncWithMinMaxDates();
  
  				if (this.elIsInput) {
  					if (!this.opts.inline) {
  						// Set extra classes for proper transitions
  						this._setPositionClasses(this.opts.position);
  						this._bindEvents();
  					}
  					if (this.opts.keyboardNav && !this.opts.onlyTimepicker) {
  						this._bindKeyboardEvents();
  					}
  					this.$datepicker.on('mousedown', this._onMouseDownDatepicker.bind(this));
  					this.$datepicker.on('mouseup', this._onMouseUpDatepicker.bind(this));
  				}
  
  				if (this.opts.classes) {
  					this.$datepicker.addClass(this.opts.classes);
  				}
  
  				if (this.opts.timepicker) {
  					this.timepicker = new $.fn.datepicker.Timepicker(this, this.opts);
  					this._bindTimepickerEvents();
  				}
  
  				if (this.opts.onlyTimepicker) {
  					this.$datepicker.addClass('-only-timepicker-');
  				}
  
  				this.views[this.currentView] = new $.fn.datepicker.Body(this, this.currentView, this.opts);
  				this.views[this.currentView].show();
  				this.nav = new $.fn.datepicker.Navigation(this, this.opts);
  				this.view = this.currentView;
  
  				this.$el.on('clickCell.adp', this._onClickCell.bind(this));
  				this.$datepicker.on('mouseenter', '.datepicker--cell', this._onMouseEnterCell.bind(this));
  				this.$datepicker.on('mouseleave', '.datepicker--cell', this._onMouseLeaveCell.bind(this));
  
  				this.inited = true;
  			},
  
  			_createShortCuts: function() {
  				this.minDate = this.opts.minDate ? this.opts.minDate : new Date(-8639999913600000);
  				this.maxDate = this.opts.maxDate ? this.opts.maxDate : new Date(8639999913600000);
  			},
  
  			_bindEvents: function() {
  				this.$el.on(this.opts.showEvent + '.adp', this._onShowEvent.bind(this));
  				this.$el.on('mouseup.adp', this._onMouseUpEl.bind(this));
  				this.$el.on('blur.adp', this._onBlur.bind(this));
  				this.$el.on('keyup.adp', this._onKeyUpGeneral.bind(this));
  				$(window).on('resize.adp', this._onResize.bind(this));
  				$('body').on('mouseup.adp', this._onMouseUpBody.bind(this));
  			},
  
  			_bindKeyboardEvents: function() {
  				this.$el.on('keydown.adp', this._onKeyDown.bind(this));
  				this.$el.on('keyup.adp', this._onKeyUp.bind(this));
  				this.$el.on('hotKey.adp', this._onHotKey.bind(this));
  			},
  
  			_bindTimepickerEvents: function() {
  				this.$el.on('timeChange.adp', this._onTimeChange.bind(this));
  			},
  
  			isWeekend: function(day) {
  				return this.opts.weekends.indexOf(day) !== -1;
  			},
  
  			_defineLocale: function(lang) {
  				if (typeof lang == 'string') {
  					this.loc = $.fn.datepicker.language[lang];
  					if (!this.loc) {
  						console.warn(
  							'Can\'t find language "' + lang + '" in Datepicker.language, will use "ru" instead'
  						);
  						this.loc = $.extend(true, {}, $.fn.datepicker.language.ru);
  					}
  
  					this.loc = $.extend(true, {}, $.fn.datepicker.language.ru, $.fn.datepicker.language[lang]);
  				} else {
  					this.loc = $.extend(true, {}, $.fn.datepicker.language.ru, lang);
  				}
  
  				if (this.opts.dateFormat) {
  					this.loc.dateFormat = this.opts.dateFormat;
  				}
  
  				if (this.opts.timeFormat) {
  					this.loc.timeFormat = this.opts.timeFormat;
  				}
  
  				if (this.opts.firstDay !== '') {
  					this.loc.firstDay = this.opts.firstDay;
  				}
  
  				if (this.opts.timepicker) {
  					this.loc.dateFormat = [ this.loc.dateFormat, this.loc.timeFormat ].join(
  						this.opts.dateTimeSeparator
  					);
  				}
  
  				if (this.opts.onlyTimepicker) {
  					this.loc.dateFormat = this.loc.timeFormat;
  				}
  
  				var boundary = this._getWordBoundaryRegExp;
  				if (this.loc.timeFormat.match(boundary('aa')) || this.loc.timeFormat.match(boundary('AA'))) {
  					this.ampm = true;
  				}
  			},
  
  			_buildDatepickersContainer: function() {
  				containerBuilt = true;
  				$body.append('<div class="datepickers-container" id="datepickers-container"></div>');
  				$datepickersContainer = $('#datepickers-container');
  			},
  
  			_buildBaseHtml: function() {
  				var $appendTarget,
  					$inline = $('<div class="datepicker-inline">');
  
  				if (this.el.nodeName == 'INPUT') {
  					if (!this.opts.inline) {
  						$appendTarget = $datepickersContainer;
  					} else {
  						$appendTarget = $inline.insertAfter(this.$el);
  					}
  				} else {
  					$appendTarget = $inline.appendTo(this.$el);
  				}
  
  				this.$datepicker = $(baseTemplate).appendTo($appendTarget);
  				this.$content = $('.datepicker--content', this.$datepicker);
  				this.$nav = $('.datepicker--nav', this.$datepicker);
  			},
  
  			_triggerOnChange: function() {
  				if (!this.selectedDates.length) {
  					// Prevent from triggering multiple onSelect callback with same argument (empty string) in IE10-11
  					if (this._prevOnSelectValue === '') return;
  					this._prevOnSelectValue = '';
  					return this.opts.onSelect('', '', this);
  				}
  
  				var selectedDates = this.selectedDates,
  					parsedSelected = datepicker.getParsedDate(selectedDates[0]),
  					formattedDates,
  					_this = this,
  					dates = new Date(
  						parsedSelected.year,
  						parsedSelected.month,
  						parsedSelected.date,
  						parsedSelected.hours,
  						parsedSelected.minutes
  					);
  
  				formattedDates = selectedDates
  					.map(function(date) {
  						return _this.formatDate(_this.loc.dateFormat, date);
  					})
  					.join(this.opts.multipleDatesSeparator);
  
  				// Create new dates array, to separate it from original selectedDates
  				if (this.opts.multipleDates || this.opts.range) {
  					dates = selectedDates.map(function(date) {
  						var parsedDate = datepicker.getParsedDate(date);
  						return new Date(
  							parsedDate.year,
  							parsedDate.month,
  							parsedDate.date,
  							parsedDate.hours,
  							parsedDate.minutes
  						);
  					});
  				}
  
  				this._prevOnSelectValue = formattedDates;
  				this.opts.onSelect(formattedDates, dates, this);
  			},
  
  			next: function() {
  				var d = this.parsedDate,
  					o = this.opts;
  				switch (this.view) {
  					case 'days':
  						this.date = new Date(d.year, d.month + 1, 1);
  						if (o.onChangeMonth) o.onChangeMonth(this.parsedDate.month, this.parsedDate.year);
  						break;
  					case 'months':
  						this.date = new Date(d.year + 1, d.month, 1);
  						if (o.onChangeYear) o.onChangeYear(this.parsedDate.year);
  						break;
  					case 'years':
  						this.date = new Date(d.year + 10, 0, 1);
  						if (o.onChangeDecade) o.onChangeDecade(this.curDecade);
  						break;
  				}
  			},
  
  			prev: function() {
  				var d = this.parsedDate,
  					o = this.opts;
  				switch (this.view) {
  					case 'days':
  						this.date = new Date(d.year, d.month - 1, 1);
  						if (o.onChangeMonth) o.onChangeMonth(this.parsedDate.month, this.parsedDate.year);
  						break;
  					case 'months':
  						this.date = new Date(d.year - 1, d.month, 1);
  						if (o.onChangeYear) o.onChangeYear(this.parsedDate.year);
  						break;
  					case 'years':
  						this.date = new Date(d.year - 10, 0, 1);
  						if (o.onChangeDecade) o.onChangeDecade(this.curDecade);
  						break;
  				}
  			},
  
  			formatDate: function(string, date) {
  				date = date || this.date;
  				var result = string,
  					boundary = this._getWordBoundaryRegExp,
  					locale = this.loc,
  					leadingZero = datepicker.getLeadingZeroNum,
  					decade = datepicker.getDecade(date),
  					d = datepicker.getParsedDate(date),
  					fullHours = d.fullHours,
  					hours = d.hours,
  					ampm = string.match(boundary('aa')) || string.match(boundary('AA')),
  					dayPeriod = 'am',
  					replacer = this._replacer,
  					validHours;
  
  				if (this.opts.timepicker && this.timepicker && ampm) {
  					validHours = this.timepicker._getValidHoursFromDate(date, ampm);
  					fullHours = leadingZero(validHours.hours);
  					hours = validHours.hours;
  					dayPeriod = validHours.dayPeriod;
  				}
  
  				switch (true) {
  					case /@/.test(result):
  						result = result.replace(/@/, date.getTime());
  					case /aa/.test(result):
  						result = replacer(result, boundary('aa'), dayPeriod);
  					case /AA/.test(result):
  						result = replacer(result, boundary('AA'), dayPeriod.toUpperCase());
  					case /dd/.test(result):
  						result = replacer(result, boundary('dd'), d.fullDate);
  					case /d/.test(result):
  						result = replacer(result, boundary('d'), d.date);
  					case /DD/.test(result):
  						result = replacer(result, boundary('DD'), locale.days[d.day]);
  					case /D/.test(result):
  						result = replacer(result, boundary('D'), locale.daysShort[d.day]);
  					case /mm/.test(result):
  						result = replacer(result, boundary('mm'), d.fullMonth);
  					case /m/.test(result):
  						result = replacer(result, boundary('m'), d.month + 1);
  					case /MM/.test(result):
  						result = replacer(result, boundary('MM'), this.loc.months[d.month]);
  					case /M/.test(result):
  						result = replacer(result, boundary('M'), locale.monthsShort[d.month]);
  					case /ii/.test(result):
  						result = replacer(result, boundary('ii'), d.fullMinutes);
  					case /i/.test(result):
  						result = replacer(result, boundary('i'), d.minutes);
  					case /hh/.test(result):
  						result = replacer(result, boundary('hh'), fullHours);
  					case /h/.test(result):
  						result = replacer(result, boundary('h'), hours);
  					case /yyyy/.test(result):
  						result = replacer(result, boundary('yyyy'), d.year);
  					case /yyyy1/.test(result):
  						result = replacer(result, boundary('yyyy1'), decade[0]);
  					case /yyyy2/.test(result):
  						result = replacer(result, boundary('yyyy2'), decade[1]);
  					case /yy/.test(result):
  						result = replacer(result, boundary('yy'), d.year.toString().slice(-2));
  				}
  
  				return result;
  			},
  
  			_replacer: function(str, reg, data) {
  				return str.replace(reg, function(match, p1, p2, p3) {
  					return p1 + data + p3;
  				});
  			},
  
  			_getWordBoundaryRegExp: function(sign) {
  				var symbols = '\\s|\\.|-|/|\\\\|,|\\$|\\!|\\?|:|;';
  
  				return new RegExp('(^|>|' + symbols + ')(' + sign + ')($|<|' + symbols + ')', 'g');
  			},
  
  			selectDate: function(date) {
  				var _this = this,
  					opts = _this.opts,
  					d = _this.parsedDate,
  					selectedDates = _this.selectedDates,
  					len = selectedDates.length,
  					newDate = '';
  
  				if (Array.isArray(date)) {
  					date.forEach(function(d) {
  						_this.selectDate(d);
  					});
  					return;
  				}
  
  				if (!(date instanceof Date)) return;
  
  				this.lastSelectedDate = date;
  
  				// Set new time values from Date
  				if (this.timepicker) {
  					this.timepicker._setTime(date);
  				}
  
  				// On this step timepicker will set valid values in it's instance
  				_this._trigger('selectDate', date);
  
  				// Set correct time values after timepicker's validation
  				// Prevent from setting hours or minutes which values are lesser then `min` value or
  				// greater then `max` value
  				if (this.timepicker) {
  					date.setHours(this.timepicker.hours);
  					date.setMinutes(this.timepicker.minutes);
  				}
  
  				if (_this.view == 'days') {
  					if (date.getMonth() != d.month && opts.moveToOtherMonthsOnSelect) {
  						newDate = new Date(date.getFullYear(), date.getMonth(), 1);
  					}
  				}
  
  				if (_this.view == 'years') {
  					if (date.getFullYear() != d.year && opts.moveToOtherYearsOnSelect) {
  						newDate = new Date(date.getFullYear(), 0, 1);
  					}
  				}
  
  				if (newDate) {
  					_this.silent = true;
  					_this.date = newDate;
  					_this.silent = false;
  					_this.nav._render();
  				}
  
  				if (opts.multipleDates && !opts.range) {
  					// Set priority to range functionality
  					if (len === opts.multipleDates) return;
  					if (!_this._isSelected(date)) {
  						_this.selectedDates.push(date);
  					}
  				} else if (opts.range) {
  					if (len == 2) {
  						_this.selectedDates = [ date ];
  						_this.minRange = date;
  						_this.maxRange = '';
  					} else if (len == 1) {
  						_this.selectedDates.push(date);
  						if (!_this.maxRange) {
  							_this.maxRange = date;
  						} else {
  							_this.minRange = date;
  						}
  						// Swap dates if they were selected via dp.selectDate() and second date was smaller then first
  						if (datepicker.bigger(_this.maxRange, _this.minRange)) {
  							_this.maxRange = _this.minRange;
  							_this.minRange = date;
  						}
  						_this.selectedDates = [ _this.minRange, _this.maxRange ];
  					} else {
  						_this.selectedDates = [ date ];
  						_this.minRange = date;
  					}
  				} else {
  					_this.selectedDates = [ date ];
  				}
  
  				_this._setInputValue();
  
  				if (opts.onSelect) {
  					_this._triggerOnChange();
  				}
  
  				if (opts.autoClose && !this.timepickerIsActive) {
  					if (!opts.multipleDates && !opts.range) {
  						_this.hide();
  					} else if (opts.range && _this.selectedDates.length == 2) {
  						_this.hide();
  					}
  				}
  
  				_this.views[this.currentView]._render();
  			},
  
  			removeDate: function(date) {
  				var selected = this.selectedDates,
  					_this = this;
  
  				if (!(date instanceof Date)) return;
  
  				return selected.some(function(curDate, i) {
  					if (datepicker.isSame(curDate, date)) {
  						selected.splice(i, 1);
  
  						if (!_this.selectedDates.length) {
  							_this.minRange = '';
  							_this.maxRange = '';
  							_this.lastSelectedDate = '';
  						} else {
  							_this.lastSelectedDate = _this.selectedDates[_this.selectedDates.length - 1];
  						}
  
  						_this.views[_this.currentView]._render();
  						_this._setInputValue();
  
  						if (_this.opts.onSelect) {
  							_this._triggerOnChange();
  						}
  
  						return true;
  					}
  				});
  			},
  
  			today: function() {
  				this.silent = true;
  				this.view = this.opts.minView;
  				this.silent = false;
  				this.date = new Date();
  
  				if (this.opts.todayButton instanceof Date) {
  					this.selectDate(this.opts.todayButton);
  				}
  			},
  
  			clear: function() {
  				this.selectedDates = [];
  				this.minRange = '';
  				this.maxRange = '';
  				this.views[this.currentView]._render();
  				this._setInputValue();
  				if (this.opts.onSelect) {
  					this._triggerOnChange();
  				}
  			},
  
  			/**
           * Updates datepicker options
           * @param {String|Object} param - parameter's name to update. If object then it will extend current options
           * @param {String|Number|Object} [value] - new param value
           */
  			update: function(param, value) {
  				var len = arguments.length,
  					lastSelectedDate = this.lastSelectedDate;
  
  				if (len == 2) {
  					this.opts[param] = value;
  				} else if (len == 1 && typeof param == 'object') {
  					this.opts = $.extend(true, this.opts, param);
  				}
  
  				this._createShortCuts();
  				this._syncWithMinMaxDates();
  				this._defineLocale(this.opts.language);
  				this.nav._addButtonsIfNeed();
  				if (!this.opts.onlyTimepicker) this.nav._render();
  				this.views[this.currentView]._render();
  
  				if (this.elIsInput && !this.opts.inline) {
  					this._setPositionClasses(this.opts.position);
  					if (this.visible) {
  						this.setPosition(this.opts.position);
  					}
  				}
  
  				if (this.opts.classes) {
  					this.$datepicker.addClass(this.opts.classes);
  				}
  
  				if (this.opts.onlyTimepicker) {
  					this.$datepicker.addClass('-only-timepicker-');
  				}
  
  				if (this.opts.timepicker) {
  					if (lastSelectedDate) this.timepicker._handleDate(lastSelectedDate);
  					this.timepicker._updateRanges();
  					this.timepicker._updateCurrentTime();
  					// Change hours and minutes if it's values have been changed through min/max hours/minutes
  					if (lastSelectedDate) {
  						lastSelectedDate.setHours(this.timepicker.hours);
  						lastSelectedDate.setMinutes(this.timepicker.minutes);
  					}
  				}
  
  				this._setInputValue();
  
  				return this;
  			},
  
  			_syncWithMinMaxDates: function() {
  				var curTime = this.date.getTime();
  				this.silent = true;
  				if (this.minTime > curTime) {
  					this.date = this.minDate;
  				}
  
  				if (this.maxTime < curTime) {
  					this.date = this.maxDate;
  				}
  				this.silent = false;
  			},
  
  			_isSelected: function(checkDate, cellType) {
  				var res = false;
  				this.selectedDates.some(function(date) {
  					if (datepicker.isSame(date, checkDate, cellType)) {
  						res = date;
  						return true;
  					}
  				});
  				return res;
  			},
  
  			_setInputValue: function() {
  				var _this = this,
  					opts = _this.opts,
  					format = _this.loc.dateFormat,
  					altFormat = opts.altFieldDateFormat,
  					value = _this.selectedDates.map(function(date) {
  						return _this.formatDate(format, date);
  					}),
  					altValues;
  
  				if (opts.altField && _this.$altField.length) {
  					altValues = this.selectedDates.map(function(date) {
  						return _this.formatDate(altFormat, date);
  					});
  					altValues = altValues.join(this.opts.multipleDatesSeparator);
  					this.$altField.val(altValues);
  				}
  
  				value = value.join(this.opts.multipleDatesSeparator);
  
  				this.$el.val(value);
  			},
  
  			/**
           * Check if date is between minDate and maxDate
           * @param date {object} - date object
           * @param type {string} - cell type
           * @returns {boolean}
           * @private
           */
  			_isInRange: function(date, type) {
  				var time = date.getTime(),
  					d = datepicker.getParsedDate(date),
  					min = datepicker.getParsedDate(this.minDate),
  					max = datepicker.getParsedDate(this.maxDate),
  					dMinTime = new Date(d.year, d.month, min.date).getTime(),
  					dMaxTime = new Date(d.year, d.month, max.date).getTime(),
  					types = {
  						day: time >= this.minTime && time <= this.maxTime,
  						month: dMinTime >= this.minTime && dMaxTime <= this.maxTime,
  						year: d.year >= min.year && d.year <= max.year
  					};
  				return type ? types[type] : types.day;
  			},
  
  			_getDimensions: function($el) {
  				var offset = $el.offset();
  
  				return {
  					width: $el.outerWidth(),
  					height: $el.outerHeight(),
  					left: offset.left,
  					top: offset.top
  				};
  			},
  
  			_getDateFromCell: function(cell) {
  				var curDate = this.parsedDate,
  					year = cell.data('year') || curDate.year,
  					month = cell.data('month') == undefined ? curDate.month : cell.data('month'),
  					date = cell.data('date') || 1;
  
  				return new Date(year, month, date);
  			},
  
  			_setPositionClasses: function(pos) {
  				pos = pos.split(' ');
  				var main = pos[0],
  					sec = pos[1],
  					classes = 'datepicker -' + main + '-' + sec + '- -from-' + main + '-';
  
  				if (this.visible) classes += ' active';
  
  				this.$datepicker.removeAttr('class').addClass(classes);
  			},
  
  			setPosition: function(position) {
  				position = position || this.opts.position;
  
  				var dims = this._getDimensions(this.$el),
  					selfDims = this._getDimensions(this.$datepicker),
  					pos = position.split(' '),
  					top,
  					left,
  					offset = this.opts.offset,
  					main = pos[0],
  					secondary = pos[1];
  
  				switch (main) {
  					case 'top':
  						top = dims.top - selfDims.height - offset;
  						break;
  					case 'right':
  						left = dims.left + dims.width + offset;
  						break;
  					case 'bottom':
  						top = dims.top + dims.height + offset;
  						break;
  					case 'left':
  						left = dims.left - selfDims.width - offset;
  						break;
  				}
  
  				switch (secondary) {
  					case 'top':
  						top = dims.top;
  						break;
  					case 'right':
  						left = dims.left + dims.width - selfDims.width;
  						break;
  					case 'bottom':
  						top = dims.top + dims.height - selfDims.height;
  						break;
  					case 'left':
  						left = dims.left;
  						break;
  					case 'center':
  						if (/left|right/.test(main)) {
  							top = dims.top + dims.height / 2 - selfDims.height / 2;
  						} else {
  							left = dims.left + dims.width / 2 - selfDims.width / 2;
  						}
  				}
  
  				this.$datepicker.css({
  					left: left,
  					top: top
  				});
  			},
  
  			show: function() {
  				var onShow = this.opts.onShow;
  
  				this.setPosition(this.opts.position);
  				this.$datepicker.addClass('active');
  				this.visible = true;
  
  				if (onShow) {
  					this._bindVisionEvents(onShow);
  				}
  			},
  
  			hide: function() {
  				var onHide = this.opts.onHide;
  
  				this.$datepicker.removeClass('active').css({
  					left: '-100000px'
  				});
  
  				this.focused = '';
  				this.keys = [];
  
  				this.inFocus = false;
  				this.visible = false;
  				this.$el.blur();
  
  				if (onHide) {
  					this._bindVisionEvents(onHide);
  				}
  			},
  
  			down: function(date) {
  				this._changeView(date, 'down');
  			},
  
  			up: function(date) {
  				this._changeView(date, 'up');
  			},
  
  			_bindVisionEvents: function(event) {
  				this.$datepicker.off('transitionend.dp');
  				event(this, false);
  				this.$datepicker.one('transitionend.dp', event.bind(this, this, true));
  			},
  
  			_changeView: function(date, dir) {
  				date = date || this.focused || this.date;
  
  				var nextView = dir == 'up' ? this.viewIndex + 1 : this.viewIndex - 1;
  				if (nextView > 2) nextView = 2;
  				if (nextView < 0) nextView = 0;
  
  				this.silent = true;
  				this.date = new Date(date.getFullYear(), date.getMonth(), 1);
  				this.silent = false;
  				this.view = this.viewIndexes[nextView];
  			},
  
  			_handleHotKey: function(key) {
  				var date = datepicker.getParsedDate(this._getFocusedDate()),
  					focusedParsed,
  					o = this.opts,
  					newDate,
  					totalDaysInNextMonth,
  					monthChanged = false,
  					yearChanged = false,
  					decadeChanged = false,
  					y = date.year,
  					m = date.month,
  					d = date.date;
  
  				switch (key) {
  					case 'ctrlRight':
  					case 'ctrlUp':
  						m += 1;
  						monthChanged = true;
  						break;
  					case 'ctrlLeft':
  					case 'ctrlDown':
  						m -= 1;
  						monthChanged = true;
  						break;
  					case 'shiftRight':
  					case 'shiftUp':
  						yearChanged = true;
  						y += 1;
  						break;
  					case 'shiftLeft':
  					case 'shiftDown':
  						yearChanged = true;
  						y -= 1;
  						break;
  					case 'altRight':
  					case 'altUp':
  						decadeChanged = true;
  						y += 10;
  						break;
  					case 'altLeft':
  					case 'altDown':
  						decadeChanged = true;
  						y -= 10;
  						break;
  					case 'ctrlShiftUp':
  						this.up();
  						break;
  				}
  
  				totalDaysInNextMonth = datepicker.getDaysCount(new Date(y, m));
  				newDate = new Date(y, m, d);
  
  				// If next month has less days than current, set date to total days in that month
  				if (totalDaysInNextMonth < d) d = totalDaysInNextMonth;
  
  				// Check if newDate is in valid range
  				if (newDate.getTime() < this.minTime) {
  					newDate = this.minDate;
  				} else if (newDate.getTime() > this.maxTime) {
  					newDate = this.maxDate;
  				}
  
  				this.focused = newDate;
  
  				focusedParsed = datepicker.getParsedDate(newDate);
  				if (monthChanged && o.onChangeMonth) {
  					o.onChangeMonth(focusedParsed.month, focusedParsed.year);
  				}
  				if (yearChanged && o.onChangeYear) {
  					o.onChangeYear(focusedParsed.year);
  				}
  				if (decadeChanged && o.onChangeDecade) {
  					o.onChangeDecade(this.curDecade);
  				}
  			},
  
  			_registerKey: function(key) {
  				var exists = this.keys.some(function(curKey) {
  					return curKey == key;
  				});
  
  				if (!exists) {
  					this.keys.push(key);
  				}
  			},
  
  			_unRegisterKey: function(key) {
  				var index = this.keys.indexOf(key);
  
  				this.keys.splice(index, 1);
  			},
  
  			_isHotKeyPressed: function() {
  				var currentHotKey,
  					found = false,
  					_this = this,
  					pressedKeys = this.keys.sort();
  
  				for (var hotKey in hotKeys) {
  					currentHotKey = hotKeys[hotKey];
  					if (pressedKeys.length != currentHotKey.length) continue;
  
  					if (
  						currentHotKey.every(function(key, i) {
  							return key == pressedKeys[i];
  						})
  					) {
  						_this._trigger('hotKey', hotKey);
  						found = true;
  					}
  				}
  
  				return found;
  			},
  
  			_trigger: function(event, args) {
  				this.$el.trigger(event, args);
  			},
  
  			_focusNextCell: function(keyCode, type) {
  				type = type || this.cellType;
  
  				var date = datepicker.getParsedDate(this._getFocusedDate()),
  					y = date.year,
  					m = date.month,
  					d = date.date;
  
  				if (this._isHotKeyPressed()) {
  					return;
  				}
  
  				switch (keyCode) {
  					case 37: // left
  						type == 'day' ? (d -= 1) : '';
  						type == 'month' ? (m -= 1) : '';
  						type == 'year' ? (y -= 1) : '';
  						break;
  					case 38: // up
  						type == 'day' ? (d -= 7) : '';
  						type == 'month' ? (m -= 3) : '';
  						type == 'year' ? (y -= 4) : '';
  						break;
  					case 39: // right
  						type == 'day' ? (d += 1) : '';
  						type == 'month' ? (m += 1) : '';
  						type == 'year' ? (y += 1) : '';
  						break;
  					case 40: // down
  						type == 'day' ? (d += 7) : '';
  						type == 'month' ? (m += 3) : '';
  						type == 'year' ? (y += 4) : '';
  						break;
  				}
  
  				var nd = new Date(y, m, d);
  				if (nd.getTime() < this.minTime) {
  					nd = this.minDate;
  				} else if (nd.getTime() > this.maxTime) {
  					nd = this.maxDate;
  				}
  
  				this.focused = nd;
  			},
  
  			_getFocusedDate: function() {
  				var focused = this.focused || this.selectedDates[this.selectedDates.length - 1],
  					d = this.parsedDate;
  
  				if (!focused) {
  					switch (this.view) {
  						case 'days':
  							focused = new Date(d.year, d.month, new Date().getDate());
  							break;
  						case 'months':
  							focused = new Date(d.year, d.month, 1);
  							break;
  						case 'years':
  							focused = new Date(d.year, 0, 1);
  							break;
  					}
  				}
  
  				return focused;
  			},
  
  			_getCell: function(date, type) {
  				type = type || this.cellType;
  
  				var d = datepicker.getParsedDate(date),
  					selector = '.datepicker--cell[data-year="' + d.year + '"]',
  					$cell;
  
  				switch (type) {
  					case 'month':
  						selector = '[data-month="' + d.month + '"]';
  						break;
  					case 'day':
  						selector += '[data-month="' + d.month + '"][data-date="' + d.date + '"]';
  						break;
  				}
  				$cell = this.views[this.currentView].$el.find(selector);
  
  				return $cell.length ? $cell : $('');
  			},
  
  			destroy: function() {
  				var _this = this;
  				_this.$el.off('.adp').data('datepicker', '');
  
  				_this.selectedDates = [];
  				_this.focused = '';
  				_this.views = {};
  				_this.keys = [];
  				_this.minRange = '';
  				_this.maxRange = '';
  
  				if (_this.opts.inline || !_this.elIsInput) {
  					_this.$datepicker.closest('.datepicker-inline').remove();
  				} else {
  					_this.$datepicker.remove();
  				}
  			},
  
  			_handleAlreadySelectedDates: function(alreadySelected, selectedDate) {
  				if (this.opts.range) {
  					if (!this.opts.toggleSelected) {
  						// Add possibility to select same date when range is true
  						if (this.selectedDates.length != 2) {
  							this._trigger('clickCell', selectedDate);
  						}
  					} else {
  						this.removeDate(selectedDate);
  					}
  				} else if (this.opts.toggleSelected) {
  					this.removeDate(selectedDate);
  				}
  
  				// Change last selected date to be able to change time when clicking on this cell
  				if (!this.opts.toggleSelected) {
  					this.lastSelectedDate = alreadySelected;
  					if (this.opts.timepicker) {
  						this.timepicker._setTime(alreadySelected);
  						this.timepicker.update();
  					}
  				}
  			},
  
  			_onShowEvent: function(e) {
  				if (!this.visible) {
  					this.show();
  				}
  			},
  
  			_onBlur: function() {
  				if (!this.inFocus && this.visible) {
  					this.hide();
  				}
  			},
  
  			_onMouseDownDatepicker: function(e) {
  				this.inFocus = true;
  			},
  
  			_onMouseUpDatepicker: function(e) {
  				this.inFocus = false;
  				e.originalEvent.inFocus = true;
  				if (!e.originalEvent.timepickerFocus) this.$el.focus();
  			},
  
  			_onKeyUpGeneral: function(e) {
  				var val = this.$el.val();
  
  				if (!val) {
  					this.clear();
  				}
  			},
  
  			_onResize: function() {
  				if (this.visible) {
  					this.setPosition();
  				}
  			},
  
  			_onMouseUpBody: function(e) {
  				if (e.originalEvent.inFocus) return;
  
  				if (this.visible && !this.inFocus) {
  					this.hide();
  				}
  			},
  
  			_onMouseUpEl: function(e) {
  				e.originalEvent.inFocus = true;
  				setTimeout(this._onKeyUpGeneral.bind(this), 4);
  			},
  
  			_onKeyDown: function(e) {
  				var code = e.which;
  				this._registerKey(code);
  
  				// Arrows
  				if (code >= 37 && code <= 40) {
  					e.preventDefault();
  					this._focusNextCell(code);
  				}
  
  				// Enter
  				if (code == 13) {
  					if (this.focused) {
  						if (this._getCell(this.focused).hasClass('-disabled-')) return;
  						if (this.view != this.opts.minView) {
  							this.down();
  						} else {
  							var alreadySelected = this._isSelected(this.focused, this.cellType);
  
  							if (!alreadySelected) {
  								if (this.timepicker) {
  									this.focused.setHours(this.timepicker.hours);
  									this.focused.setMinutes(this.timepicker.minutes);
  								}
  								this.selectDate(this.focused);
  								return;
  							}
  							this._handleAlreadySelectedDates(alreadySelected, this.focused);
  						}
  					}
  				}
  
  				// Esc
  				if (code == 27) {
  					this.hide();
  				}
  			},
  
  			_onKeyUp: function(e) {
  				var code = e.which;
  				this._unRegisterKey(code);
  			},
  
  			_onHotKey: function(e, hotKey) {
  				this._handleHotKey(hotKey);
  			},
  
  			_onMouseEnterCell: function(e) {
  				var $cell = $(e.target).closest('.datepicker--cell'),
  					date = this._getDateFromCell($cell);
  
  				// Prevent from unnecessary rendering and setting new currentDate
  				this.silent = true;
  
  				if (this.focused) {
  					this.focused = '';
  				}
  
  				$cell.addClass('-focus-');
  
  				this.focused = date;
  				this.silent = false;
  
  				if (this.opts.range && this.selectedDates.length == 1) {
  					this.minRange = this.selectedDates[0];
  					this.maxRange = '';
  					if (datepicker.less(this.minRange, this.focused)) {
  						this.maxRange = this.minRange;
  						this.minRange = '';
  					}
  					this.views[this.currentView]._update();
  				}
  			},
  
  			_onMouseLeaveCell: function(e) {
  				var $cell = $(e.target).closest('.datepicker--cell');
  
  				$cell.removeClass('-focus-');
  
  				this.silent = true;
  				this.focused = '';
  				this.silent = false;
  			},
  
  			_onTimeChange: function(e, h, m) {
  				var date = new Date(),
  					selectedDates = this.selectedDates,
  					selected = false;
  
  				if (selectedDates.length) {
  					selected = true;
  					date = this.lastSelectedDate;
  				}
  
  				date.setHours(h);
  				date.setMinutes(m);
  
  				if (!selected && !this._getCell(date).hasClass('-disabled-')) {
  					this.selectDate(date);
  				} else {
  					this._setInputValue();
  					if (this.opts.onSelect) {
  						this._triggerOnChange();
  					}
  				}
  			},
  
  			_onClickCell: function(e, date) {
  				if (this.timepicker) {
  					date.setHours(this.timepicker.hours);
  					date.setMinutes(this.timepicker.minutes);
  				}
  				this.selectDate(date);
  			},
  
  			set focused(val) {
  				if (!val && this.focused) {
  					var $cell = this._getCell(this.focused);
  
  					if ($cell.length) {
  						$cell.removeClass('-focus-');
  					}
  				}
  				this._focused = val;
  				if (this.opts.range && this.selectedDates.length == 1) {
  					this.minRange = this.selectedDates[0];
  					this.maxRange = '';
  					if (datepicker.less(this.minRange, this._focused)) {
  						this.maxRange = this.minRange;
  						this.minRange = '';
  					}
  				}
  				if (this.silent) return;
  				this.date = val;
  			},
  
  			get focused() {
  				return this._focused;
  			},
  
  			get parsedDate() {
  				return datepicker.getParsedDate(this.date);
  			},
  
  			set date(val) {
  				if (!(val instanceof Date)) return;
  
  				this.currentDate = val;
  
  				if (this.inited && !this.silent) {
  					this.views[this.view]._render();
  					this.nav._render();
  					if (this.visible && this.elIsInput) {
  						this.setPosition();
  					}
  				}
  				return val;
  			},
  
  			get date() {
  				return this.currentDate;
  			},
  
  			set view(val) {
  				this.viewIndex = this.viewIndexes.indexOf(val);
  
  				if (this.viewIndex < 0) {
  					return;
  				}
  
  				this.prevView = this.currentView;
  				this.currentView = val;
  
  				if (this.inited) {
  					if (!this.views[val]) {
  						this.views[val] = new $.fn.datepicker.Body(this, val, this.opts);
  					} else {
  						this.views[val]._render();
  					}
  
  					this.views[this.prevView].hide();
  					this.views[val].show();
  					this.nav._render();
  
  					if (this.opts.onChangeView) {
  						this.opts.onChangeView(val);
  					}
  					if (this.elIsInput && this.visible) this.setPosition();
  				}
  
  				return val;
  			},
  
  			get view() {
  				return this.currentView;
  			},
  
  			get cellType() {
  				return this.view.substring(0, this.view.length - 1);
  			},
  
  			get minTime() {
  				var min = datepicker.getParsedDate(this.minDate);
  				return new Date(min.year, min.month, min.date).getTime();
  			},
  
  			get maxTime() {
  				var max = datepicker.getParsedDate(this.maxDate);
  				return new Date(max.year, max.month, max.date).getTime();
  			},
  
  			get curDecade() {
  				return datepicker.getDecade(this.date);
  			}
  		};
  
  		//  Utils
  		// -------------------------------------------------
  
  		datepicker.getDaysCount = function(date) {
  			return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
  		};
  
  		datepicker.getParsedDate = function(date) {
  			return {
  				year: date.getFullYear(),
  				month: date.getMonth(),
  				fullMonth: date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1, // One based
  				date: date.getDate(),
  				fullDate: date.getDate() < 10 ? '0' + date.getDate() : date.getDate(),
  				day: date.getDay(),
  				hours: date.getHours(),
  				fullHours: date.getHours() < 10 ? '0' + date.getHours() : date.getHours(),
  				minutes: date.getMinutes(),
  				fullMinutes: date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
  			};
  		};
  
  		datepicker.getDecade = function(date) {
  			var firstYear = Math.floor(date.getFullYear() / 10) * 10;
  
  			return [ firstYear, firstYear + 9 ];
  		};
  
  		datepicker.template = function(str, data) {
  			return str.replace(/#\{([\w]+)\}/g, function(source, match) {
  				if (data[match] || data[match] === 0) {
  					return data[match];
  				}
  			});
  		};
  
  		datepicker.isSame = function(date1, date2, type) {
  			if (!date1 || !date2) return false;
  			var d1 = datepicker.getParsedDate(date1),
  				d2 = datepicker.getParsedDate(date2),
  				_type = type ? type : 'day',
  				conditions = {
  					day: d1.date == d2.date && d1.month == d2.month && d1.year == d2.year,
  					month: d1.month == d2.month && d1.year == d2.year,
  					year: d1.year == d2.year
  				};
  
  			return conditions[_type];
  		};
  
  		datepicker.less = function(dateCompareTo, date, type) {
  			if (!dateCompareTo || !date) return false;
  			return date.getTime() < dateCompareTo.getTime();
  		};
  
  		datepicker.bigger = function(dateCompareTo, date, type) {
  			if (!dateCompareTo || !date) return false;
  			return date.getTime() > dateCompareTo.getTime();
  		};
  
  		datepicker.getLeadingZeroNum = function(num) {
  			return parseInt(num) < 10 ? '0' + num : num;
  		};
  
  		/**
       * Returns copy of date with hours and minutes equals to 0
       * @param date {Date}
       */
  		datepicker.resetTime = function(date) {
  			if (typeof date != 'object') return;
  			date = datepicker.getParsedDate(date);
  			return new Date(date.year, date.month, date.date);
  		};
  
  		$.fn.datepicker = function(options) {
  			return this.each(function() {
  				if (!$.data(this, pluginName)) {
  					$.data(this, pluginName, new Datepicker(this, options));
  				} else {
  					var _this = $.data(this, pluginName);
  
  					_this.opts = $.extend(true, _this.opts, options);
  					_this.update();
  				}
  			});
  		};
  
  		$.fn.datepicker.Constructor = Datepicker;
  
  		$.fn.datepicker.language = {
  			ru: {
  				days: [ 'Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота' ],
  				daysShort: [ 'Вос', 'Пон', 'Вто', 'Сре', 'Чет', 'Пят', 'Суб' ],
  				daysMin: [ 'Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб' ],
  				months: [
  					'Январь',
  					'Февраль',
  					'Март',
  					'Апрель',
  					'Май',
  					'Июнь',
  					'Июль',
  					'Август',
  					'Сентябрь',
  					'Октябрь',
  					'Ноябрь',
  					'Декабрь'
  				],
  				monthsShort: [ 'Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек' ],
  				today: 'Сегодня',
  				clear: 'Очистить',
  				dateFormat: 'dd.mm.yyyy',
  				timeFormat: 'hh:ii',
  				firstDay: 1
  			}
  		};
  
  		$(function() {
  			$(autoInitSelector).datepicker();
  		});
  	})();
  
  	(function() {
  		var templates = {
  				days:
  					'' +
  					'<div class="datepicker--days datepicker--body">' +
  					'<div class="datepicker--days-names"></div>' +
  					'<div class="datepicker--cells datepicker--cells-days"></div>' +
  					'</div>',
  				months:
  					'' +
  					'<div class="datepicker--months datepicker--body">' +
  					'<div class="datepicker--cells datepicker--cells-months"></div>' +
  					'</div>',
  				years:
  					'' +
  					'<div class="datepicker--years datepicker--body">' +
  					'<div class="datepicker--cells datepicker--cells-years"></div>' +
  					'</div>'
  			},
  			datepicker = $.fn.datepicker,
  			dp = datepicker.Constructor;
  
  		datepicker.Body = function(d, type, opts) {
  			this.d = d;
  			this.type = type;
  			this.opts = opts;
  			this.$el = $('');
  
  			if (this.opts.onlyTimepicker) return;
  			this.init();
  		};
  
  		datepicker.Body.prototype = {
  			init: function() {
  				this._buildBaseHtml();
  				this._render();
  
  				this._bindEvents();
  			},
  
  			_bindEvents: function() {
  				this.$el.on('click', '.datepicker--cell', $.proxy(this._onClickCell, this));
  			},
  
  			_buildBaseHtml: function() {
  				this.$el = $(templates[this.type]).appendTo(this.d.$content);
  				this.$names = $('.datepicker--days-names', this.$el);
  				this.$cells = $('.datepicker--cells', this.$el);
  			},
  
  			_getDayNamesHtml: function(firstDay, curDay, html, i) {
  				curDay = curDay != undefined ? curDay : firstDay;
  				html = html ? html : '';
  				i = i != undefined ? i : 0;
  
  				if (i > 7) return html;
  				if (curDay == 7) return this._getDayNamesHtml(firstDay, 0, html, ++i);
  
  				html +=
  					'<div class="datepicker--day-name' +
  					(this.d.isWeekend(curDay) ? ' -weekend-' : '') +
  					'">' +
  					this.d.loc.daysMin[curDay] +
  					'</div>';
  
  				return this._getDayNamesHtml(firstDay, ++curDay, html, ++i);
  			},
  
  			_getCellContents: function(date, type) {
  				var classes = 'datepicker--cell datepicker--cell-' + type,
  					currentDate = new Date(),
  					parent = this.d,
  					minRange = dp.resetTime(parent.minRange),
  					maxRange = dp.resetTime(parent.maxRange),
  					opts = parent.opts,
  					d = dp.getParsedDate(date),
  					render = {},
  					html = d.date;
  
  				switch (type) {
  					case 'day':
  						if (parent.isWeekend(d.day)) classes += ' -weekend-';
  						if (d.month != this.d.parsedDate.month) {
  							classes += ' -other-month-';
  							if (!opts.selectOtherMonths) {
  								classes += ' -disabled-';
  							}
  							if (!opts.showOtherMonths) html = '';
  						}
  						break;
  					case 'month':
  						html = parent.loc[parent.opts.monthsField][d.month];
  						break;
  					case 'year':
  						var decade = parent.curDecade;
  						html = d.year;
  						if (d.year < decade[0] || d.year > decade[1]) {
  							classes += ' -other-decade-';
  							if (!opts.selectOtherYears) {
  								classes += ' -disabled-';
  							}
  							if (!opts.showOtherYears) html = '';
  						}
  						break;
  				}
  
  				if (opts.onRenderCell) {
  					render = opts.onRenderCell(date, type) || {};
  					html = render.html ? render.html : html;
  					classes += render.classes ? ' ' + render.classes : '';
  				}
  
  				if (opts.range) {
  					if (dp.isSame(minRange, date, type)) classes += ' -range-from-';
  					if (dp.isSame(maxRange, date, type)) classes += ' -range-to-';
  
  					if (parent.selectedDates.length == 1 && parent.focused) {
  						if (
  							(dp.bigger(minRange, date) && dp.less(parent.focused, date)) ||
  							(dp.less(maxRange, date) && dp.bigger(parent.focused, date))
  						) {
  							classes += ' -in-range-';
  						}
  
  						if (dp.less(maxRange, date) && dp.isSame(parent.focused, date)) {
  							classes += ' -range-from-';
  						}
  						if (dp.bigger(minRange, date) && dp.isSame(parent.focused, date)) {
  							classes += ' -range-to-';
  						}
  					} else if (parent.selectedDates.length == 2) {
  						if (dp.bigger(minRange, date) && dp.less(maxRange, date)) {
  							classes += ' -in-range-';
  						}
  					}
  				}
  
  				if (dp.isSame(currentDate, date, type)) classes += ' -current-';
  				if (parent.focused && dp.isSame(date, parent.focused, type)) classes += ' -focus-';
  				if (parent._isSelected(date, type)) classes += ' -selected-';
  				if (!parent._isInRange(date, type) || render.disabled) classes += ' -disabled-';
  
  				return {
  					html: html,
  					classes: classes
  				};
  			},
  
  			/**
           * Calculates days number to render. Generates days html and returns it.
           * @param {object} date - Date object
           * @returns {string}
           * @private
           */
  			_getDaysHtml: function(date) {
  				var totalMonthDays = dp.getDaysCount(date),
  					firstMonthDay = new Date(date.getFullYear(), date.getMonth(), 1).getDay(),
  					lastMonthDay = new Date(date.getFullYear(), date.getMonth(), totalMonthDays).getDay(),
  					daysFromPevMonth = firstMonthDay - this.d.loc.firstDay,
  					daysFromNextMonth = 6 - lastMonthDay + this.d.loc.firstDay;
  
  				daysFromPevMonth = daysFromPevMonth < 0 ? daysFromPevMonth + 7 : daysFromPevMonth;
  				daysFromNextMonth = daysFromNextMonth > 6 ? daysFromNextMonth - 7 : daysFromNextMonth;
  
  				var startDayIndex = -daysFromPevMonth + 1,
  					m,
  					y,
  					html = '';
  
  				for (var i = startDayIndex, max = totalMonthDays + daysFromNextMonth; i <= max; i++) {
  					y = date.getFullYear();
  					m = date.getMonth();
  
  					html += this._getDayHtml(new Date(y, m, i));
  				}
  
  				return html;
  			},
  
  			_getDayHtml: function(date) {
  				var content = this._getCellContents(date, 'day');
  
  				return (
  					'<div class="' +
  					content.classes +
  					'" ' +
  					'data-date="' +
  					date.getDate() +
  					'" ' +
  					'data-month="' +
  					date.getMonth() +
  					'" ' +
  					'data-year="' +
  					date.getFullYear() +
  					'">' +
  					content.html +
  					'</div>'
  				);
  			},
  
  			/**
           * Generates months html
           * @param {object} date - date instance
           * @returns {string}
           * @private
           */
  			_getMonthsHtml: function(date) {
  				var html = '',
  					d = dp.getParsedDate(date),
  					i = 0;
  
  				while (i < 12) {
  					html += this._getMonthHtml(new Date(d.year, i));
  					i++;
  				}
  
  				return html;
  			},
  
  			_getMonthHtml: function(date) {
  				var content = this._getCellContents(date, 'month');
  
  				return (
  					'<div class="' +
  					content.classes +
  					'" data-month="' +
  					date.getMonth() +
  					'">' +
  					content.html +
  					'</div>'
  				);
  			},
  
  			_getYearsHtml: function(date) {
  				var d = dp.getParsedDate(date),
  					decade = dp.getDecade(date),
  					firstYear = decade[0] - 1,
  					html = '',
  					i = firstYear;
  
  				for (i; i <= decade[1] + 1; i++) {
  					html += this._getYearHtml(new Date(i, 0));
  				}
  
  				return html;
  			},
  
  			_getYearHtml: function(date) {
  				var content = this._getCellContents(date, 'year');
  
  				return (
  					'<div class="' +
  					content.classes +
  					'" data-year="' +
  					date.getFullYear() +
  					'">' +
  					content.html +
  					'</div>'
  				);
  			},
  
  			_renderTypes: {
  				days: function() {
  					var dayNames = this._getDayNamesHtml(this.d.loc.firstDay),
  						days = this._getDaysHtml(this.d.currentDate);
  
  					this.$cells.html(days);
  					this.$names.html(dayNames);
  				},
  				months: function() {
  					var html = this._getMonthsHtml(this.d.currentDate);
  
  					this.$cells.html(html);
  				},
  				years: function() {
  					var html = this._getYearsHtml(this.d.currentDate);
  
  					this.$cells.html(html);
  				}
  			},
  
  			_render: function() {
  				if (this.opts.onlyTimepicker) return;
  				this._renderTypes[this.type].bind(this)();
  			},
  
  			_update: function() {
  				var $cells = $('.datepicker--cell', this.$cells),
  					_this = this,
  					classes,
  					$cell,
  					date;
  				$cells.each(function(cell, i) {
  					$cell = $(this);
  					date = _this.d._getDateFromCell($(this));
  					classes = _this._getCellContents(date, _this.d.cellType);
  					$cell.attr('class', classes.classes);
  				});
  			},
  
  			show: function() {
  				if (this.opts.onlyTimepicker) return;
  				this.$el.addClass('active');
  				this.acitve = true;
  			},
  
  			hide: function() {
  				this.$el.removeClass('active');
  				this.active = false;
  			},
  
  			//  Events
  			// -------------------------------------------------
  
  			_handleClick: function(el) {
  				var date = el.data('date') || 1,
  					month = el.data('month') || 0,
  					year = el.data('year') || this.d.parsedDate.year,
  					dp = this.d;
  				// Change view if min view does not reach yet
  				if (dp.view != this.opts.minView) {
  					dp.down(new Date(year, month, date));
  					return;
  				}
  				// Select date if min view is reached
  				var selectedDate = new Date(year, month, date),
  					alreadySelected = this.d._isSelected(selectedDate, this.d.cellType);
  
  				if (!alreadySelected) {
  					dp._trigger('clickCell', selectedDate);
  					return;
  				}
  
  				dp._handleAlreadySelectedDates.bind(dp, alreadySelected, selectedDate)();
  			},
  
  			_onClickCell: function(e) {
  				var $el = $(e.target).closest('.datepicker--cell');
  
  				if ($el.hasClass('-disabled-')) return;
  
  				this._handleClick.bind(this)($el);
  			}
  		};
  	})();
  
  	(function() {
  		var template =
  				'' +
  				'<div class="datepicker--nav-action" data-action="prev">#{prevHtml}</div>' +
  				'<div class="datepicker--nav-title">#{title}</div>' +
  				'<div class="datepicker--nav-action" data-action="next">#{nextHtml}</div>',
  			buttonsContainerTemplate = '<div class="datepicker--buttons"></div>',
  			button = '<span class="datepicker--button" data-action="#{action}">#{label}</span>',
  			datepicker = $.fn.datepicker,
  			dp = datepicker.Constructor;
  
  		datepicker.Navigation = function(d, opts) {
  			this.d = d;
  			this.opts = opts;
  
  			this.$buttonsContainer = '';
  
  			this.init();
  		};
  
  		datepicker.Navigation.prototype = {
  			init: function() {
  				this._buildBaseHtml();
  				this._bindEvents();
  			},
  
  			_bindEvents: function() {
  				this.d.$nav.on('click', '.datepicker--nav-action', $.proxy(this._onClickNavButton, this));
  				this.d.$nav.on('click', '.datepicker--nav-title', $.proxy(this._onClickNavTitle, this));
  				this.d.$datepicker.on('click', '.datepicker--button', $.proxy(this._onClickNavButton, this));
  			},
  
  			_buildBaseHtml: function() {
  				if (!this.opts.onlyTimepicker) {
  					this._render();
  				}
  				this._addButtonsIfNeed();
  			},
  
  			_addButtonsIfNeed: function() {
  				if (this.opts.todayButton) {
  					this._addButton('today');
  				}
  				if (this.opts.clearButton) {
  					this._addButton('clear');
  				}
  			},
  
  			_render: function() {
  				var title = this._getTitle(this.d.currentDate),
  					html = dp.template(template, $.extend({ title: title }, this.opts));
  				this.d.$nav.html(html);
  				if (this.d.view == 'years') {
  					$('.datepicker--nav-title', this.d.$nav).addClass('-disabled-');
  				}
  				this.setNavStatus();
  			},
  
  			_getTitle: function(date) {
  				return this.d.formatDate(this.opts.navTitles[this.d.view], date);
  			},
  
  			_addButton: function(type) {
  				if (!this.$buttonsContainer.length) {
  					this._addButtonsContainer();
  				}
  
  				var data = {
  						action: type,
  						label: this.d.loc[type]
  					},
  					html = dp.template(button, data);
  
  				if ($('[data-action=' + type + ']', this.$buttonsContainer).length) return;
  				this.$buttonsContainer.append(html);
  			},
  
  			_addButtonsContainer: function() {
  				this.d.$datepicker.append(buttonsContainerTemplate);
  				this.$buttonsContainer = $('.datepicker--buttons', this.d.$datepicker);
  			},
  
  			setNavStatus: function() {
  				if (!(this.opts.minDate || this.opts.maxDate) || !this.opts.disableNavWhenOutOfRange) return;
  
  				var date = this.d.parsedDate,
  					m = date.month,
  					y = date.year,
  					d = date.date;
  
  				switch (this.d.view) {
  					case 'days':
  						if (!this.d._isInRange(new Date(y, m - 1, 1), 'month')) {
  							this._disableNav('prev');
  						}
  						if (!this.d._isInRange(new Date(y, m + 1, 1), 'month')) {
  							this._disableNav('next');
  						}
  						break;
  					case 'months':
  						if (!this.d._isInRange(new Date(y - 1, m, d), 'year')) {
  							this._disableNav('prev');
  						}
  						if (!this.d._isInRange(new Date(y + 1, m, d), 'year')) {
  							this._disableNav('next');
  						}
  						break;
  					case 'years':
  						var decade = dp.getDecade(this.d.date);
  						if (!this.d._isInRange(new Date(decade[0] - 1, 0, 1), 'year')) {
  							this._disableNav('prev');
  						}
  						if (!this.d._isInRange(new Date(decade[1] + 1, 0, 1), 'year')) {
  							this._disableNav('next');
  						}
  						break;
  				}
  			},
  
  			_disableNav: function(nav) {
  				$('[data-action="' + nav + '"]', this.d.$nav).addClass('-disabled-');
  			},
  
  			_activateNav: function(nav) {
  				$('[data-action="' + nav + '"]', this.d.$nav).removeClass('-disabled-');
  			},
  
  			_onClickNavButton: function(e) {
  				var $el = $(e.target).closest('[data-action]'),
  					action = $el.data('action');
  
  				this.d[action]();
  			},
  
  			_onClickNavTitle: function(e) {
  				if ($(e.target).hasClass('-disabled-')) return;
  
  				if (this.d.view == 'days') {
  					return (this.d.view = 'months');
  				}
  
  				this.d.view = 'years';
  			}
  		};
  	})();
  
  	(function() {
  		var template =
  				'<div class="datepicker--time">' +
  				'<div class="datepicker--time-current">' +
  				'   <span class="datepicker--time-current-hours">#{hourVisible}</span>' +
  				'   <span class="datepicker--time-current-colon">:</span>' +
  				'   <span class="datepicker--time-current-minutes">#{minValue}</span>' +
  				'</div>' +
  				'<div class="datepicker--time-sliders">' +
  				'   <div class="datepicker--time-row">' +
  				'      <input type="range" name="hours" value="#{hourValue}" min="#{hourMin}" max="#{hourMax}" step="#{hourStep}"/>' +
  				'   </div>' +
  				'   <div class="datepicker--time-row">' +
  				'      <input type="range" name="minutes" value="#{minValue}" min="#{minMin}" max="#{minMax}" step="#{minStep}"/>' +
  				'   </div>' +
  				'</div>' +
  				'</div>',
  			datepicker = $.fn.datepicker,
  			dp = datepicker.Constructor;
  
  		datepicker.Timepicker = function(inst, opts) {
  			this.d = inst;
  			this.opts = opts;
  
  			this.init();
  		};
  
  		datepicker.Timepicker.prototype = {
  			init: function() {
  				var input = 'input';
  				this._setTime(this.d.date);
  				this._buildHTML();
  
  				if (navigator.userAgent.match(/trident/gi)) {
  					input = 'change';
  				}
  
  				this.d.$el.on('selectDate', this._onSelectDate.bind(this));
  				this.$ranges.on(input, this._onChangeRange.bind(this));
  				this.$ranges.on('mouseup', this._onMouseUpRange.bind(this));
  				this.$ranges.on('mousemove focus ', this._onMouseEnterRange.bind(this));
  				this.$ranges.on('mouseout blur', this._onMouseOutRange.bind(this));
  			},
  
  			_setTime: function(date) {
  				var _date = dp.getParsedDate(date);
  
  				this._handleDate(date);
  				this.hours = _date.hours < this.minHours ? this.minHours : _date.hours;
  				this.minutes = _date.minutes < this.minMinutes ? this.minMinutes : _date.minutes;
  			},
  
  			/**
           * Sets minHours and minMinutes from date (usually it's a minDate)
           * Also changes minMinutes if current hours are bigger then @date hours
           * @param date {Date}
           * @private
           */
  			_setMinTimeFromDate: function(date) {
  				this.minHours = date.getHours();
  				this.minMinutes = date.getMinutes();
  
  				// If, for example, min hours are 10, and current hours are 12,
  				// update minMinutes to default value, to be able to choose whole range of values
  				if (this.d.lastSelectedDate) {
  					if (this.d.lastSelectedDate.getHours() > date.getHours()) {
  						this.minMinutes = this.opts.minMinutes;
  					}
  				}
  			},
  
  			_setMaxTimeFromDate: function(date) {
  				this.maxHours = date.getHours();
  				this.maxMinutes = date.getMinutes();
  
  				if (this.d.lastSelectedDate) {
  					if (this.d.lastSelectedDate.getHours() < date.getHours()) {
  						this.maxMinutes = this.opts.maxMinutes;
  					}
  				}
  			},
  
  			_setDefaultMinMaxTime: function() {
  				var maxHours = 23,
  					maxMinutes = 59,
  					opts = this.opts;
  
  				this.minHours = opts.minHours < 0 || opts.minHours > maxHours ? 0 : opts.minHours;
  				this.minMinutes = opts.minMinutes < 0 || opts.minMinutes > maxMinutes ? 0 : opts.minMinutes;
  				this.maxHours = opts.maxHours < 0 || opts.maxHours > maxHours ? maxHours : opts.maxHours;
  				this.maxMinutes = opts.maxMinutes < 0 || opts.maxMinutes > maxMinutes ? maxMinutes : opts.maxMinutes;
  			},
  
  			/**
           * Looks for min/max hours/minutes and if current values
           * are out of range sets valid values.
           * @private
           */
  			_validateHoursMinutes: function(date) {
  				if (this.hours < this.minHours) {
  					this.hours = this.minHours;
  				} else if (this.hours > this.maxHours) {
  					this.hours = this.maxHours;
  				}
  
  				if (this.minutes < this.minMinutes) {
  					this.minutes = this.minMinutes;
  				} else if (this.minutes > this.maxMinutes) {
  					this.minutes = this.maxMinutes;
  				}
  			},
  
  			_buildHTML: function() {
  				var lz = dp.getLeadingZeroNum,
  					data = {
  						hourMin: this.minHours,
  						hourMax: lz(this.maxHours),
  						hourStep: this.opts.hoursStep,
  						hourValue: this.hours,
  						hourVisible: lz(this.displayHours),
  						minMin: this.minMinutes,
  						minMax: lz(this.maxMinutes),
  						minStep: this.opts.minutesStep,
  						minValue: lz(this.minutes)
  					},
  					_template = dp.template(template, data);
  
  				this.$timepicker = $(_template).appendTo(this.d.$datepicker);
  				this.$ranges = $('[type="range"]', this.$timepicker);
  				this.$hours = $('[name="hours"]', this.$timepicker);
  				this.$minutes = $('[name="minutes"]', this.$timepicker);
  				this.$hoursText = $('.datepicker--time-current-hours', this.$timepicker);
  				this.$minutesText = $('.datepicker--time-current-minutes', this.$timepicker);
  
  				if (this.d.ampm) {
  					this.$ampm = $('<span class="datepicker--time-current-ampm">')
  						.appendTo($('.datepicker--time-current', this.$timepicker))
  						.html(this.dayPeriod);
  
  					this.$timepicker.addClass('-am-pm-');
  				}
  			},
  
  			_updateCurrentTime: function() {
  				var h = dp.getLeadingZeroNum(this.displayHours),
  					m = dp.getLeadingZeroNum(this.minutes);
  
  				this.$hoursText.html(h);
  				this.$minutesText.html(m);
  
  				if (this.d.ampm) {
  					this.$ampm.html(this.dayPeriod);
  				}
  			},
  
  			_updateRanges: function() {
  				this.$hours
  					.attr({
  						min: this.minHours,
  						max: this.maxHours
  					})
  					.val(this.hours);
  
  				this.$minutes
  					.attr({
  						min: this.minMinutes,
  						max: this.maxMinutes
  					})
  					.val(this.minutes);
  			},
  
  			/**
           * Sets minHours, minMinutes etc. from date. If date is not passed, than sets
           * values from options
           * @param [date] {object} - Date object, to get values from
           * @private
           */
  			_handleDate: function(date) {
  				this._setDefaultMinMaxTime();
  				if (date) {
  					if (dp.isSame(date, this.d.opts.minDate)) {
  						this._setMinTimeFromDate(this.d.opts.minDate);
  					} else if (dp.isSame(date, this.d.opts.maxDate)) {
  						this._setMaxTimeFromDate(this.d.opts.maxDate);
  					}
  				}
  
  				this._validateHoursMinutes(date);
  			},
  
  			update: function() {
  				this._updateRanges();
  				this._updateCurrentTime();
  			},
  
  			/**
           * Calculates valid hour value to display in text input and datepicker's body.
           * @param date {Date|Number} - date or hours
           * @param [ampm] {Boolean} - 12 hours mode
           * @returns {{hours: *, dayPeriod: string}}
           * @private
           */
  			_getValidHoursFromDate: function(date, ampm) {
  				var d = date,
  					hours = date;
  
  				if (date instanceof Date) {
  					d = dp.getParsedDate(date);
  					hours = d.hours;
  				}
  
  				var _ampm = ampm || this.d.ampm,
  					dayPeriod = 'am';
  
  				if (_ampm) {
  					switch (true) {
  						case hours == 0:
  							hours = 12;
  							break;
  						case hours == 12:
  							dayPeriod = 'pm';
  							break;
  						case hours > 11:
  							hours = hours - 12;
  							dayPeriod = 'pm';
  							break;
  						default:
  							break;
  					}
  				}
  
  				return {
  					hours: hours,
  					dayPeriod: dayPeriod
  				};
  			},
  
  			set hours(val) {
  				this._hours = val;
  
  				var displayHours = this._getValidHoursFromDate(val);
  
  				this.displayHours = displayHours.hours;
  				this.dayPeriod = displayHours.dayPeriod;
  			},
  
  			get hours() {
  				return this._hours;
  			},
  
  			//  Events
  			// -------------------------------------------------
  
  			_onChangeRange: function(e) {
  				var $target = $(e.target),
  					name = $target.attr('name');
  
  				this.d.timepickerIsActive = true;
  
  				this[name] = $target.val();
  				this._updateCurrentTime();
  				this.d._trigger('timeChange', [ this.hours, this.minutes ]);
  
  				this._handleDate(this.d.lastSelectedDate);
  				this.update();
  			},
  
  			_onSelectDate: function(e, data) {
  				this._handleDate(data);
  				this.update();
  			},
  
  			_onMouseEnterRange: function(e) {
  				var name = $(e.target).attr('name');
  				$('.datepicker--time-current-' + name, this.$timepicker).addClass('-focus-');
  			},
  
  			_onMouseOutRange: function(e) {
  				var name = $(e.target).attr('name');
  				if (this.d.inFocus) return; // Prevent removing focus when mouse out of range slider
  				$('.datepicker--time-current-' + name, this.$timepicker).removeClass('-focus-');
  			},
  
  			_onMouseUpRange: function(e) {
  				this.d.timepickerIsActive = false;
  			}
  		};
  	})();
  }
  