/**
 * Created by Greg Zhang
 */
 ;(function (factory) {

    if (typeof define === 'function' && define.amd) {
      define(['jquery'], factory);
    } else if (typeof exports === 'object') {
      factory(require('jquery'));
    } else {
      factory(jQuery);
    }
  
  })(function ($) {
  
    'use strict';
  
    var dateUtils = (function () {
        var fecha = {};
        var token = /d{1,4}|M{1,4}|yy(?:yy)?|S{1,3}|Do|ZZ|([HhMsDm])\1?|[aA]|"[^"]*"|'[^']*'/g;
        var twoDigits = /\d\d?/;
        var threeDigits = /\d{3}/;
        var fourDigits = /\d{4}/;
        var word = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i;
        var noop = function () {
        };
  
        function shorten(arr, sLen) {
          var newArr = [];
          for (var i = 0, len = arr.length; i < len; i++) {
            newArr.push(arr[i].substr(0, sLen));
          }
          return newArr;
        }
  
        function monthUpdate(arrName) {
          return function (d, v, i18n) {
            var index = i18n[arrName].indexOf(v.charAt(0).toUpperCase() + v.substr(1).toLowerCase());
            if (~index) {
              d.month = index;
            }
          };
        }
  
        function pad(val, len) {
          val = String(val);
          len = len || 2;
          while (val.length < len) {
            val = '0' + val;
          }
          return val;
        }
  
        var dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
        var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
        var monthNamesShort = shorten(monthNames, 3);
        var dayNamesShort = shorten(dayNames, 3);
        fecha.i18n = {
          dayNamesShort: dayNamesShort,
          dayNames: dayNames,
          monthNamesShort: monthNamesShort,
          monthNames: monthNames,
          amPm: ['am', 'pm'],
          DoFn: function DoFn(D) {
            return D + ['th', 'st', 'nd', 'rd'][D % 10 > 3 ? 0 : (D - D % 10 !== 10) * D % 10];
          }
        };
  
        var formatFlags = {
          D: function(dateObj) {
            return dateObj.getDay();
          },
          DD: function(dateObj) {
            return pad(dateObj.getDay());
          },
          Do: function(dateObj, i18n) {
            return i18n.DoFn(dateObj.getDate());
          },
          d: function(dateObj) {
            return dateObj.getDate();
          },
          dd: function(dateObj) {
            return pad(dateObj.getDate());
          },
          ddd: function(dateObj, i18n) {
            return i18n.dayNamesShort[dateObj.getDay()];
          },
          dddd: function(dateObj, i18n) {
            return i18n.dayNames[dateObj.getDay()];
          },
          M: function(dateObj) {
            return dateObj.getMonth() + 1;
          },
          MM: function(dateObj) {
            return pad(dateObj.getMonth() + 1);
          },
          MMM: function(dateObj, i18n) {
            return i18n.monthNamesShort[dateObj.getMonth()];
          },
          MMMM: function(dateObj, i18n) {
            return i18n.monthNames[dateObj.getMonth()];
          },
          yy: function(dateObj) {
            return String(dateObj.getFullYear()).substr(2);
          },
          yyyy: function(dateObj) {
            return dateObj.getFullYear();
          },
          h: function(dateObj) {
            return dateObj.getHours() % 12 || 12;
          },
          hh: function(dateObj) {
            return pad(dateObj.getHours() % 12 || 12);
          },
          H: function(dateObj) {
            return dateObj.getHours();
          },
          HH: function(dateObj) {
            return pad(dateObj.getHours());
          },
          m: function(dateObj) {
            return dateObj.getMinutes();
          },
          mm: function(dateObj) {
            return pad(dateObj.getMinutes());
          },
          s: function(dateObj) {
            return dateObj.getSeconds();
          },
          ss: function(dateObj) {
            return pad(dateObj.getSeconds());
          },
          S: function(dateObj) {
            return Math.round(dateObj.getMilliseconds() / 100);
          },
          SS: function(dateObj) {
            return pad(Math.round(dateObj.getMilliseconds() / 10), 2);
          },
          SSS: function(dateObj) {
            return pad(dateObj.getMilliseconds(), 3);
          },
          a: function(dateObj, i18n) {
            return dateObj.getHours() < 12 ? i18n.amPm[0] : i18n.amPm[1];
          },
          A: function(dateObj, i18n) {
            return dateObj.getHours() < 12 ? i18n.amPm[0].toUpperCase() : i18n.amPm[1].toUpperCase();
          },
          ZZ: function(dateObj) {
            var o = dateObj.getTimezoneOffset();
            return (o > 0 ? '-' : '+') + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4);
          }
        };
  
        var parseFlags = {
          d: [twoDigits, function (d, v) {
            d.day = v;
          }],
          M: [twoDigits, function (d, v) {
            d.month = v - 1;
          }],
          yy: [twoDigits, function (d, v) {
            var da = new Date(), cent = +('' + da.getFullYear()).substr(0, 2);
            d.year = '' + (v > 68 ? cent - 1 : cent) + v;
          }],
          h: [twoDigits, function (d, v) {
            d.hour = v;
          }],
          m: [twoDigits, function (d, v) {
            d.minute = v;
          }],
          s: [twoDigits, function (d, v) {
            d.second = v;
          }],
          yyyy: [fourDigits, function (d, v) {
            d.year = v;
          }],
          S: [/\d/, function (d, v) {
            d.millisecond = v * 100;
          }],
          SS: [/\d{2}/, function (d, v) {
            d.millisecond = v * 10;
          }],
          SSS: [threeDigits, function (d, v) {
            d.millisecond = v;
          }],
          D: [twoDigits, noop],
          ddd: [word, noop],
          MMM: [word, monthUpdate('monthNamesShort')],
          MMMM: [word, monthUpdate('monthNames')],
          a: [word, function (d, v, i18n) {
            var val = v.toLowerCase();
            if (val === i18n.amPm[0]) {
              d.isPm = false;
            } else if (val === i18n.amPm[1]) {
              d.isPm = true;
            }
          }],
          ZZ: [/[\+\-]\d\d:?\d\d/, function (d, v) {
            var parts = (v + '').match(/([\+\-]|\d\d)/gi), minutes;
  
            if (parts) {
              minutes = +(parts[1] * 60) + parseInt(parts[2], 10);
              d.timezoneOffset = parts[0] === '+' ? minutes : -minutes;
            }
          }]
        };
        parseFlags.DD = parseFlags.DD;
        parseFlags.dddd = parseFlags.ddd;
        parseFlags.Do = parseFlags.dd = parseFlags.d;
        parseFlags.mm = parseFlags.m;
        parseFlags.hh = parseFlags.H = parseFlags.HH = parseFlags.h;
        parseFlags.MM = parseFlags.M;
        parseFlags.ss = parseFlags.s;
        parseFlags.A = parseFlags.a;
  
  
        // Some common format strings
        fecha.masks = {
          'default': 'ddd MMM dd yyyy HH:mm:ss',
          shortDate: 'M/D/yy',
          mediumDate: 'MMM d, yyyy',
          longDate: 'MMMM d, yyyy',
          fullDate: 'dddd, MMMM d, yyyy',
          shortTime: 'HH:mm',
          mediumTime: 'HH:mm:ss',
          longTime: 'HH:mm:ss.SSS'
        };
  
        /***
         * Format a date
         * @method format
         * @param {Date|number} dateObj
         * @param {string} mask Format of the date, i.e. 'mm-dd-yy' or 'shortDate'
         */
        fecha.format = function (dateObj, mask, i18nSettings) {
          var i18n = i18nSettings || fecha.i18n;
  
          if (typeof dateObj === 'number') {
            dateObj = new Date(dateObj);
          }
  
          if (Object.prototype.toString.call(dateObj) !== '[object Date]' || isNaN(dateObj.getTime())) {
            throw new Error('Invalid Date in fecha.format');
          }
  
          mask = fecha.masks[mask] || mask || fecha.masks['default'];
  
          return mask.replace(token, function ($0) {
            return $0 in formatFlags ? formatFlags[$0](dateObj, i18n) : $0.slice(1, $0.length - 1);
          });
        };
  
        /**
         * Parse a date string into an object, changes - into /
         * @method parse
         * @param {string} dateStr Date string
         * @param {string} format Date parse format
         * @returns {Date|boolean}
         */
        fecha.parse = function (dateStr, format, i18nSettings) {
          var i18n = i18nSettings || fecha.i18n;
  
          if (typeof format !== 'string') {
            throw new Error('Invalid format in fecha.parse');
          }
  
          format = fecha.masks[format] || format;
  
          // Avoid regular expression denial of service, fail early for really long strings
          // https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS
          if (dateStr.length > 1000) {
            return false;
          }
  
          var isValid = true;
          var dateInfo = {};
          format.replace(token, function ($0) {
            if (parseFlags[$0]) {
              var info = parseFlags[$0];
              var index = dateStr.search(info[0]);
              if (!~index) {
                isValid = false;
              } else {
                dateStr.replace(info[0], function (result) {
                  info[1](dateInfo, result, i18n);
                  dateStr = dateStr.substr(index + result.length);
                  return result;
                });
              }
            }
  
            return parseFlags[$0] ? '' : $0.slice(1, $0.length - 1);
          });
  
          if (!isValid) {
            return false;
          }
  
          var today = new Date();
          if (dateInfo.isPm === true && dateInfo.hour != null && +dateInfo.hour !== 12) {
            dateInfo.hour = +dateInfo.hour + 12;
          } else if (dateInfo.isPm === false && +dateInfo.hour === 12) {
            dateInfo.hour = 0;
          }
  
          var date;
          if (dateInfo.timezoneOffset != null) {
            dateInfo.minute = +(dateInfo.minute || 0) - +dateInfo.timezoneOffset;
            date = new Date(Date.UTC(dateInfo.year || today.getFullYear(), dateInfo.month || 0, dateInfo.day || 1,
              dateInfo.hour || 0, dateInfo.minute || 0, dateInfo.second || 0, dateInfo.millisecond || 0));
          } else {
            date = new Date(dateInfo.year || today.getFullYear(), dateInfo.month || 0, dateInfo.day || 1,
              dateInfo.hour || 0, dateInfo.minute || 0, dateInfo.second || 0, dateInfo.millisecond || 0);
          }
          return date;
        };
  
        return fecha;
      })();
  
    // Const DAY_DURATION
    var DAY_DURATION = 86400000;
    // Const DEFAULT_TIME_FORMAT
    var DEFAULT_TIME_FORMAT = 'HH:mm:ss';
    // Const DEFAULT_TIME_VALUE
    var DEFAULT_TIME_VALUE = '00:00:00';
    // Const DATE_PANEL_Z_INDEX
    var DATE_PANEL_Z_INDEX = 2008;
    // Const TIME_PANEL_Z_INDEX
    var TIME_PANEL_Z_INDEX = DATE_PANEL_Z_INDEX + 1;
    // Const TIME_PANEL_WIDTH
    var TIME_PANEL_WIDTH = 154;
    // Const KEY_CODE_ENTER
    var KEY_CODE_ENTER = 13;
    // Const CLASS_PLACEMENT_LEFT_BOTTOM
    var CLASS_PLACEMENT_LEFT_BOTTOM = 'placement-left-bottom';
    // Const CLASS_PLACEMENT_CENTER_BOTTOM
    var CLASS_PLACEMENT_CENTER_BOTTOM = 'placement-center-bottom';
    // Const CLASS_PLACEMENT_RIGHT_BOTTOM
    var CLASS_PLACEMENT_RIGHT_BOTTOM = 'placement-right-bottom';
    // Const IE_MODE
    var inBrowser = typeof window !== 'undefined';
    var UA = inBrowser && window.navigator.userAgent.toLowerCase();
    var isIE = UA && /msie|trident/.test(UA);
    var IE_MODE = document.documentMode;
  
    var DatePicker = function ($el, options) {
      var datepicker = this;
      var core = {
        defaults: {
          readonly: false,
          disabled: false,
          type: 'date', // year/month/date/date-range/datetime/datetime-range
          format: 'yyyy-MM-dd',
          placeholder: 'Please pick a day',
          align: 'left',
          startDate: null,
          endDate: null,
          lang: 'en-US',
          rangeSeparator: '-',
          weekStart: 0,
          defaultValue: '',
          zIndex: DATE_PANEL_Z_INDEX,
          onChange: null,
          onShow: null,
          onHide: null
        },
        _init: function () {
          // copy property from options
          datepicker = $.extend(true, datepicker, core.defaults, options || {});
          if (datepicker.lang) datepicker = $.extend(datepicker, $.fn.datepicker.lang[datepicker.lang]);
  
          var type = datepicker.type;
  
          if ((type === 'datetime' || type === 'datetime-range')
            && datepicker.format === core.defaults.format) {
            datepicker.format = 'yyyy-MM-dd HH:mm:ss';
          } else if (type === 'year') {
            datepicker.format = 'yyyy';
          } else if (type === 'month' && datepicker.format === core.defaults.format) {
            datepicker.format = 'yyyy-MM';
          }
  
          // init param date and value
          datepicker.date = new Date();
          datepicker.value = '';
          datepicker.yearLabel = datepicker.date.getFullYear();
          datepicker.monthLabel = datepicker.date.getMonth();
  
          if (datepicker.readonly) $el.attr('readonly', true);
          if (datepicker.disabled) $el.attr('disabled', true);
          if (datepicker.placeholder) $el.attr('placeholder', datepicker.placeholder);
          core._created();
        },
        _created: function () {
          var type = datepicker.type;
          var align = datepicker.align;
          var zIndex = datepicker.zIndex;
          var originX = align === 'center' ? '50%' : align === 'right' ? '100%' : '0';
          var $body = $('body');
          var dateDom;
  
          if (type === 'date' || type === 'month' || type === 'year' || type === 'datetime') {
            if (type === 'date' || type === 'datetime') {
              datepicker.currentView = 'dateView';
            } else if (type === 'year') {
              datepicker.currentView = 'yearView';
            } else if (type === 'month') {
              datepicker.currentView = 'monthView';
            }
            // get datepicker panel
            dateDom = core._generateDateDOM();
          } else if (type === 'date-range' || type === 'datetime-range') {
            // get range datepicker panel
            dateDom = core._generateRangeDateDOM();
          }
  
          // append picker panel into the dom tree
          datepicker.$pickerPanel = $(dateDom).appendTo($body).css({
            position: 'absolute',
            zIndex: parseInt(zIndex, 10)
          });
  
          if (!isIE  || (isIE && IE_MODE > 9)) {
            datepicker.$pickerPanel.css({transformOrigin: originX + ' 0', msTransformOrigin: originX + ' 0'});
          }
  
          // set position for time panel
          if (datepicker.$pickerPanel.find('.gmi-time-panel').length > 0) {
            var $timePanel = datepicker.$pickerPanel.find('.gmi-time-panel');
            $timePanel.css({
              width: TIME_PANEL_WIDTH + 'px',
              position: 'absolute',
              left: 0,
              zIndex: TIME_PANEL_Z_INDEX
            });
            datepicker.$timePanel = $timePanel;
          }
  
          // bind EVENT_PICK for $el
          if ($.isFunction(datepicker.onChange)) {
            $el.on('pick.datepicker', datepicker.onChange);
          }
  
          // set default date
          core._setDate(datepicker.defaultValue);
  
          // set table show or hide for date, datetime, year, month
          switch (type) {
            case 'date':
              datepicker.$pickerPanel.find('.gmi-date-table').show().siblings().hide();
              break;
            case 'datetime':
              datepicker.$pickerPanel.find('.gmi-date-table').show().siblings().hide();
              break;
            case 'year':
              datepicker.$pickerPanel.find('.gmi-year-table').show().siblings().hide();
              break;
            case 'month':
              datepicker.$pickerPanel.find('.gmi-month-table').show().siblings().hide();
              break;
          }
          // push date or time value into the input elements
          core._echoDateOrTimeIntoInput();
          // bind events for datepicker
          core._bindEvent();
        },
        _unCreate: function () {
          var $pickPanel = datepicker.$pickerPanel;
          if ($pickPanel && $pickPanel.length > 0) $pickPanel.remove();
        },
        _bindEvent: function () {
          $(document).on('click.datepicker', function (e) {
            var $target = $(e.target);
            if (!$target.is($el) && $el.has($target).length <= 0) {
              core._hidePickerPanel();
            }
          });
  
          // when window resizing or scrolling that the panel will change its position
          $(window).on('resize.datepicker', function () {
            core._setDatePanelPosition();
          }).on('scroll.datepicker', function () {
            core._setDatePanelPosition();
          });
  
          // bind events for $el
          $el.on('focus.datepicker', core._elFocusHandler)
            .on('click.datepicker', core._elClickHandler)
            .on('change.datepicker', core._elChangeHandler)
            .on('keyup.datepicker', core._elKeyUpHandler);
  
          if ($.isFunction(datepicker.onShow)) {
            $el.on('show.datepicker', datepicker.onShow);
          }
  
          if ($.isFunction(datepicker.onHide)) {
            $el.on('show.datepicker', datepicker.onHide);
          }
  
          // cancel bubble for $pickerPanel
          datepicker.$pickerPanel.on('click.datepicker', function (e) {
            e.stopPropagation();
          });
  
          // bind events for time input
          datepicker.$pickerPanel.on('focus.datepicker', '.gmi-time-picker--input', function (e) {
            var $self = $(this);
            core._setTimeView($self, e);
          }).on('keyup.datepicker', '.gmi-time-picker--input', function (e) {
            var $self = $(this);
            core._setTimeView($self, e);
          }).on('change.datepicker', '.gmi-time-picker--input', function (e) {
            var $self = $(this);
            core._setTimeView($self, e);
          });
  
          if (datepicker.$timePanel && datepicker.$timePanel.length > 0) {
            datepicker.$timePanel.on('mouseenter.datepicker', '.gmi-time-panel__body__item', function (e) {
              // mouseenter
              var $self = $(this);
              var $spinner = $self.find('> ul.gmi-time-panel__body__item--spinner');
              $self.css('overflow', 'auto');
              $spinner.css('width', '100%');
            }).on('mouseleave.datepicker', '.gmi-time-panel__body__item', function (e) {
              // mouseleave
              var $self = $(this);
              var $spinner = $self.find('> ul.gmi-time-panel__body__item--spinner');
              var selfWidth = $self.outerWidth();
              $self.css('overflow', 'hidden');
              $spinner.css('width', selfWidth + 'px');
            });
  
            datepicker.$timePanel.on('click.datepicker', '.gmi-time-panel__body__item--spinner__item:not(.disabled)', function (e) {
              var $timeItem = $(this);
              var $timeItemWrapper = $timeItem.parents('.gmi-time-panel__body__item').eq(0);
              var role = $timeItem.parents('.gmi-time-panel__body__item--spinner').data('role');
              var num = Number($timeItem.text());
              var itemHeight = $timeItem.outerHeight();
              var scrollTop = num * itemHeight;
  
              $timeItem.addClass('active').siblings().removeClass('active');
              $timeItemWrapper.scrollTop(scrollTop);
              e.stopPropagation();
            });
  
            // timer panel button events
            datepicker.$timePanel.on('click.datepicker', '.gmi-time-panel__btn', function (e) {
              var $self = $(this);
              var role = $self.data('role');
              var tempDate = new Date();
              var $delegateTarget = $(e.delegateTarget);
              var $timeInput = $delegateTarget.siblings('.gmi-time-picker--input');
              var $hourSpinner = $delegateTarget.find('.gmi-time-panel__body__item--spinner[data-role="hour"]');
              var $minSpinner = $delegateTarget.find('.gmi-time-panel__body__item--spinner[data-role="min"]');
              var $secSpinner = $delegateTarget.find('.gmi-time-panel__body__item--spinner[data-role="sec"]');
              var hour = Number($hourSpinner.find('> li.active').text());
              var minutes = Number($minSpinner.find('> li.active').text());
              var seconds = Number($secSpinner.find('> li.active').text());
  
              tempDate.setHours(hour, minutes, seconds, 0);
              switch (role) {
                case 'confirm':
                  $timeInput.val($.formatDate(tempDate, DEFAULT_TIME_FORMAT));
                  $delegateTarget.hide();
                  var $allTimeInput = datepicker.$pickerPanel.find('.gmi-time-picker--input').filter(function () {
                    return $(this).val() === '' || !$.parseDate($(this).val(), DEFAULT_TIME_FORMAT);
                  });
                  if ($allTimeInput && $allTimeInput.length === 0) datepicker.$pickerPanel.find('.gmi-picker-panel__link-btn--determine').removeClass('disabled');
                  break;
                case 'cancel':
                  $delegateTarget.hide();
                  break;
                default:
                  break;
              }
            });
          }
  
          // bind event for td
          datepicker.$pickerPanel.on('click.datepicker', 'td:not(.disabled)', function (e) {
            var type = datepicker.type;
            var format = datepicker.format;
            var $delegateTarget = $(e.delegateTarget);
            var $determineButton = $delegateTarget.find('.gmi-picker-panel__link-btn--determine');
            var $timeInput;
            var $td = $(this);
            var elValue;
  
            e.stopPropagation();
            if (type === 'date' || type === 'datetime') {
              var currentView = datepicker.currentView;
              var year;
              var month;
  
              if (currentView === 'dateView') { // dateView
                var date = $td.text() === datepicker.todaySuffix ? new Date().getDate() : Number($td.text());
                if ($td.hasClass('prev-month') || $td.hasClass('next-month')) {
                  if ($td.hasClass('prev-month')) {
                    year = datepicker.monthLabel - 1 < 0 ? datepicker.yearLabel - 1 : datepicker.yearLabel;
                    month = datepicker.monthLabel - 1 < 0 ? 11 : datepicker.monthLabel - 1;
                  } else if ($td.hasClass('next-month')) {
                    year = datepicker.monthLabel + 1 > 11 ? datepicker.yearLabel + 1 : datepicker.yearLabel;
                    month = datepicker.monthLabel + 1 > 11 ? 0 : datepicker.monthLabel + 1;
                  }
  
                  if (type === 'date') {
                    elValue = $.formatDate(new Date(year, month, date), format);
                    core._setDate(elValue);
                  } else {
                    elValue = $.formatDate(new Date(year, month, date));
                    core._setNewDateDOM($delegateTarget, year, month, date);
                    $delegateTarget.find('.gmi-date-table td').removeClass('current').filter(function () {
                      return Number($(this).data('year')) === year && Number($(this).data('month')) === month &&
                        Number($(this).text()) === date;
                    }).addClass('current');
                    datepicker.yearLabel = year;
                    datepicker.monthLabel = month;
                  }
                } else {
                  year = datepicker.yearLabel;
                  month = datepicker.monthLabel;
  
                  if (type === 'date') {
                    elValue = $.formatDate(new Date(year, month, date), format);
                    core._setDate(elValue);
                  } else {
                    elValue = $.formatDate(new Date(year, month, date));
                    $delegateTarget.find('.gmi-date-table').find('td').removeClass('current');
                    $td.addClass('current');
                  }
                }
                if (type === 'datetime') {
                  $delegateTarget.find('.gmi-date-picker--input').val(elValue);
                  $timeInput = $delegateTarget.find('.gmi-time-picker--input');
                  if ($timeInput.val() === '' || !$.parseDate($timeInput.val(), DEFAULT_TIME_FORMAT)) {
                    $timeInput.val(DEFAULT_TIME_VALUE);
                  }
                  $determineButton.removeClass('disabled');
                  return false;
                }
                core._hidePickerPanel();
              } else if (currentView === 'yearView') { // yearView
                core._setYearView($td);
              } else { // monthView
                core._setMonthView($td);
              }
            } else if (type === 'month') {
              switch (datepicker.currentView) {
                case 'monthView':
                  core._setMonthView($td);
                  break;
                case 'yearView':
                  core._setYearView($td);
                  break;
                default:
                  break;
              }
            } else if (type === 'year') {
              core._setYearView($td);
            } else if (type === 'date-range' || type === 'datetime-range') {
              var minDate = datepicker.minDate;
              var maxDate = datepicker.maxDate;
              var $startDateInput = $delegateTarget.find('.gmi-date-picker--input[data-role="range-start"]');
              var $startTimeInput = $delegateTarget.find('.gmi-time-picker--input[data-role="range-start"]');
              var $endDateInput = $delegateTarget.find('.gmi-date-picker--input[data-role="range-end"]');
              var $endTimeInput = $delegateTarget.find('.gmi-time-picker--input[data-role="range-end"]');
              var value;
              year = Number($td.data('year'));
              month = Number($td.data('month'));
              date = $td.text() === datepicker.todaySuffix ? new Date().getDate() : Number($td.text());
              if (minDate && maxDate) {
                datepicker.minDate = new Date(year, month, date);
                datepicker.maxDate = null;
                $delegateTarget.find('.gmi-date-table td').removeClass('start-date in-range end-date');
                if (!$td.hasClass('prev-month') && !$td.hasClass('next-month')) $td.addClass('start-date in-range');
                if (type === 'datetime-range') {
                  $startDateInput.val($.formatDate(datepicker.minDate));
                  $determineButton.addClass('disabled');
                }
              } else if (minDate && !maxDate) {
                if (new Date(year, month, date).getTime() < minDate.getTime()) {
                  datepicker.minDate = new Date(year, month, date);
                  $delegateTarget.find('.gmi-date-table td').removeClass('start-date in-range');
                  if (!$td.hasClass('prev-month') && !$td.hasClass('next-month')) $td.addClass('start-date in-range');
                  if (type === 'datetime-range') {
                    $startDateInput.val($.formatDate(datepicker.minDate));
                    $determineButton.addClass('disabled');
                  }
                } else {
                  datepicker.maxDate = new Date(year, month, date);
                  if (!$td.hasClass('prev-month') && !$td.hasClass('next-month')) $td.addClass('end-date in-range');
                  $delegateTarget.find('.gmi-date-table td').filter(function () {
                    var $self = $(this);
                    var selfYear = Number($self.data('year'));
                    var selfMonth = Number($self.data('month'));
                    var selfDate = $self.text() === datepicker.todaySuffix ? new Date().getDate() : Number($self.text());
                    var rangeDate = new Date(selfYear, selfMonth, selfDate);
                    return !$self.hasClass('prev-month') && !$self.hasClass('next-month') &&
                      (rangeDate > datepicker.minDate.getTime()) &&
                      (rangeDate.getTime() < datepicker.maxDate.getTime());
                  }).addClass('in-range');
                  if (type === 'date-range') {
                    value = $.formatDate(datepicker.minDate, format) + ' '+ datepicker.rangeSeparator +' ' + $.formatDate(datepicker.maxDate, format);
                    core._setDate(value);
                    core._hidePickerPanel();
                  } else {
                    $endDateInput.val($.formatDate(datepicker.maxDate));
                    if ($startTimeInput.val() === '') $startTimeInput.val(DEFAULT_TIME_VALUE);
                    if ($endTimeInput.val() === '') $endTimeInput.val(DEFAULT_TIME_VALUE);
                    $determineButton.removeClass('disabled');
                  }
                }
              } else if (!minDate) {
                datepicker.minDate = new Date(year, month, date);
                if (!$td.hasClass('prev-month') && !$td.hasClass('next-month')) $td.addClass('start-date in-range');
                if (type === 'datetime-range') {
                  $startDateInput.val($.formatDate(datepicker.minDate));
                  $determineButton.addClass('disabled');
                }
              }
            }
          });
  
          // bind event for month label
          datepicker.$pickerPanel.on('click.datepicker', '.gmi-date-picker__header__label--month', function (e) {
            var $monthLabel = $(this);
            var $yearLabel = $monthLabel.siblings('.gmi-date-picker__header__label--year');
            var $delegateTarget = $(e.delegateTarget);
            var $monthTable = $delegateTarget.find('.gmi-month-table');
            var year = datepicker.yearLabel;
            var month = datepicker.monthLabel;
  
            // add current class for month table td
            if (!$monthTable.find('td').removeClass('current').eq(Number(month)).hasClass('disabled')) {
              $monthTable.find('td').removeClass('current').eq(Number(month)).addClass('current');
            }
  
            // change year label text
            $yearLabel.text(year + ' '+ datepicker.yearSuffix +'');
            // hide month label
            $monthLabel.hide();
            // show month table
            core._setNewMonthDOM($delegateTarget, month);
            // reset picker current view, current view is month view
            datepicker.currentView = 'monthView';
            e.stopPropagation();
          });
  
          // bind event for year label
          datepicker.$pickerPanel.on('click.datepicker', '.gmi-date-picker__header__label--year', function (e) {
            if (datepicker.currentView === 'yearView') {
              return false;
            }
            var $yearLabel = $(this);
            var $monthLabel = $yearLabel.siblings('.gmi-date-picker__header__label--month');
            var $delegateTarget = $(e.delegateTarget);
            var year = datepicker.yearLabel;
  
            // generate new year dom
            core._setNewYearDOM($delegateTarget, year, $yearLabel);
            // hide month label
            $monthLabel.hide();
            // show year table
            datepicker.currentView = 'yearView';
            e.stopPropagation();
          });
  
          // bind events for arrow-down and arrow-up
          datepicker.$pickerPanel.on('click.datepicker', '.gmi-date-picker__header__icon-btn', function (e) {
            var $delegateTarget = $(e.delegateTarget);
            var $button = $(this);
            var action = $button.data('action');
            var currentView = datepicker.currentView;
  
            switch (action) {
              case 'prev':
                core._setPrevButtonAction($delegateTarget, currentView);
                break;
              case 'next':
                core._setNextButtonAction($delegateTarget, currentView);
                break;
              default:
                break;
            }
            e.stopPropagation();
          });
  
          // bind event for link button
          datepicker.$pickerPanel.on('click.datepicker', '.gmi-picker-panel__link-btn:not(.gmi-time-panel__btn)', function (e) {
            var $linkButton = $(this);
            var $delegateTarget = $(e.delegateTarget);
            var role = $linkButton.data('role');
            var type = datepicker.type;
            var dateValue;
            var timeValue;
            var date;
            var time;
            var minDateValue;
            var minTimeValue;
            var minDate;
            var minTime;
            var minValue;
            var maxDateValue;
            var maxTimeValue;
            var maxDate;
            var maxTime;
            var maxValue;
            var value;
  
            switch (role) {
              case 'determine':
                if ($linkButton.hasClass('disabled')) {
                  return false;
                }
                if (type === 'datetime') {
                  dateValue = $delegateTarget.find('.gmi-date-picker--input').val();
                  timeValue = $delegateTarget.find('.gmi-time-picker--input').val();
                  date = $.parseDate(dateValue);
                  time = $.parseDate(timeValue, DEFAULT_TIME_FORMAT);
                  date.setHours(time.getHours(), time.getMinutes(), time.getSeconds(), 0);
                  value = $.formatDate(date, datepicker.format);
                } else if (type === 'datetime-range') {
                  minDateValue = $delegateTarget.find('.gmi-date-picker--input[data-role="range-start"]').val();
                  minTimeValue = $delegateTarget.find('.gmi-time-picker--input[data-role="range-start"]').val();
                  minDate = $.parseDate(minDateValue);
                  minTime = $.parseDate(minTimeValue !== '' ? minTimeValue : DEFAULT_TIME_VALUE, DEFAULT_TIME_FORMAT);
                  minDate.setHours(minTime.getHours(), minTime.getMinutes(), minTime.getSeconds(), 0);
                  minValue = $.formatDate(minDate, datepicker.format);
                  maxDateValue = $delegateTarget.find('.gmi-date-picker--input[data-role="range-end"]').val();
                  maxTimeValue = $delegateTarget.find('.gmi-time-picker--input[data-role="range-end"]').val();
                  maxDate = $.parseDate(maxDateValue);
                  maxTime = $.parseDate(maxTimeValue !== '' ? maxTimeValue : DEFAULT_TIME_VALUE, DEFAULT_TIME_FORMAT);
                  maxDate.setHours(maxTime.getHours(), maxTime.getMinutes(), maxTime.getSeconds(), 0);
                  maxValue = $.formatDate(maxDate, datepicker.format);
                  value = minValue + ' '+ datepicker.rangeSeparator +' ' + maxValue;
                }
                core._setDate(value);
                core._hidePickerPanel();
                break;
              case 'now':
                value = $.formatDate(new Date(), datepicker.format);
                core._setDate(value);
                core._hidePickerPanel();
                break;
              case 'clear':
                core._clear();
                break;
              default:
                break;
            }
            e.stopPropagation();
          });
  
          // bind events for range date button
          datepicker.$pickerPanel.on('click.datepicker', '.gmi-date-range-picker__header__icon-btn', function (e) {
            var $delegateTarget = $(e.delegateTarget);
            var $button = $(this);
            var action = $button.data('action');
  
            core._setRangeDateView($delegateTarget, action);
            e.stopPropagation();
          });
  
          datepicker.$pickerPanel.on('mouseenter.datepicker', 'td:not(.disabled)', function (e) {
            if (datepicker.type !== 'date-range' && datepicker.type !== 'datetime-range') {
              return false;
            }
            var $delegateTarget = $(e.delegateTarget);
            var $td = $(this);
            var year = Number($td.data('year'));
            var month = Number($td.data('month'));
            var date = $td.text() === datepicker.todaySuffix ? new Date().getDate() : Number($td.text());
            var currentDate = new Date(year, month, date);
            var minDate = datepicker.minDate;
            var maxDate = datepicker.maxDate;
  
            if (minDate && !maxDate) {
              datepicker.$minDateTarget = $delegateTarget.find('.gmi-date-table td').filter(function () {
                return !$(this).hasClass('prev-month') && !$(this).hasClass('next-month') &&
                  Number($(this).data('year')) === minDate.getFullYear() &&
                  Number($(this).data('month')) === minDate.getMonth() &&
                  ($(this).text() === datepicker.todaySuffix ? new Date().getDate() : Number($(this).text())) === minDate.getDate();
              });
              if (!datepicker.$minDateTarget.hasClass('start-date')) {
                datepicker.$minDateTarget.addClass('in-range start-date');
              }
              if (currentDate.getTime() >= minDate.getTime()) {
                $delegateTarget.find('.gmi-date-table td').filter(function () {
                  var inRangeYear = Number($(this).data('year'));
                  var inRangeMonth = Number($(this).data('month'));
                  var inRangeDate = $(this).text() === datepicker.todaySuffix ? new Date().getDate() : Number($(this).text());
                  return !$(this).hasClass('prev-month') && !$(this).hasClass('next-month') &&
                    new Date(inRangeYear, inRangeMonth, inRangeDate).getTime() > minDate.getTime() &&
                    new Date(inRangeYear, inRangeMonth, inRangeDate).getTime() < new Date(year, month, date).getTime();
                }).addClass('in-range');
                if (!$td.hasClass('prev-month') && !$td.hasClass('next-month')) {
                  $td.addClass('in-range end-date');
                } else {
                  $delegateTarget.find('.gmi-date-table td').filter(function () {
                    var endDateYear = Number($(this).data('year'));
                    var endDateMonth = Number($(this).data('month'));
                    var endDateDate = $(this).text() === datepicker.todaySuffix ? new Date().getDate() : Number($(this).text());
                    return !$(this).hasClass('prev-month') && !$(this).hasClass('next-month') &&
                      endDateYear === year && endDateMonth === month && endDateDate === date;
                  }).addClass('in-range end-date');
                }
              }
            }
            e.stopPropagation();
          }).on('mouseleave.datepicker', 'td:not(.disabled)', function (e) {
            if (datepicker.type !== 'date-range' && datepicker.type !== 'datetime-range') {
              return false;
            }
            var $delegateTarget = $(e.delegateTarget);
            var $td = $(this);
            var year = Number($td.data('year'));
            var month = Number($td.data('month'));
            var date = $td.text() === datepicker.todaySuffix ? new Date().getDate() : Number($td.text());
            var minDate = datepicker.minDate;
            var maxDate = datepicker.maxDate;
  
            if (minDate && !maxDate) {
              $delegateTarget.find('.gmi-date-table td').filter(function () {
                return !$(this).hasClass('start-date') && !$(this).hasClass('end-date');
              }).removeClass('in-range');
              if (!$td.hasClass('prev-month') && !$td.hasClass('next-month')) {
                $td.removeClass('in-range end-date');
              } else {
                $delegateTarget.find('.gmi-date-table td').filter(function () {
                  var endDateYear = Number($(this).data('year'));
                  var endDateMonth = Number($(this).data('month'));
                  var endDateDate = $(this).text() === datepicker.todaySuffix ? new Date().getDate() : Number($(this).text());
                  return !$(this).hasClass('prev-month') && !$(this).hasClass('next-month') &&
                    endDateYear === year && endDateMonth === month && endDateDate === date;
                }).removeClass('in-range end-date');
              }
            }
            e.stopPropagation();
          });
        },
        _unBindEvent: function () {
          $el.off('focus.datepicker', core._elFocusHandler);
          $el.off('click.datepicker', core._elClickHandler);
          $el.off('change.datepicker', core._elChangeHandler);
          $el.off('keyup.datepicker', core._elKeyUpHandler);
          $el.off('pick.datepicker', datepicker.onChange);
          $el.off('show.datepicker', datepicker.onShow);
          $el.off('hide.datepicker', datepicker.onHide);
        },
        _generateDateDOM: function () {
          var type = datepicker.type;
          var hasTimeClass = type === 'datetime' ? 'has-time' : '';
          var datetimeHeaderStr = type === 'datetime' ? core._generateDatetimeHeader() : '';
          var datetimeFooterStr = type === 'datetime' ? core._generateDatetimeFooter(type) : '';
          var startYear = Math.floor(datepicker.date.getFullYear() / 10) * 10;
          var endYear = Math.floor(datepicker.date.getFullYear() / 10) * 10 + 9;
          var yearLabel = type === 'date' || type === 'month' || type === 'datetime' ? datepicker.date.getFullYear() + ' '+ datepicker.yearSuffix +'' : startYear + ' '+ datepicker.yearSuffix +'' + ' - ' + endYear + ' '+ datepicker.yearSuffix +'';
          var monthLabel = datepicker.monthsShort[datepicker.date.getMonth()];
          var monthDom = type === 'date' || type === 'datetime' ? '<span class="gmi-date-picker__header__label--month">'+ monthLabel +'</span>' : '';
          var dateDomStr = '<div data-role="'+ type +'" class="gmi-picker-panel gmi-date-picker '+ hasTimeClass +'" style="display: none;">' +
            '<div class="gmi-picker-panel__body">'
            + datetimeHeaderStr +
            '<div class="gmi-picker-panel__body__header">' +
            '<span class="gmi-date-picker__header__label--year">'+ yearLabel +'</span>'
            + monthDom +
            '<em data-action="next" class="gmi-date-picker__header__icon-btn gmi-picker-panel__btn--next"></em>' +
            '<em data-action="prev" class="gmi-date-picker__header__icon-btn gmi-picker-panel__btn--prev"></em>' +
            '</div>' +
            '<div class="gmi-picker-panel__body__main">'
            + core._getDateTable(datepicker.date) +
            '' + core._getYearTable(datepicker.date.getFullYear()) +
            '' + core._getMonthTable(datepicker.date.getMonth()) +
            '</div>' +
            '</div>'
            + datetimeFooterStr +
            '</div>';
  
          return dateDomStr;
        },
        _generateRangeDateDOM: function () {
          var date = datepicker.date;
          var firstDate = datepicker.minDate ? datepicker.minDate : date;
          var nextDate = datepicker.minDate ? $.getNextMonth(datepicker.minDate) : $.getNextMonth(date);
          var type = datepicker.type;
          var hasTimeClass = type === 'datetime-range' ? 'has-time' : '';
          var rangeDatetimeHeaderStr = type === 'datetime-range' ? core._generateRangeDatetimeHeader() : '';
          var rangeDatetimeFooterStr = type === 'datetime-range' ? core._generateDatetimeFooter(type) : '';
          var rangeDateDomStr = '<div data-role="'+ type +'" class="gmi-picker-panel gmi-date-range-picker '+ hasTimeClass +'" style="display: none;">'
            + rangeDatetimeHeaderStr +
            '<div class="gmi-picker-panel__body gmi-date-range-picker__body">' +
            '<div class="gmi-picker-panel__body__main f-lt">' +
            '<div class="gmi-date-range-picker__body__header">' +
            '<p>'+ firstDate.getFullYear() +' '+ datepicker.yearSuffix +' '+ datepicker.monthsShort[firstDate.getMonth()] +'</p>' +
            '<em data-action="prev-year" class="gmi-date-range-picker__header__icon-btn gmi-date-range-picker__btn--prev gmi-date-range-picker__btn--prev-year"></em>' +
            '<em data-action="prev-month" class="gmi-date-range-picker__header__icon-btn gmi-date-range-picker__btn--prev gmi-date-range-picker__btn--prev-month"></em>' +
            '</div>'
            + core._getDateTable(firstDate) +
            '</div>' +
            '<div class="gmi-picker-panel__body__main f-rt">' +
            '<div class="gmi-date-range-picker__body__header">' +
            '<p>'+ nextDate.getFullYear() +' '+ datepicker.yearSuffix +' '+ datepicker.monthsShort[nextDate.getMonth()] +'</p>' +
            '<em data-action="next-year" class="gmi-date-range-picker__header__icon-btn gmi-date-range-picker__btn--next gmi-date-range-picker__btn--next-year"></em>' +
            '<em data-action="next-month" class="gmi-date-range-picker__header__icon-btn gmi-date-range-picker__btn--next gmi-date-range-picker__btn--next-month"></em>' +
            '</div>'
            + core._getDateTable(nextDate) +
            '</div>' +
            '</div>'
            + rangeDatetimeFooterStr +
            '</div>';
          return rangeDateDomStr;
        },
        _generateDatetimeHeader: function () {
          var datetimeHeaderStr = '<ul class="gmi-picker-panel__body__header--time">' +
            '<li class="gmi-picker-panel__body__header--time__wrapper">' +
            '<div class="gmi-input">' +
            '<input data-role="date" type="text" readonly class="gmi-input__inner gmi-date-picker--input" placeholder="'+ datepicker.dateInputPlaceholder +'">' +
            '</div>' +
            '</li>' +
            '<li class="gmi-picker-panel__body__header--time__wrapper">' +
            '<div data-role="date" class="gmi-input gmi-time-picker--wrapper">' +
            '<input data-role="date" type="text" class="gmi-input__inner gmi-time-picker--input" placeholder="'+ datepicker.dateTimeInputPlaceholder +'">'
            + core._generateTimePickerDOM('date') +
            '</div>' +
            '</li>' +
            '</ul>';
  
          return datetimeHeaderStr;
        },
        _generateRangeDatetimeHeader: function () {
          var rangeDatetimeHeaderStr = '<ul class="gmi-picker-panel__body__header--time">' +
            '<li class="gmi-picker-panel__body__header--time__wrapper gmi-date-range-picker__header--time__wrapper">' +
            '<div class="gmi-input">' +
            '<input data-role="range-start" type="text" readonly class="gmi-input__inner gmi-date-picker--input" placeholder="'+ datepicker.rangeStartInputPlaceholder +'">' +
            '</div>' +
            '<div data-role="range-start" class="gmi-input gmi-time-picker--wrapper">' +
            '<input data-role="range-start" type="text" class="gmi-input__inner gmi-time-picker--input" placeholder="'+ datepicker.rangeStartTimeInputPlaceholder +'">'
            + core._generateTimePickerDOM('range-start') +
            '</div>' +
            '</li>' +
            '<li class="gmi-picker-panel__body__header--time__wrapper gmi-date-range-picker__header--time__wrapper">' +
            '<div class="gmi-input">' +
            '<input data-role="range-end" type="text" readonly class="gmi-input__inner gmi-date-picker--input" placeholder="'+ datepicker.rangeEndPlaceholder +'">' +
            '</div>' +
            '<div data-role="range-end" class="gmi-input gmi-time-picker--wrapper">' +
            '<input data-role="range-end" type="text" class="gmi-input__inner gmi-time-picker--input" placeholder="'+ datepicker.rangeEndTimeInputPlaceholder +'">'
            + core._generateTimePickerDOM('range-end') +
            '</div>' +
            '</li>' +
            '</ul>';
  
          return rangeDatetimeHeaderStr;
        },
        _generateDatetimeFooter: function (type) {
          var buttonText = type === 'datetime-range' ? datepicker.clearButton : datepicker.nowDateButton;
          var buttonClass = type === 'datetime-range' ? 'gmi-picker-panel__link-btn--clear' : 'gmi-picker-panel__link-btn--now';
          var buttonRole = type === 'datetime-range' ? 'clear' : 'now';
          var disabledClass = type === 'datetime-range' && datepicker.minDate && datepicker.maxDate ? '' : type === 'datetime' ? '' : 'disabled';
          var datetimeFooterStr = '<div class="gmi-picker-panel__footer">' +
            '<a href="JavaScript:" data-role="'+ buttonRole +'" class="gmi-picker-panel__link-btn '+ buttonClass +'">'+ buttonText +'</a>' +
            '<a href="JavaScript:" data-role="determine" class="gmi-picker-panel__link-btn gmi-picker-panel__link-btn--determine '+ disabledClass +'">'+ datepicker.confirmDateButton +'</a>' +
            '</div>';
          return datetimeFooterStr;
        },
        _generateTimePickerDOM: function (role) {
          var timePickerStr = '<div data-role="'+ role +'" class="gmi-time-panel gmi-time-picker" style="display: none;">' +
            '<div class="gmi-time-panel__body">' +
            '<div class="gmi-time-panel__body__item">'
            + core._getTimeSpinner('hour') +
            '</div>' +
            '<div class="gmi-time-panel__body__item">'
            + core._getTimeSpinner('min') +
            '</div>' +
            '<div class="gmi-time-panel__body__item">'
            + core._getTimeSpinner('sec') +
            '</div>' +
            '</div>' +
            '<div class="gmi-picker-panel__footer gmi-time-panel__footer">' +
            '<a href="JavaScript:" data-role="cancel" class="gmi-picker-panel__link-btn gmi-time-panel__btn gmi-picker-panel__link-btn--default">'+ datepicker.cancelTimeButton +'</a>' +
            '<a href="JavaScript:" data-role="confirm" class="gmi-picker-panel__link-btn gmi-time-panel__btn gmi-picker-panel__link-btn--primary">'+ datepicker.confirmDateButton +'</a>' +
            '</div>' +
            '</div>';
          return timePickerStr;
        },
        _getDateRows: function (nowDate) {
          var tableRows = [ [], [], [], [], [], [] ];
          var year = nowDate.getFullYear();
          var month = nowDate.getMonth();
  
          var date = new Date(year, month, 1);
          var day = $.getFirstDayOfMonth(date);// day of first day
          var dateCountOfMonth = $.getTotalDayCountOfMonth(date.getFullYear(), date.getMonth());
          var dateCountOfLastMonth = $.getTotalDayCountOfMonth(date.getFullYear(), (date.getMonth() === 0 ? 11 : date.getMonth() - 1));
  
          day = (day === 0) ? 7 : day;
          var offset = -1 * datepicker.weekStart;
          var rows = tableRows;
          var count = 1;
          var firstDayPosition;
          var startDate = $.getStartDateOfMonth(year, month);
          var now = $.clearHours(new Date()); // get now time
  
          for (var i = 0; i < 6; i++) {
            var row = rows[i];
            for (var j = 0; j < 7; j++) {
              var cell = row[j];
              if (!cell) {
                cell = { row: i, column: j, type: 'normal', year: date.getFullYear(), month: date.getMonth(), inRange: false, start: false, end: false}; // init cell
              }
              cell.type = 'normal';
              var index = i * 7 + j;
              var time = startDate.getTime() + DAY_DURATION * (index - offset);
              cell.inRange = time >= $.clearHours(datepicker.minDate) && time <= $.clearHours(datepicker.maxDate);
              cell.start = datepicker.minDate && time === $.clearHours(datepicker.minDate);
              cell.end = datepicker.maxDate && time === $.clearHours(datepicker.maxDate);
              cell.disabled = false;
              if (datepicker.startDate && isDate(datepicker.startDate) && time < $.clearHours(datepicker.startDate)) {
                cell.disabled = true;
              }
              if (datepicker.endDate && isDate(datepicker.endDate) && time > $.clearHours(datepicker.endDate)) {
                cell.disabled = true;
              }
              var isToday = time === now;
              if (isToday) {
                cell.type = 'today';
              }
  
              if (i >= 0 && i <= 1) {
                if (j + i * 7 >= (day + offset)) {
                  cell.text = count++;
                  if (count === 2) {
                    firstDayPosition = i * 7 + j;
                  }
                  cell.year = date.getFullYear();
                  cell.month = date.getMonth();
                } else {
                  cell.text = dateCountOfLastMonth - (day + offset - j % 7) + 1 + i * 7;
                  cell.type = 'prev-month';
                  cell.year = date.getMonth() === 0 ? date.getFullYear() - 1 : date.getFullYear();
                  cell.month = date.getMonth() === 0 ? 11 : date.getMonth() - 1;
                }
              } else {
                if (count <= dateCountOfMonth) {
                  cell.text = count++;
                  if (count === 2) {
                    firstDayPosition = i * 7 + j;
                  }
                  cell.year = date.getFullYear();
                  cell.month = date.getMonth();
                } else {
                  cell.text = count++ - dateCountOfMonth;
                  cell.type = 'next-month';
                  cell.year = date.getMonth() === 11 ? date.getFullYear() + 1 : date.getFullYear();
                  cell.month = date.getMonth() === 11 ? 0 : date.getMonth() + 1;
                }
              }
              row[j] = $.extend({}, cell);
            }
          }
          rows.firstDayPosition = firstDayPosition;
          return rows;
        },
        _getWeekDayRows: function () {
          var weekDayRows = datepicker.daysMin;
          if (datepicker.weekStart) weekDayRows = weekDayRows.slice(datepicker.weekStart).concat(weekDayRows.slice(0,datepicker.weekStart));
          return weekDayRows;
        },
        _getYearRows: function (year) {
          var startYear = Math.floor(year / 10) * 10;
          var yearRows = [[], [], []];
  
          for (var i = 0; i < 3; i++) {
            var yearRow = yearRows[i];
            for (var j = 0; j < 4; j++) {
              var index = i * 4 + j;
              if (index > 9) {
                break;
              }
              yearRow[j] = startYear + index;
            }
          }
  
          return yearRows;
        },
        _getMonthRows: function () {
          var monthsRows = [[], [], []];
          var monthsArray = datepicker.monthsShort;
          var index = 0;
          for (var i = 0; i < monthsArray.length; i++) {
            var monthCell = monthsArray[i];
            if (i % 4 === 0 && i !== 0) {
              index++;
            }
            monthsRows[index].push(monthCell);
          }
          return monthsRows;
        },
        _getTimeSpinnerData: function (type) {
          var timeArray = [];
          switch (type) {
            case 'hour':
              for (var h = 0; h <= 23; h++) {
                timeArray.push(h);
              }
              break;
            case 'min':
              for (var m = 0; m <= 59; m++) {
                timeArray.push(m);
              }
              break;
            case 'sec':
              for (var s = 0; s <= 59; s++) {
                timeArray.push(s);
              }
              break;
            default:
              break;
          }
          return timeArray;
        },
        _getDateTable: function (nowDate) {
          var rows = core._getDateRows(nowDate);
          var weekDayRows = core._getWeekDayRows();
          var type = datepicker.type;
          var showStyle = type === 'date-range' || type === 'datetime-range' ? '' : 'style="display:none;"';
          var tableStr = '<table cellspacing="0" cellpadding="0" class="gmi-date-table" '+ showStyle +'><tbody>';
  
          tableStr += '<tr>';
          for (var w = 0; w < weekDayRows.length; w++) {
            tableStr += '<th>'+ weekDayRows[w] +'</th>';
          }
          tableStr += '</tr>';
  
          for (var i = 0; i < rows.length; i++) {
            var row = rows[i];
            tableStr += '<tr class="gmi-date-table__row">';
            for (var j = 0; j < row.length; j++) {
              var cell = row[j];
              var classes = core._getCellClasses(cell);
              var text = cell.type === 'today' ? datepicker.todaySuffix : cell.text;
              tableStr += '<td data-year="'+ cell.year +'" data-month="'+ cell.month +'" class="'+ classes +'">'+ text +'</td>';
            }
            tableStr += '</tr>';
          }
  
          tableStr += '</tbody></table>';
          return tableStr;
        },
        _getYearTable: function (year) {
          var yearRows = core._getYearRows(year);
          var startYear = datepicker.startDate && isDate(datepicker.startDate) ? datepicker.startDate.getFullYear() : null;
          var endYear = datepicker.endDate && isDate(datepicker.endDate) ? datepicker.endDate.getFullYear() : null;
          var tableStr = '<table class="gmi-year-table" style="display:none;"><tbody>';
  
          for (var i = 0; i < yearRows.length; i++) {
            var yearRow = yearRows[i];
            tableStr += '<tr>';
            for (var j = 0; j < yearRow.length; j++) {
              var yearCell = yearRow[j];
              var isDisabled = false;
              if (startYear && yearCell < startYear) {
                isDisabled = true;
              }
              if (endYear && yearCell > endYear) {
                isDisabled = true;
              }
              var disabledClass = isDisabled ? 'disabled' : '';
              var currentClass = year === yearCell && !isDisabled ? 'current' : '';
              tableStr += '<td class="available '+ currentClass +' '+ disabledClass +'"><span class="cell">'+ yearCell +'</span></td>';
            }
            tableStr += '</tr>';
          }
  
          tableStr += '</tbody></table>';
          return tableStr;
        },
        _getMonthTable: function (month) {
          var monthRows = core._getMonthRows();
          var startYear = datepicker.startDate && isDate(datepicker.startDate) ? datepicker.startDate.getFullYear() : null;
          var startMonth = datepicker.startDate && isDate(datepicker.startDate) ? datepicker.startDate.getMonth() : null;
          var startTime = startYear && startMonth ? new Date(startYear, startMonth, 1).getTime() : null;
          var endYear = datepicker.endDate && isDate(datepicker.endDate) ? datepicker.endDate.getFullYear() : null;
          var endMonth = datepicker.endDate && isDate(datepicker.endDate) ? datepicker.endDate.getMonth() : null;
          var endTime = endYear && endMonth ? new Date(endYear, endMonth, 1).getTime() : null;
          var tableStr = '<table class="gmi-month-table" style="display:none;"><tbody>';
  
          for (var i = 0; i < monthRows.length; i++) {
            var monthRow = monthRows[i];
            tableStr += '<tr>';
            for (var j = 0; j < monthRow.length; j++) {
              var monthCell = monthRow[j];
              var isDisabled = false;
              if (startTime && (new Date(datepicker.yearLabel, i * 4 + j, 1) < startTime)) {
                isDisabled = true;
              }
  
              if (endTime && (new Date(datepicker.yearLabel, i * 4 + j, 1) > endTime)) {
                isDisabled = true;
              }
              var disabledClass = isDisabled ? 'disabled' : '';
              var currentClass = month === (i * 4 + j) && !isDisabled ? 'current' : '';
              tableStr += '<td data-year="'+ datepicker.date.getFullYear() +'" data-month="'+ (i * 4 + j) +'" class="'+ currentClass +' '+ disabledClass +'"><span class="cell">'+ monthCell +'</span></td>';
            }
            tableStr += '</tr>';
          }
          tableStr += '</tbody></table>';
          return tableStr;
        },
        _getTimeSpinner: function (type) {
          var data = core._getTimeSpinnerData(type);
          var timeSpinnerStr = '<ul data-role="'+ type +'" class="gmi-time-panel__body__item--spinner">';
          for (var i = 0; i < data.length; i++) {
            timeSpinnerStr += '<li class="gmi-time-panel__body__item--spinner__item">'+ data[i] +'</li>';
          }
          timeSpinnerStr += '</ul>';
          return timeSpinnerStr;
        },
        _echoDateOrTimeIntoInput: function () {
          var type = datepicker.type;
          if (type === 'datetime') {
            var $dateInput = datepicker.$pickerPanel.find('.gmi-date-picker--input');
            var $timeInput = datepicker.$pickerPanel.find('.gmi-time-picker--input');
            var date = $.formatDate(datepicker.date);
            var time = $.formatDate(datepicker.date, DEFAULT_TIME_FORMAT);
            $dateInput.val(date);
            $timeInput.val(time);
          } else if (type === 'datetime-range') {
            var $startDateInput = datepicker.$pickerPanel.find('.gmi-date-picker--input').filter('[data-role="range-start"]');
            var $endDateInput = datepicker.$pickerPanel.find('.gmi-date-picker--input').filter('[data-role="range-end"]');
            var $startTimeInput = datepicker.$pickerPanel.find('.gmi-time-picker--input').filter('[data-role="range-start"]');
            var $endTimeInput = datepicker.$pickerPanel.find('.gmi-time-picker--input').filter('[data-role="range-end"]');
            var startDate;
            var startTime;
            var endDate;
            var endTime;
            if (datepicker.minDate && datepicker.maxDate) {
              startDate = $.formatDate(datepicker.minDate);
              startTime = $.formatDate(datepicker.minDate, DEFAULT_TIME_FORMAT);
              endDate = $.formatDate(datepicker.maxDate);
              endTime = $.formatDate(datepicker.maxDate, DEFAULT_TIME_FORMAT);
              $startDateInput.val(startDate);
              $startTimeInput.val(startTime);
              $endDateInput.val(endDate);
              $endTimeInput.val(endTime);
            } else {
              startDate = $.formatDate(datepicker.date);
              startTime = $.formatDate(datepicker.date, DEFAULT_TIME_FORMAT);
              endDate = $.formatDate(datepicker.date);
              endTime = $.formatDate(datepicker.date, DEFAULT_TIME_FORMAT);
              $startDateInput.val(startDate);
              $startTimeInput.val(startTime);
              $endDateInput.val(endDate);
              $endTimeInput.val(endTime);
            }
          }
        },
        _echoTimeIntoSpinner: function (time, rangeType) {
          var type = datepicker.type;
  
          if (type === 'datetime') {
            var hour = time && typeof time === 'object' ? time.hour : datepicker.date.getHours();
            var min = time && typeof time === 'object' ? time.min : datepicker.date.getMinutes();
            var sec = time && typeof time === 'object' ? time.sec : datepicker.date.getSeconds();
            var $hourSpinner = datepicker.$timePanel.find('.gmi-time-panel__body__item--spinner[data-role=hour]');
            var $minSpinner = datepicker.$timePanel.find('.gmi-time-panel__body__item--spinner[data-role=min]');
            var $secSpinner = datepicker.$timePanel.find('.gmi-time-panel__body__item--spinner[data-role=sec]');
            var $curHourItem = $hourSpinner.children('li').filter(function () {
              return Number($(this).text()) === hour;
            });
            var $curMinItem = $minSpinner.children('li').filter(function () {
              return Number($(this).text()) === min;
            });
            var $curSecItem = $secSpinner.children('li').filter(function () {
              return Number($(this).text()) === sec;
            });
            var curTimeItemHeight = $curHourItem.outerHeight();
  
            datepicker.$timePanel.show();
            if ($curHourItem.length > 0 && $curMinItem.length > 0 && $curSecItem.length > 0) {
              $curHourItem.addClass('active').siblings().removeClass('active');
              $curMinItem.addClass('active').siblings().removeClass('active');
              $curSecItem.addClass('active').siblings().removeClass('active');
              $hourSpinner.parent().scrollTop(hour * curTimeItemHeight);
              $minSpinner.parent().scrollTop(min * curTimeItemHeight);
              $secSpinner.parent().scrollTop(sec * curTimeItemHeight);
            } else {
              return;
            }
          } else if (type === 'datetime-range') {
            if (rangeType === 'range-start') {
              var minHour = time && typeof time === 'object' ? time.minHour : datepicker.minDate ? datepicker.minDate.getHours() : datepicker.date.getHours();
              var minMin = time && typeof time === 'object' ? time.minMin : datepicker.minDate ? datepicker.minDate.getMinutes() : datepicker.date.getMinutes();
              var minSec = time && typeof time === 'object' ? time.minSec : datepicker.minDate ? datepicker.minDate.getSeconds() : datepicker.date.getSeconds();
              var $minHourSpinner = datepicker.$timePanel.filter('[data-role="range-start"]').find('.gmi-time-panel__body__item--spinner[data-role=hour]');
              var $minMinSpinner = datepicker.$timePanel.filter('[data-role="range-start"]').find('.gmi-time-panel__body__item--spinner[data-role=min]');
              var $minSecSpinner = datepicker.$timePanel.filter('[data-role="range-start"]').find('.gmi-time-panel__body__item--spinner[data-role=sec]');
              var $curMinHourItem = $minHourSpinner.children('li').filter(function () {
                return Number($(this).text()) === minHour;
              });
              var $curMinMinItem = $minMinSpinner.children('li').filter(function () {
                return Number($(this).text()) === minMin;
              });
              var $curMinSecItem = $minSecSpinner.children('li').filter(function () {
                return Number($(this).text()) === minSec;
              });
              var curMinTimeItemHeight = $curMinHourItem.outerHeight();
  
              datepicker.$timePanel.filter('[data-role="range-start"]').show();
  
              if ($curMinHourItem.length > 0 && $curMinMinItem.length > 0 && $curMinSecItem.length > 0) {
                $curMinHourItem.addClass('active').siblings().removeClass('active');
                $curMinMinItem.addClass('active').siblings().removeClass('active');
                $curMinSecItem.addClass('active').siblings().removeClass('active');
                $minHourSpinner.parent().scrollTop(minHour * curMinTimeItemHeight);
                $minMinSpinner.parent().scrollTop(minMin * curMinTimeItemHeight);
                $minSecSpinner.parent().scrollTop(minSec * curMinTimeItemHeight);
              } else {
                return;
              }
            } else if (rangeType === 'range-end') {
              var maxHour = time && typeof time === 'object' ? time.maxHour : datepicker.maxDate ? datepicker.maxDate.getHours() : datepicker.date.getHours();
              var maxMin = time && typeof time === 'object' ? time.maxMin : datepicker.maxDate ? datepicker.maxDate.getMinutes() : datepicker.date.getMinutes();
              var maxSec = time && typeof time === 'object' ? time.maxSec : datepicker.maxDate ? datepicker.maxDate.getSeconds() : datepicker.date.getSeconds();
              var $maxHourSpinner = datepicker.$timePanel.filter('[data-role="range-end"]').find('.gmi-time-panel__body__item--spinner[data-role=hour]');
              var $maxMinSpinner = datepicker.$timePanel.filter('[data-role="range-end"]').find('.gmi-time-panel__body__item--spinner[data-role=min]');
              var $maxSecSpinner = datepicker.$timePanel.filter('[data-role="range-end"]').find('.gmi-time-panel__body__item--spinner[data-role=sec]');
              var $curMaxHourItem = $maxHourSpinner.children('li').filter(function () {
                return Number($(this).text()) === maxHour;
              });
              var $curMaxMinItem = $maxMinSpinner.children('li').filter(function () {
                return Number($(this).text()) === maxMin;
              });
              var $curMaxSecItem = $maxSecSpinner.children('li').filter(function () {
                return Number($(this).text()) === maxSec;
              });
  
              var curMaxTimeItemHeight = $curMaxHourItem.outerHeight();
  
              datepicker.$timePanel.filter('[data-role="range-end"]').show();
  
              if ($curMaxHourItem.length > 0 && $curMaxMinItem.length > 0 && $curMaxSecItem.length > 0) {
                $curMaxHourItem.addClass('active').siblings().removeClass('active');
                $curMaxMinItem.addClass('active').siblings().removeClass('active');
                $curMaxSecItem.addClass('active').siblings().removeClass('active');
                $maxHourSpinner.parent().scrollTop(maxHour * curMaxTimeItemHeight);
                $maxMinSpinner.parent().scrollTop(maxMin * curMaxTimeItemHeight);
                $maxSecSpinner.parent().scrollTop(maxSec * curMaxTimeItemHeight);
              } else {
                return;
              }
            }
          }
        },
        _getCellClasses: function (cell) {
          var type = datepicker.type;
          var classes = [];
  
          // push class 'available' or 'today'
          if (cell.type === 'normal' || cell.type === 'today' && !cell.disabled) {
            classes.push('available');
            if (cell.type === 'today') {
              classes.push('today');
            }
          } else {
            classes.push(cell.type);
          }
  
          // push class 'current'
          if ((type === 'date' || type === 'datetime') && ((cell.type === 'normal' || cell.type === 'today') &&
            Number(cell.year) === datepicker.date.getFullYear() && Number(cell.month) === datepicker.date.getMonth() &&
            Number(cell.text) === datepicker.date.getDate())) {
            classes.push('current');
          }
  
          // push class 'in-range' or 'start-date' or 'end-date'
          if (cell.inRange && ((type === 'date-range' || type === 'datetime-range') && (cell.type === 'normal' || cell.type === 'today'))) {
            classes.push('in-range');
  
            if (cell.start) {
              classes.push('start-date');
            }
  
            if (cell.end) {
              classes.push('end-date');
            }
          }
  
          if (cell.disabled) {
            classes.push('disabled');
          }
  
          return classes.join(' ');
        },
        _setDate: function (date) {
          var type = datepicker.type;
          var format = datepicker.format;
          if (date) {
            if (isString(date)) {
              if (type === 'date-range' || type === 'datetime-range') {
                if (date !== datepicker.value) {
                  datepicker.date = date.split(' '+ datepicker.rangeSeparator +' ');
                  var minDate = $.parseDate(datepicker.date[0], format);
                  var maxDate = $.parseDate(datepicker.date[1], format);
                  var startDate = datepicker.startDate && isDate(datepicker.startDate) ? new Date($.clearHours(datepicker.startDate)) : null;
                  var endDate = datepicker.endDate && isDate(datepicker.endDate) ? new Date($.clearHours(datepicker.endDate)) : null;
                  if (startDate && (minDate.getTime() < startDate.getTime() || maxDate.getTime() < startDate.getTime())) {
                    date = '';
                  }
                  if (endDate && (minDate.getTime() > endDate.getTime() || maxDate.getTime() > endDate.getTime())) {
                    date = '';
                  }
                  if (date && date !== '') {
                    if (maxDate.getTime() >= minDate.getTime()) {
                      // EVENT_PICK triggered
                      core._trigger('pick.datepicker', {newDate: date, oldDate: datepicker.value});
                      datepicker.value = date;
                      datepicker.minDate = minDate;
                      datepicker.maxDate = maxDate;
                      core._setRangeDateView(datepicker.$pickerPanel);
                      if (type === 'datetime-range') {
                        datepicker.$pickerPanel.find('.gmi-date-picker--input[data-role="range-start"]')
                          .val($.formatDate(minDate));
                        datepicker.$pickerPanel.find('.gmi-date-picker--input[data-role="range-end"]')
                          .val($.formatDate(maxDate));
                        datepicker.$pickerPanel.find('.gmi-time-picker--input[data-role="range-start"]')
                          .val($.formatDate(minDate, DEFAULT_TIME_FORMAT));
                        datepicker.$pickerPanel.find('.gmi-time-picker--input[data-role="range-end"]')
                          .val($.formatDate(maxDate, DEFAULT_TIME_FORMAT));
                        datepicker.$pickerPanel.find('.gmi-picker-panel__link-btn--determine').removeClass('disabled');
                      }
                      $el.val(date);
                    } else {
                      throw new Error('The maximum date must be greater than or equal to the minimum date');
                    }
                  } else {
                    datepicker.date = new Date();
                    datepicker.limitStartDate = new Date();
                  }
                }
              } else if (type === 'date' || type === 'datetime') {
                var oldDate = datepicker.date;
                var oldDateTime = oldDate.getTime();
                var newDate = $.parseDate(date, format);
                var newDateTime = newDate.getTime();
                if (oldDateTime !== newDateTime) {
                  // EVENT_PICK triggered
                  core._trigger('pick.datepicker', {newDate: date, oldDate: $.formatDate(datepicker.date, format)});
                  datepicker.value = date;
                  datepicker.date = newDate;
                  datepicker.yearLabel = newDate.getFullYear();
                  datepicker.monthLabel = newDate.getMonth();
                  core._setNewDateDOM(datepicker.$pickerPanel, newDate.getFullYear(), newDate.getMonth(), newDate.getDate());
                  if (type === 'datetime') {
                    datepicker.$pickerPanel.find('.gmi-date-picker--input').val($.formatDate(newDate));
                    datepicker.$pickerPanel.find('.gmi-time-picker--input').val($.formatDate(newDate, DEFAULT_TIME_FORMAT));
                    datepicker.$pickerPanel.find('.gmi-picker-panel__link-btn--determine')
                      .removeClass('disabled');
                  }
                  $el.val(date);
                }
              } else if (type === 'month') {
                var oldMonth = $.formatDate(datepicker.date, format);
                var newMonth = date;
                var oldMonthTime = datepicker.date.getTime();
                var newMonthTime = new Date($.parseDate(newMonth, format).getFullYear(), $.parseDate(newMonth, format).getMonth(), 1).getTime();
                if (newMonthTime !== oldMonthTime) {
                  // EVENT_PICK triggered
                  core._trigger('pick.datepicker', {newDate: newMonth, oldDate: oldMonth});
                  if ($.parseDate(newMonth, format).getFullYear() !== datepicker.date.getFullYear()) {
                    datepicker.$pickerPanel.find('.gmi-date-picker__header__label--year')
                      .text($.parseDate(newMonth, format).getFullYear() + ' '+ datepicker.yearSuffix +'');
                    datepicker.$pickerPanel.find('.gmi-month-table td')
                      .attr('data-year', $.parseDate(newMonth, format).getFullYear())
                      .data('year', $.parseDate(newMonth, format).getFullYear());
                  }
                  datepicker.value = date;
                  datepicker.date = new Date($.parseDate(newMonth, format).getFullYear(), $.parseDate(newMonth, format).getMonth(), 1);
                  datepicker.yearLabel = $.parseDate(newMonth, format).getFullYear();
                  datepicker.monthLabel = $.parseDate(newMonth, format).getMonth();
                  core._setNewMonthDOM(datepicker.$pickerPanel, $.parseDate(newMonth, format).getMonth());
                  $el.val(newMonth);
                }
              } else if (type === 'year') {
                var oldYear = datepicker.date.getFullYear();
                var oldStartYear = Math.floor(oldYear / 10) * 10;
                var newYear = Number(date);
                var newStartYear = Math.floor(newYear / 10) * 10;
                if (oldYear !== newYear) {
                  // EVENT_PICK triggered
                  core._trigger('pick.datepicker', {newDate: date, oldDate: oldYear});
                  datepicker.value = date;
                  datepicker.date = new Date(newYear, 0, 1);
                  datepicker.yearLabel = newYear;
  
                  if (oldStartYear === newStartYear) {
                    datepicker.$pickerPanel.find('.gmi-year-table td').removeClass('current').filter(function () {
                      return Number($(this).text()) === newYear;
                    }).addClass('current');
                  } else {
                    core._setNewYearDOM(datepicker.$pickerPanel, newYear);
                  }
                  $el.val(newYear);
                }
              }
            } else if (isDate(date)) {
              if (type === 'date-range' || type === 'datetime-range') return;
              var formatDate = $.formatDate(date, format);
              core._setDate(formatDate);
            }
          } else {
            datepicker.date = new Date();
            datepicker.limitStartDate = new Date();
            if (datepicker.type !== 'date-range' || datepicker.type !== 'datetime-range') {
              datepicker.yearLabel = datepicker.date.getFullYear();
              datepicker.monthLabel = datepicker.date.getMonth();
            }
          }
        },
        _setPrevButtonAction: function ($delegateTarget, currentView) {
          var $yearLabel;
          var year;
          var month;
          switch (currentView) {
            case 'dateView':
              month = datepicker.monthLabel - 1 < 0 ? 11 : datepicker.monthLabel - 1;
              year = datepicker.monthLabel - 1 < 0 ? datepicker.yearLabel - 1 : datepicker.yearLabel;
              core._setNewDateDOM($delegateTarget, year, month);
              datepicker.yearLabel = year;
              datepicker.monthLabel = month;
              break;
            case 'monthView':
              $yearLabel = $delegateTarget.find('.gmi-date-picker__header__label--year');
              year = datepicker.yearLabel - 1;
              month = datepicker.monthLabel;
              $yearLabel.text(year + ' '+ datepicker.yearSuffix +'');
              datepicker.yearLabel = year;
              core._setNewMonthDOM($delegateTarget, month);
              break;
            case 'yearView':
              year = datepicker.yearLabel - 10;
              core._setNewYearDOM($delegateTarget, year);
              datepicker.yearLabel = year;
              break;
            default:
              break;
          }
        },
        _setNextButtonAction: function ($delegateTarget, currentView) {
          var $yearLabel;
          var year;
          var month;
          switch (currentView) {
            case 'dateView':
              month = datepicker.monthLabel + 1 > 11 ? 0 : datepicker.monthLabel + 1;
              year = datepicker.monthLabel + 1 > 11 ? datepicker.yearLabel + 1 : datepicker.yearLabel;
              core._setNewDateDOM($delegateTarget, year, month);
              datepicker.yearLabel = year;
              datepicker.monthLabel = month;
              break;
            case 'monthView':
              $yearLabel = $delegateTarget.find('.gmi-date-picker__header__label--year');
              year = datepicker.yearLabel + 1;
              month = datepicker.monthLabel;
              $yearLabel.text(year + ' '+ datepicker.yearSuffix +'');
              datepicker.yearLabel = year;
              core._setNewMonthDOM($delegateTarget, month);
              break;
            case 'yearView':
              year = datepicker.yearLabel + 10;
              core._setNewYearDOM($delegateTarget, year);
              datepicker.yearLabel = year;
              break;
            default:
              break;
          }
        },
        _setNewDateDOM: function ($delegateTarget, year, month, date) {
          var newDateTableStr = core._getDateTable(new Date(year, month, date ? date : 1));
          var $tableWrapper = $delegateTarget.find('.gmi-picker-panel__body__main');
          var $yearLabel = $delegateTarget.find('.gmi-date-picker__header__label--year');
          var $monthLabel = $delegateTarget.find('.gmi-date-picker__header__label--month');
          $yearLabel.text(year + ' '+ datepicker.yearSuffix +'');
          $monthLabel.show().text(datepicker.monthsShort[month]);
          $delegateTarget.find('.gmi-date-table').remove();
          $(newDateTableStr).appendTo($tableWrapper).show().find('td').removeClass('current').filter(function () {
            return (!$(this).hasClass('prev-month') && !$(this).hasClass('next-month')) && year === datepicker.date.getFullYear() &&
              month === datepicker.date.getMonth() &&
              ($(this).text() === datepicker.todaySuffix ? new Date().getDate() : Number($(this).text()))  === datepicker.date.getDate();
          }).addClass('current');
        },
        _setNewYearDOM: function ($delegateTarget, year, $yearLabel) {
          var $tableWrapper = $delegateTarget.find('.gmi-picker-panel__body__main');
          var $yearLabel = $yearLabel && $yearLabel.length > 0 ? $yearLabel : $delegateTarget.find('.gmi-date-picker__header__label--year');
          var $yearTable = $delegateTarget.find('.gmi-year-table');
          var startYear = Math.floor(year / 10) * 10;
          var endYear = startYear + 9;
          var newYearTableStr = core._getYearTable(year);
  
          $yearTable.remove();
          // add current class for year table td
          $(newYearTableStr).appendTo($tableWrapper).show().find('td').removeClass('current').filter(function () {
            return !$(this).hasClass('disabled') && Number($(this).text()) === year;
          }).addClass('current');
          $delegateTarget.find('.gmi-year-table').siblings('table').hide();
          // show year label text
          $yearLabel.text(startYear + ' '+ datepicker.yearSuffix +' - ' + endYear + ' '+ datepicker.yearSuffix +'');
        },
        _setNewMonthDOM: function ($delegateTarget, month) {
          var $tableWrapper = $delegateTarget.find('.gmi-picker-panel__body__main');
          var $monthTable = $delegateTarget.find('.gmi-month-table');
          var newMonthTableStr = core._getMonthTable(month);
          $monthTable.remove();
          $(newMonthTableStr).appendTo($tableWrapper).show();
          $delegateTarget.find('.gmi-month-table').siblings('table').hide();
        },
        _setYearView: function ($currentTd) {
          var $yearTable = datepicker.$pickerPanel.find('.gmi-year-table');
          var $yearLabel = datepicker.$pickerPanel.find('.gmi-date-picker__header__label--year');
          var type = datepicker.type;
          var year = Number($currentTd.text());
  
          $yearTable.find('td').removeClass('current');
          $currentTd.addClass('current');
          if (type === 'date' || type === 'datetime' || type === 'month') {
            $yearTable.hide();
            $yearLabel.text(year + ' '+ datepicker.yearSuffix +'');
            datepicker.yearLabel = year;
            core._setNewMonthDOM(datepicker.$pickerPanel, datepicker.date.getMonth());
            datepicker.currentView = 'monthView';
          } else if (type === 'year') {
            var oldValue = datepicker.date.getTime();
            var elValue = new Date(year, 0, 1).getTime();
            if (elValue !== oldValue) {
              core._trigger('pick.datepicker', {newDate: $.formatDate(new Date(year, 0, 1), datepicker.format), oldDate: $.formatDate(datepicker.date, datepicker.format)});
              datepicker.date = new Date(year, 0, 1);
              datepicker.value = year;
              datepicker.yearLabel = year;
            }
            $el.val($.formatDate(new Date(year, 0, 1), datepicker.format));
            core._hidePickerPanel();
          }
        },
        _setMonthView: function ($currentTd) {
          var $delegateTarget = datepicker.$pickerPanel;
          var $monthTable = $delegateTarget.find('.gmi-month-table');
          var type = datepicker.type;
          var year = datepicker.yearLabel;
          var month = Number($currentTd.data('month'));
  
          $monthTable.find('td').removeClass('current');
          $currentTd.addClass('current');
          if (type === 'date' || type === 'datetime') {
            core._setNewDateDOM($delegateTarget, year, month);
            $monthTable.hide();
            datepicker.currentView = 'dateView';
          } else if (type === 'month') {
            var oldValue = datepicker.date.getTime();
            var elValue = new Date(year, month, 1).getTime();
            if (elValue !== oldValue) {
              core._trigger('pick.datepicker', {newDate: $.formatDate(new Date(year, month, 1), datepicker.format), oldDate: $.formatDate(datepicker.date, datepicker.format)});
              datepicker.date = new Date(year, month, 1);
            }
            datepicker.value = $.formatDate(new Date(year, month, 1), datepicker.format);
            $el.val($.formatDate(new Date(year, month, 1), datepicker.format));
            core._hidePickerPanel();
          }
          datepicker.monthLabel = month;
        },
        _setRangeDateView: function ($delegateTarget, action) {
          var $leftTableWrapper = $delegateTarget.find('.gmi-picker-panel__body__main.f-lt');
          var $leftDateLabel = $leftTableWrapper.find('.gmi-date-range-picker__body__header > p');
          var $rightTableWrapper = $delegateTarget.find('.gmi-picker-panel__body__main.f-rt');
          var $rightDateLabel = $rightTableWrapper.find('.gmi-date-range-picker__body__header > p');
          var minDate = datepicker.limitStartDate;
          var methodName;
          var leftDate;
          var rightDate;
          var leftTableStr;
          var rightTableStr;
  
          switch (action) {
            case 'next-year':
              methodName = 'getNextYear';
              break;
            case 'next-month':
              methodName = 'getNextMonth';
              break;
            case 'prev-year':
              methodName = 'getPrevYear';
              break;
            case 'prev-month':
              methodName = 'getPrevMonth';
              break;
            default:
              methodName = 'normal';
              break;
          }
  
          if (methodName === 'normal') {
            leftDate = datepicker.minDate;
          } else {
            leftDate = $[methodName](minDate);
          }
          datepicker.limitStartDate = leftDate;
          rightDate = $.getNextMonth(leftDate);
          leftTableStr = core._getDateTable(leftDate);
          rightTableStr = core._getDateTable(rightDate);
          $delegateTarget.find('.gmi-date-table').remove();
          $(leftTableStr).appendTo($leftTableWrapper).show();
          $(rightTableStr).appendTo($rightTableWrapper).show();
          $leftDateLabel.text(leftDate.getFullYear() + ' '+ datepicker.yearSuffix +' ' + datepicker.monthsShort[leftDate.getMonth()] + '');
          $rightDateLabel.text(rightDate.getFullYear() + ' '+ datepicker.yearSuffix +' ' + datepicker.monthsShort[rightDate.getMonth()] + '');
        },
        _setTimeView: function ($timeInput, event) {
          var eventType = event.type;
          var keyCode = eventType === 'keyup' ? event.which : null;
          if ($timeInput.val() === '') datepicker.$pickerPanel.find('.gmi-picker-panel__link-btn--determine').addClass('disabled');
          if (eventType === 'focusin' || eventType === 'change' || (eventType === 'keyup' && keyCode && keyCode === KEY_CODE_ENTER)) {
            var hour = $timeInput.val() !== '' && $.parseDate($timeInput.val(), DEFAULT_TIME_FORMAT) ? $.parseDate($timeInput.val(), DEFAULT_TIME_FORMAT).getHours() : 0;
            var minutes = $timeInput.val() !== '' && $.parseDate($timeInput.val(), DEFAULT_TIME_FORMAT) ? $.parseDate($timeInput.val(), DEFAULT_TIME_FORMAT).getMinutes() : 0;
            var seconds = $timeInput.val() !== '' && $.parseDate($timeInput.val(), DEFAULT_TIME_FORMAT) ? $.parseDate($timeInput.val(), DEFAULT_TIME_FORMAT).getSeconds() : 0;
            var role = $timeInput.data('role');
            var time = {};
            switch (role) {
              case 'date':
                time.hour = hour;
                time.min = minutes;
                time.sec = seconds;
                break;
              case 'range-start':
                time.minHour = hour;
                time.minMin = minutes;
                time.minSec = seconds;
                break;
              case 'range-end':
                time.maxHour = hour;
                time.maxMin = minutes;
                time.maxSec = seconds;
                break;
              default:
                break;
            }
          }
          core._echoTimeIntoSpinner(time, role);
        },
        _echoInputValue: function (value) {
          if (datepicker.type === 'date-range' || datepicker.type === 'datetime-range') {
            core._setDate(value);
          } else {
            if ($.parseDate(value, datepicker.format)) {
              core._setDate($.formatDate($.parseDate(value, datepicker.format), datepicker.format));
              $(this).val($.formatDate($.parseDate(value, datepicker.format), datepicker.format));
            }
          }
        },
        _getDatePanelPosition: function () {
          var viewHeight = $(window).height();
          var scrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;
          var elHeight = $el.outerHeight();
          var panelHeight = datepicker.$pickerPanel.outerHeight();
          var top = $el.offset().top;
          var left = $el.offset().left;
          var classOriginBottomX = datepicker.align === 'left' ? CLASS_PLACEMENT_LEFT_BOTTOM : datepicker.align === 'center' ? CLASS_PLACEMENT_CENTER_BOTTOM : CLASS_PLACEMENT_RIGHT_BOTTOM;
  
          if ((top - scrollTop > panelHeight) && (top - scrollTop + elHeight + panelHeight > viewHeight)) {
            top -= panelHeight + Number(datepicker.$pickerPanel.css('margin-top').replace(/px/, '')) * 2;
            datepicker.$pickerPanel.addClass(classOriginBottomX);
          } else {
            top += elHeight;
            datepicker.$pickerPanel.removeClass(classOriginBottomX);
          }
          return {top: top, left: left};
        },
        _setDatePanelPosition: function () {
          var elWidth = $el.outerWidth();
          var position = core._getDatePanelPosition();
          var panelWidth;
          var left;
          if (datepicker.$pickerPanel.length > 0) {
            panelWidth = datepicker.$pickerPanel.outerWidth();
            switch (datepicker.align) {
              case 'left':
                left = position.left;
                break;
              case 'center':
                left = position.left - Math.abs(panelWidth - elWidth)/2;
                break;
              case 'right':
                left = position.left - Math.abs(panelWidth - elWidth);
                break;
              default:
                break;
            }
            datepicker.$pickerPanel.css({
              top: position.top + 'px',
              left: left + 'px'
            });
          }
        },
        _clear: function () {
          var type = datepicker.type;
          var $delegateTarget = datepicker.$pickerPanel;
          var $determineLinkButton = $delegateTarget.find('.gmi-picker-panel__link-btn--determine');
          var $yearLabel = $delegateTarget.find('.gmi-date-picker__header__label--year');
  
          $delegateTarget.find('.gmi-date-picker--input, .gmi-time-picker--input').val('');
          if ($determineLinkButton.length > 0) $determineLinkButton.addClass('disabled');
          if ('' !== datepicker.value) {
            // EVENT_PICK triggered
            core._trigger('pick.datepicker', {newDate: '', oldDate: datepicker.value});
            $el.val('');
            datepicker.date = new Date();
            datepicker.value = '';
            datepicker.yearLabel = datepicker.date.getFullYear();
            datepicker.monthLabel = datepicker.date.getMonth();
          }
          if (type === 'date-range' || type === 'datetime-range') {
            $delegateTarget.find('.gmi-date-table td').removeClass('start-date in-range end-date');
            datepicker.minDate= null;
            datepicker.maxDate= null;
          } else {
            if (type === 'year') {
              core._setNewYearDOM($delegateTarget, datepicker.yearLabel, $yearLabel);
            } else if (type === 'month') {
              $yearLabel.text(datepicker.yearLabel + ' '+ datepicker.yearSuffix +'');
              core._setNewMonthDOM($delegateTarget, datepicker.monthLabel);
            } else {
              core._setNewDateDOM($delegateTarget,datepicker.yearLabel,
                datepicker.monthLabel, new Date().getDate());
            }
          }
        },
        _showPickerPanel: function () {
          if (!datepicker.$pickerPanel.is(':hidden')) return;
          datepicker.$pickerPanel.show();
          // set picker panel position
          core._setDatePanelPosition();
  
          animation(datepicker.$pickerPanel, 'picker-show', function () {
            core._trigger('show.datepicker');
          });
        },
        _hidePickerPanel: function () {
          var type = datepicker.type;
          var currentView = datepicker.currentView;
          if (datepicker.$pickerPanel.is(':hidden')) return;
          if (type === 'date-range' || type === 'datetime-range' || type === 'month' || type === 'year') {
            animation(datepicker.$pickerPanel, 'picker-hide', function () {
              datepicker.$pickerPanel.hide();
              core._trigger('hide.datepicker');
            });
          } else {
            switch (currentView) {
              case 'yearView':
                datepicker.$pickerPanel.find('.gmi-date-picker__header__label--year')
                  .text(datepicker.yearLabel + ' ' + datepicker.yearSuffix)
                  .siblings('.gmi-date-picker__header__label--month').show();
                break;
              case 'monthView':
                datepicker.$pickerPanel.find('.gmi-date-picker__header__label--month')
                  .show();
                break;
              default:
                break;
            }
            animation(datepicker.$pickerPanel, 'picker-hide', function () {
              datepicker.$pickerPanel.hide().find('.gmi-date-table').show()
                  .siblings('table').hide();
              core._trigger('hide.datepicker');
            });
          }
        },
        _elFocusHandler: function () {
          core._showPickerPanel();
        },
        _elClickHandler: function (e) {
          e.stopPropagation();
        },
        _elChangeHandler: function () {
          var elValue = $(this).val();
          core._echoInputValue(elValue);
        },
        _elKeyUpHandler: function (e) {
          var keyCode = e.which;
          var elValue = $(this).val();
          if (keyCode === KEY_CODE_ENTER) {
            core._echoInputValue(elValue);
          }
        },
        _trigger: function (type, data) {
          var evt = $.Event(type, data);
          $el.trigger(evt);
          return evt;
        }
      };
  
      datepicker.version = '1.0.15';
  
      datepicker.setDate = function (date) {
        core._setDate(date);
      };
  
      datepicker.getDate = function () {
        return datepicker.value;
      };
  
      datepicker.show = function () {
        core._showPickerPanel();
      };
  
      datepicker.hide = function () {
        core._hidePickerPanel();
      };
  
      datepicker.clear = function () {
        core._clear();
      };
  
      datepicker.disable = function (disable) {
        $el.attr('disabled', disable);
        core._hidePickerPanel();
      };
  
      datepicker.destroy = function () {
        core._unBindEvent();
        core._unCreate();
        $el.removeData('datepicker');
      };
  
      core._init();
    };
  
    DatePicker.LANG = {
      'en-US': {
        days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
        daysMin: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
        months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
        monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
        yearSuffix: '',
        monthSuffix: '',
        todaySuffix: 'Today',
        dateInputPlaceholder: 'Select date',
        rangeStartInputPlaceholder: 'Start Date',
        rangeEndPlaceholder: 'End Date',
        dateTimeInputPlaceholder: 'Select time',
        rangeStartTimeInputPlaceholder: 'Start Time',
        rangeEndTimeInputPlaceholder: 'End Time',
        nowDateButton: 'Now',
        confirmDateButton: 'Confirm',
        cancelTimeButton: 'Cancel',
        clearButton: 'Clear'
      }
    };
  
    $.fn.datepicker = function (options) {
      var args = toArray(arguments, 1);
      var options = options || {};
      var $self = this;
      var result;
  
      $self.each(function () {
        var data = $(this).data('datepicker');
        var fn;
        if (!data) {
          if (/destroy/.test(options)) {
            return false;
          }
          if (typeof options !== 'string') return $(this).data('datepicker', (data = new DatePicker($(this), options)));
        }
  
        if (data && typeof options === 'string' && $.isFunction(fn = data[options])) {
          result = fn.apply(data, args);
        }
      });
      return typeof result === 'undefined' ? $self : result;
    };
  
    $.fn.datepicker.Constructor = DatePicker;
    $.fn.datepicker.lang = DatePicker.LANG;
  
    $.isLeapYear = function (year) {
      return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
    };
  
    $.getTotalDayCountOfMonth = function (year, month) {
      var lastDayArray = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
        totalCount = lastDayArray[month];
      if ($.isLeapYear(year) && month === 1) {
        totalCount++;
      }
      return totalCount;
    };
  
    $.getWeekDay = function (year, month, date) {
      var date = new Date(year, month-1, date);
      return date.getDay();
    };
  
    $.getFirstDayOfMonth = function (date) {
      var tempDate = new Date(date.getTime());
      tempDate.setDate(1);
      return tempDate.getDay();
    };
  
    $.getStartDateOfMonth = function (year, month) {
      var date = new Date(year, month, 1),
        day = date.getDay();
      if (day === 0) {
        date.setTime(date.getTime() - DAY_DURATION * 7);
      } else {
        date.setTime(date.getTime() - DAY_DURATION * day);
      }
      return date;
    };
  
    $.getWeekNumber = function (d) {
      var date = new Date(d.getTime()),
        firstWeek;
      date.setHours(0, 0, 0, 0);
      date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
      firstWeek = new Date(date.getFullYear(), 0, 4);
      return 1 + Math.round(((date.getTime() - firstWeek.getTime()) / 86400000 - 3 + (firstWeek.getDay() + 6) % 7) / 7);
    };
  
    $.getPrevMonth = function (d) {
      var year = d.getFullYear(),
        month = d.getMonth(),
        date = d.getDate(),
        newDate = new Date(),
        newYear = month === 0 ? year - 1 : year,
        newMonth = month === 0 ? 11 : month - 1,
        newMonthDayCount = $.getTotalDayCountOfMonth(newYear, newMonth);
  
      newDate.setMonth(newMonth);
      newDate.setFullYear(newYear);
  
      if (newMonthDayCount < date) {
        newDate.setDate(newMonthDayCount);
      }
      return new Date(newDate.getTime());
    };
  
    $.getNextMonth = function (d) {
      var year = d.getFullYear(),
        month = d.getMonth(),
        date = d.getDate(),
        newDate = new Date(),
        newYear = month === 11 ? year + 1 : year,
        newMonth = month === 11 ? 0 : month + 1,
        newMonthDayCount = $.getTotalDayCountOfMonth(newYear, newMonth);
  
      newDate.setMonth(newMonth);
      newDate.setFullYear(newYear);
  
      if (newMonthDayCount < date) {
        newDate.setDate(newMonthDayCount);
      }
      return new Date(newDate.getTime());
    };
  
    $.getPrevYear = function (d) {
      var year = d.getFullYear(),
        month = d.getMonth(),
        date = d.getDate(),
        newDate = new Date(),
        newYear = year - 1,
        newMonth = month,
        newMonthDayCount = $.getTotalDayCountOfMonth(newYear, newMonth);
  
      newDate.setMonth(newMonth);
      newDate.setFullYear(newYear);
  
      if (newMonthDayCount < date) {
        newDate.setDate(newMonthDayCount);
      }
      return new Date(newDate.getTime());
    };
  
    $.getNextYear = function (d) {
      var year = d.getFullYear(),
        month = d.getMonth(),
        date = d.getDate(),
        newDate = new Date(),
        newYear = year + 1,
        newMonth = month,
        newMonthDayCount = $.getTotalDayCountOfMonth(newYear, newMonth);
  
      newDate.setMonth(newMonth);
      newDate.setFullYear(newYear);
  
      if (newMonthDayCount < date) {
        newDate.setDate(newMonthDayCount);
      }
      return new Date(newDate.getTime());
    };
  
    $.clearHours = function (time) {
      var cloneDate = new Date(time);
      cloneDate.setHours(0, 0, 0, 0);
      return cloneDate.getTime();
    };
  
    $.formatDate = function (date, format) {
      date = new Date(date);
      if (isNaN(date.getTime())) return null;
      if (!date) return '';
      return dateUtils.format(date, format || 'yyyy-MM-dd');
    };
  
    $.parseDate = function (string, format) {
      return dateUtils.parse(string, format || 'yyyy-MM-dd');
    };
  
    // toArray Method
    function toArray (obj, offset) {
      var args = [];
      if (Array.from) {
        return Array.from(obj).slice(offset || 0);
      }
      if (typeof offset === 'number' && !isNaN(offset)) {
        args.push(offset);
      }
      return args.slice.apply(obj, args);
    }
  
    // isString Method
    function isString (str) {
      return typeof str === 'string';
    }
  
    // isDate Method
    function isDate (date) {
      return typeof date === 'object' && date instanceof Date;
    }
  
    function animation ($target, classes, fn) {
      $target.removeClass(classes).addClass(classes).one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function(){
        $(this).removeClass(classes);
        fn.apply($target, toArray(arguments));
      });
      if (isIE && IE_MODE <= 9) {
        $target.removeClass(classes);
        fn.apply($target, toArray(arguments));
      }
    }
  });