text: angular.module('ui.bootstrap.dateparser', [])
  
      .service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', 'filterFilter', function($log, $locale, dateFilter, orderByFilter, filterFilter) {
          // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
          var SPECIAL_CHARACTERS_REGEXP = /[\\^$*+?|[\]().{}]/g;
  
          var localeId;
          var formatCodeToRegex;
  
          this.init = function() {
              localeId = $locale.id;
  
              this.parsers = {};
              this.formatters = {};
  
              formatCodeToRegex = [
                  {
                      key: 'yyyy',
                      regex: '\\d{4}',
                      apply: function(value) {
                          this.year = +value;
                      },
                      formatter: function(date) {
                          var _date = new Date();
                          _date.setFullYear(Math.abs(date.getFullYear()));
                          return dateFilter(_date, 'yyyy');
                      }
                  },
                  {
                      key: 'yy',
                      regex: '\\d{2}',
                      apply: function(value) {
                          value = +value;
                          this.year = value < 69 ? value + 2000 : value + 1900;
                      },
                      formatter: function(date) {
                          var _date = new Date();
                          _date.setFullYear(Math.abs(date.getFullYear()));
                          return dateFilter(_date, 'yy');
                      }
                  },
                  {
                      key: 'y',
                      regex: '\\d{1,4}',
                      apply: function(value) {
                          this.year = +value;
                      },
                      formatter: function(date) {
                          var _date = new Date();
                          _date.setFullYear(Math.abs(date.getFullYear()));
                          return dateFilter(_date, 'y');
                      }
                  },
                  {
                      key: 'M!',
                      regex: '0?[1-9]|1[0-2]',
                      apply: function(value) {
                          this.month = value - 1;
                      },
                      formatter: function(date) {
                          var value = date.getMonth();
                          if (/^[0-9]$/.test(value)) {
                              return dateFilter(date, 'MM');
                          }
  
                          return dateFilter(date, 'M');
                      }
                  },
                  {
                      key: 'MMMM',
                      regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
                      apply: function(value) {
                          this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value);
                      },
                      formatter: function(date) {
                          return dateFilter(date, 'MMMM');
                      }
                  },
                  {
                      key: 'MMM',
                      regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
                      apply: function(value) {
                          this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value);
                      },
                      formatter: function(date) {
                          return dateFilter(date, 'MMM');
                      }
                  },
                  {
                      key: 'MM',
                      regex: '0[1-9]|1[0-2]',
                      apply: function(value) {
                          this.month = value - 1;
                      },
                      formatter: function(date) {
                          return dateFilter(date, 'MM');
                      }
                  },
                  {
                      key: 'M',
                      regex: '[1-9]|1[0-2]',
                      apply: function(value) {
                          this.month = value - 1;
                      },
                      formatter: function(date) {
                          return dateFilter(date, 'M');
                      }
                  },
                  {
                      key: 'd!',
                      regex: '[0-2]?[0-9]{1}|3[0-1]{1}',
                      apply: function(value) {
                          this.date = +value;
                      },
                      formatter: function(date) {
                          var value = date.getDate();
                          if (/^[1-9]$/.test(value)) {
                              return dateFilter(date, 'dd');
                          }
  
                          return dateFilter(date, 'd');
                      }
                  },
                  {
                      key: 'dd',
                      regex: '[0-2][0-9]{1}|3[0-1]{1}',
                      apply: function(value) {
                          this.date = +value;
                      },
                      formatter: function(date) {
                          return dateFilter(date, 'dd');
                      }
                  },
                  {
                      key: 'd',
                      regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
                      apply: function(value) {
                          this.date = +value;
                      },
                      formatter: function(date) {
                          return dateFilter(date, 'd');
                      }
                  },
                  {
                      key: 'EEEE',
                      regex: $locale.DATETIME_FORMATS.DAY.join('|'),
                      formatter: function(date) {
                          return dateFilter(date, 'EEEE');
                      }
                  },
                  {
                      key: 'EEE',
                      regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|'),
                      formatter: function(date) {
                          return dateFilter(date, 'EEE');
                      }
                  },
                  {
                      key: 'HH',
                      regex: '(?:0|1)[0-9]|2[0-3]',
                      apply: function(value) {
                          this.hours = +value;
                      },
                      formatter: function(date) {
                          return dateFilter(date, 'HH');
                      }
                  },
                  {
                      key: 'hh',
                      regex: '0[0-9]|1[0-2]',
                      apply: function(value) {
                          this.hours = +value;
                      },
                      formatter: function(date) {
                          return dateFilter(date, 'hh');
                      }
                  },
                  {
                      key: 'H',
                      regex: '1?[0-9]|2[0-3]',
                      apply: function(value) {
                          this.hours = +value;
                      },
                      formatter: function(date) {
                          return dateFilter(date, 'H');
                      }
                  },
                  {
                      key: 'h',
                      regex: '[0-9]|1[0-2]',
                      apply: function(value) {
                          this.hours = +value;
                      },
                      formatter: function(date) {
                          return dateFilter(date, 'h');
                      }
                  },
                  {
                      key: 'mm',
                      regex: '[0-5][0-9]',
                      apply: function(value) {
                          this.minutes = +value;
                      },
                      formatter: function(date) {
                          return dateFilter(date, 'mm');
                      }
                  },
                  {
                      key: 'm',
                      regex: '[0-9]|[1-5][0-9]',
                      apply: function(value) {
                          this.minutes = +value;
                      },
                      formatter: function(date) {
                          return dateFilter(date, 'm');
                      }
                  },
                  {
                      key: 'sss',
                      regex: '[0-9][0-9][0-9]',
                      apply: function(value) {
                          this.milliseconds = +value;
                      },
                      formatter: function(date) {
                          return dateFilter(date, 'sss');
                      }
                  },
                  {
                      key: 'ss',
                      regex: '[0-5][0-9]',
                      apply: function(value) {
                          this.seconds = +value;
                      },
                      formatter: function(date) {
                          return dateFilter(date, 'ss');
                      }
                  },
                  {
                      key: 's',
                      regex: '[0-9]|[1-5][0-9]',
                      apply: function(value) {
                          this.seconds = +value;
                      },
                      formatter: function(date) {
                          return dateFilter(date, 's');
                      }
                  },
                  {
                      key: 'a',
                      regex: $locale.DATETIME_FORMATS.AMPMS.join('|'),
                      apply: function(value) {
                          if (this.hours === 12) {
                              this.hours = 0;
                          }
  
                          if (value === 'PM') {
                              this.hours += 12;
                          }
                      },
                      formatter: function(date) {
                          return dateFilter(date, 'a');
                      }
                  },
                  {
                      key: 'Z',
                      regex: '[+-]\\d{4}',
                      apply: function(value) {
                          var matches = value.match(/([+-])(\d{2})(\d{2})/);
  
                          var sign = matches[1];
  
                          var hours = matches[2];
  
                          var minutes = matches[3];
                          this.hours += toInt(sign + hours);
                          this.minutes += toInt(sign + minutes);
                      },
                      formatter: function(date) {
                          return dateFilter(date, 'Z');
                      }
                  },
                  {
                      key: 'ww',
                      regex: '[0-4][0-9]|5[0-3]',
                      formatter: function(date) {
                          return dateFilter(date, 'ww');
                      }
                  },
                  {
                      key: 'w',
                      regex: '[0-9]|[1-4][0-9]|5[0-3]',
                      formatter: function(date) {
                          return dateFilter(date, 'w');
                      }
                  },
                  {
                      key: 'GGGG',
                      regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s'),
                      formatter: function(date) {
                          return dateFilter(date, 'GGGG');
                      }
                  },
                  {
                      key: 'GGG',
                      regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
                      formatter: function(date) {
                          return dateFilter(date, 'GGG');
                      }
                  },
                  {
                      key: 'GG',
                      regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
                      formatter: function(date) {
                          return dateFilter(date, 'GG');
                      }
                  },
                  {
                      key: 'G',
                      regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
                      formatter: function(date) {
                          return dateFilter(date, 'G');
                      }
                  }
              ];
  
              if (angular.version.major >= 1 && angular.version.minor > 4) {
                  formatCodeToRegex.push({
                      key: 'LLLL',
                      regex: $locale.DATETIME_FORMATS.STANDALONEMONTH.join('|'),
                      apply: function(value) {
                          this.month = $locale.DATETIME_FORMATS.STANDALONEMONTH.indexOf(value);
                      },
                      formatter: function(date) {
                          return dateFilter(date, 'LLLL');
                      }
                  });
              }
          };
  
          this.init();
  
          function getFormatCodeToRegex(key) {
              return filterFilter(formatCodeToRegex, {key: key}, true)[0];
          }
  
          this.getParser = function(key) {
              var f = getFormatCodeToRegex(key);
              return (f && f.apply) || null;
          };
  
          this.overrideParser = function(key, parser) {
              var f = getFormatCodeToRegex(key);
              if (f && angular.isFunction(parser)) {
                  this.parsers = {};
                  f.apply = parser;
              }
          }.bind(this);
  
          function createParser(format) {
              var map = [];
              var regex = format.split('');
  
              // check for literal values
              var quoteIndex = format.indexOf('\'');
              if (quoteIndex > -1) {
                  var inLiteral = false;
                  format = format.split('');
                  for (var i = quoteIndex; i < format.length; i++) {
                      if (inLiteral) {
                          if (format[i] === '\'') {
                              if (i + 1 < format.length && format[i + 1] === '\'') { // escaped single quote
                                  format[i + 1] = '$';
                                  regex[i + 1] = '';
                              } else { // end of literal
                                  regex[i] = '';
                                  inLiteral = false;
                              }
                          }
                          format[i] = '$';
                      } else {
                          if (format[i] === '\'') { // start of literal
                              format[i] = '$';
                              regex[i] = '';
                              inLiteral = true;
                          }
                      }
                  }
  
                  format = format.join('');
              }
  
              angular.forEach(formatCodeToRegex, function(data) {
                  var index = format.indexOf(data.key);
  
                  if (index > -1) {
                      format = format.split('');
  
                      regex[index] = '(' + data.regex + ')';
                      format[index] = '$'; // Custom symbol to define consumed part of format
                      for (var i = index + 1, n = index + data.key.length; i < n; i++) {
                          regex[i] = '';
                          format[i] = '$';
                      }
                      format = format.join('');
  
                      map.push({
                          index: index,
                          key: data.key,
                          apply: data.apply,
                          matcher: data.regex
                      });
                  }
              });
  
              return {
                  regex: new RegExp('^' + regex.join('') + '$'),
                  map: orderByFilter(map, 'index')
              };
          }
  
          function createFormatter(format) {
              var formatters = [];
              var i = 0;
              var formatter, literalIdx;
              while (i < format.length) {
                  if (angular.isNumber(literalIdx)) {
                      if (format.charAt(i) === '\'') {
                          if (i + 1 >= format.length || format.charAt(i + 1) !== '\'') {
                              formatters.push(constructLiteralFormatter(format, literalIdx, i));
                              literalIdx = null;
                          }
                      } else if (i === format.length) {
                          while (literalIdx < format.length) {
                              formatter = constructFormatterFromIdx(format, literalIdx);
                              formatters.push(formatter);
                              literalIdx = formatter.endIdx;
                          }
                      }
  
                      i++;
                      continue;
                  }
  
                  if (format.charAt(i) === '\'') {
                      literalIdx = i;
                      i++;
                      continue;
                  }
  
                  formatter = constructFormatterFromIdx(format, i);
  
                  formatters.push(formatter.parser);
                  i = formatter.endIdx;
              }
  
              return formatters;
          }
  
          function constructLiteralFormatter(format, literalIdx, endIdx) {
              return function() {
                  return format.substr(literalIdx + 1, endIdx - literalIdx - 1);
              };
          }
  
          function constructFormatterFromIdx(format, i) {
              var currentPosStr = format.substr(i);
              for (var j = 0; j < formatCodeToRegex.length; j++) {
                  if (new RegExp('^' + formatCodeToRegex[j].key).test(currentPosStr)) {
                      var data = formatCodeToRegex[j];
                      return {
                          endIdx: i + data.key.length,
                          parser: data.formatter
                      };
                  }
              }
  
              return {
                  endIdx: i + 1,
                  parser: function() {
                      return currentPosStr.charAt(0);
                  }
              };
          }
  
          this.filter = function(date, format) {
              if (!angular.isDate(date) || isNaN(date) || !format) {
                  return '';
              }
  
              format = $locale.DATETIME_FORMATS[format] || format;
  
              if ($locale.id !== localeId) {
                  this.init();
              }
  
              if (!this.formatters[format]) {
                  this.formatters[format] = createFormatter(format);
              }
  
              var formatters = this.formatters[format];
  
              return formatters.reduce(function(str, formatter) {
                  return str + formatter(date);
              }, '');
          };
  
          this.parse = function(input, format, baseDate) {
              if (!angular.isString(input) || !format) {
                  return input;
              }
  
              format = $locale.DATETIME_FORMATS[format] || format;
              format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&');
  
              if ($locale.id !== localeId) {
                  this.init();
              }
  
              if (!this.parsers[format]) {
                  this.parsers[format] = createParser(format, 'apply');
              }
  
              var parser = this.parsers[format];
  
              var regex = parser.regex;
  
              var map = parser.map;
  
              var results = input.match(regex);
  
              var tzOffset = false;
              if (results && results.length) {
                  var fields, dt;
                  if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
                      fields = {
                          year: baseDate.getFullYear(),
                          month: baseDate.getMonth(),
                          date: baseDate.getDate(),
                          hours: baseDate.getHours(),
                          minutes: baseDate.getMinutes(),
                          seconds: baseDate.getSeconds(),
                          milliseconds: baseDate.getMilliseconds()
                      };
                  } else {
                      if (baseDate) {
                          $log.warn('dateparser:', 'baseDate is not a valid date');
                      }
                      fields = {year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0};
                  }
  
                  for (var i = 1, n = results.length; i < n; i++) {
                      var mapper = map[i - 1];
                      if (mapper.matcher === 'Z') {
                          tzOffset = true;
                      }
  
                      if (mapper.apply) {
                          mapper.apply.call(fields, results[i]);
                      }
                  }
  
                  var datesetter = tzOffset ? Date.prototype.setUTCFullYear
                      : Date.prototype.setFullYear;
                  var timesetter = tzOffset ? Date.prototype.setUTCHours
                      : Date.prototype.setHours;
  
                  if (isValid(fields.year, fields.month, fields.date)) {
                      if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) {
                          dt = new Date(baseDate);
                          datesetter.call(dt, fields.year, fields.month, fields.date);
                          timesetter.call(dt, fields.hours, fields.minutes,
                              fields.seconds, fields.milliseconds);
                      } else {
                          dt = new Date(0);
                          datesetter.call(dt, fields.year, fields.month, fields.date);
                          timesetter.call(dt, fields.hours || 0, fields.minutes || 0,
                              fields.seconds || 0, fields.milliseconds || 0);
                      }
                  }
  
                  return dt;
              }
          };
  
          // Check if date is valid for specific month (and year for February).
          // Month: 0 = Jan, 1 = Feb, etc
          function isValid(year, month, date) {
              if (date < 1) {
                  return false;
              }
  
              if (month === 1 && date > 28) {
                  return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
              }
  
              if (month === 3 || month === 5 || month === 8 || month === 10) {
                  return date < 31;
              }
  
              return true;
          }
  
          function toInt(str) {
              return parseInt(str, 10);
          }
  
          this.toTimezone = toTimezone;
          this.fromTimezone = fromTimezone;
          this.timezoneToOffset = timezoneToOffset;
          this.addDateMinutes = addDateMinutes;
          this.convertTimezoneToLocal = convertTimezoneToLocal;
  
          function toTimezone(date, timezone) {
              return date && timezone ? convertTimezoneToLocal(date, timezone) : date;
          }
  
          function fromTimezone(date, timezone) {
              return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date;
          }
  
          // https://github.com/angular/angular.js/blob/622c42169699ec07fc6daaa19fe6d224e5d2f70e/src/Angular.js#L1207
          function timezoneToOffset(timezone, fallback) {
              timezone = timezone.replace(/:/g, '');
              var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
              return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
          }
  
          function addDateMinutes(date, minutes) {
              date = new Date(date.getTime());
              date.setMinutes(date.getMinutes() + minutes);
              return date;
          }
  
          function convertTimezoneToLocal(date, timezone, reverse) {
              reverse = reverse ? -1 : 1;
              var dateTimezoneOffset = date.getTimezoneOffset();
              var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
              return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset));
          }
      }]);
  