angular-ui-bootstrap
Version:
Native AngularJS (Angular) directives for Bootstrap
540 lines (482 loc) • 16.2 kB
JavaScript
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));
}
}]);