UNPKG

angular-ui-bootstrap

Version:

Native AngularJS (Angular) directives for Bootstrap

540 lines (482 loc) 16.2 kB
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})/), sign = matches[1], hours = matches[2], 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 = [], 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], regex = parser.regex, map = parser.map, results = input.match(regex), 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)); } }]);