@telerik/kendo-intl
Version:
A package exporting functions for date and number parsing and formatting
608 lines (491 loc) • 15 kB
JavaScript
import { adjustDST, convertTimeZone } from './time-utils';
import { localeInfo } from '../cldr';
import { DEFAULT_LOCALE, EMPTY } from '../common/constants';
import { errors } from '../errors';
import formatNames from './format-names';
import datePattern from './date-pattern';
import round from '../common/round';
import isDate from '../common/is-date';
var timeZoneOffsetRegExp = /([+|\-]\d{1,2})(:?)(\d{2})?/;
var dateRegExp = /^\/Date\((.*?)\)\/$/;
var offsetRegExp = /[+-]\d*/;
var numberRegExp = {
2: /^\d{1,2}/,
3: /^\d{1,3}/,
4: /^\d{4}/
};
var numberRegex = /\d+/;
var PLACEHOLDER = "{0}";
var leadingSpacesRegex = /^ */;
var trailingSpacesRegex = / *$/;
var standardDateFormats = [
"yyyy/MM/dd HH:mm:ss",
"yyyy/MM/dd HH:mm",
"yyyy/MM/dd",
"E MMM dd yyyy HH:mm:ss",
"yyyy-MM-ddTHH:mm:ss.SSSSSSSXXX",
"yyyy-MM-ddTHH:mm:ss.SSSXXX",
"yyyy-MM-ddTHH:mm:ss.SSXXX",
"yyyy-MM-ddTHH:mm:ssXXX",
"yyyy-MM-ddTHH:mm:ss.SSSSSSS",
"yyyy-MM-ddTHH:mm:ss.SSS",
"yyyy-MM-ddTHH:mmXXX",
"yyyy-MM-ddTHH:mmX",
"yyyy-MM-ddTHH:mm:ss",
"yyyy-MM-ddTHH:mm",
"yyyy-MM-dd HH:mm:ss",
"yyyy-MM-dd HH:mm",
"yyyy-MM-dd",
"HH:mm:ss",
"HH:mm"
];
var FORMATS_SEQUENCE = [ "G", "g", "F", "Y", "y", "M", "m", "D", "d", "y", "T", "t" ];
var TWO_DIGIT_YEAR_MAX = 2029;
function outOfRange(value, start, end) {
return !(value >= start && value <= end);
}
function lookAhead(match, state) {
var format = state.format;
var idx = state.idx;
var i = 0;
while (format[idx] === match) {
i++;
idx++;
}
if (i > 0) {
idx -= 1;
}
state.idx = idx;
return i;
}
function getNumber(size, state) {
var regex = size ? (numberRegExp[size] || new RegExp('^\\d{1,' + size + '}')) : numberRegex,
match = state.value.substr(state.valueIdx, size).match(regex);
if (match) {
match = match[0];
state.valueIdx += match.length;
return parseInt(match, 10);
}
return null;
}
function getIndexByName(names, state, lower) {
var i = 0,
length = names.length,
name, nameLength,
matchLength = 0,
matchIdx = 0,
subValue;
for (; i < length; i++) {
name = names[i];
nameLength = name.length;
subValue = state.value.substr(state.valueIdx, nameLength);
if (lower) {
subValue = subValue.toLowerCase();
}
if (subValue === name && nameLength > matchLength) {
matchLength = nameLength;
matchIdx = i;
}
}
if (matchLength) {
state.valueIdx += matchLength;
return matchIdx + 1;
}
return null;
}
function checkLiteral(state) {
var result = false;
if (state.value.charAt(state.valueIdx) === state.format[state.idx]) {
state.valueIdx++;
result = true;
}
return result;
}
function calendarGmtFormats(calendar) {
var gmtFormat = calendar.gmtFormat;
var gmtZeroFormat = calendar.gmtZeroFormat;
if (!gmtFormat) {
throw errors.NoGMTInfo.error();
}
return [ gmtFormat.replace(PLACEHOLDER, EMPTY).toLowerCase(), gmtZeroFormat.replace(PLACEHOLDER, EMPTY).toLowerCase() ];
}
function parseTimeZoneOffset(state, info, options) {
var shortHours = options.shortHours;
var noSeparator = options.noSeparator;
var optionalMinutes = options.optionalMinutes;
var localizedName = options.localizedName;
var zLiteral = options.zLiteral;
state.UTC = true;
if (zLiteral && state.value.charAt(state.valueIdx) === "Z") {
state.valueIdx++;
return false;
}
if (localizedName && !getIndexByName(calendarGmtFormats(info.calendar), state, true)) {
return true;
}
var matches = timeZoneOffsetRegExp.exec(state.value.substr(state.valueIdx, 6));
if (!matches) {
return !localizedName;
}
var hoursMatch = matches[1];
var minutesMatch = matches[3];
var hoursOffset = parseInt(hoursMatch, 10);
var separator = matches[2];
var minutesOffset = parseInt(minutesMatch, 10);
if (isNaN(hoursOffset) || (!shortHours && hoursMatch.length !== 3) || (!optionalMinutes && isNaN(minutesOffset)) || (noSeparator && separator)) {
return true;
}
if (isNaN(minutesOffset)) {
minutesOffset = null;
}
if (outOfRange(hoursOffset, -12, 13) || (minutesOffset && outOfRange(minutesOffset, 0, 59))) {
return true;
}
state.valueIdx += matches[0].length;
state.hoursOffset = hoursOffset;
state.minutesOffset = minutesOffset;
}
function parseMonth(ch, state, info) {
var count = lookAhead(ch, state);
var names = formatNames(info, "months", count, ch === "L", true);
var month = count < 3 ? getNumber(2, state) : getIndexByName(names, state, true);
if (month === null || outOfRange(month, 1, 12)) {
return true;
}
state.month = month - 1;
}
function parseDayOfWeek(ch, state, info) {
var count = lookAhead(ch, state);
var names = formatNames(info, "days", count, ch === "c", true);
var dayOfWeek = count < 3 ? getNumber(1, state) : getIndexByName(names, state, true);
if ((!dayOfWeek && dayOfWeek !== 0) || outOfRange(dayOfWeek, 1, 7)) {
return true;
}
}
var parsers = {};
parsers.d = function(state) {
lookAhead("d", state);
var day = getNumber(2, state);
if (day === null || outOfRange(day, 1, 31)) {
return true;
}
if (state.day === null) {
state.day = day;
}
};
parsers.E = function(state, info) {
var count = lookAhead("E", state);
//validate if it matches the day?
var dayOfWeek = getIndexByName(formatNames(info, "days", count, false, true), state, true);
if (dayOfWeek === null) {
return true;
}
};
parsers.M = function(state, info) {
return parseMonth("M", state, info);
};
parsers.L = function(state, info) {
return parseMonth("L", state, info);
};
parsers.y = function(state) {
var count = lookAhead("y", state);
var year = getNumber(count === 1 ? undefined : count, state);
if (year === null) {
return true;
}
if (count === 2) {
var currentYear = new Date().getFullYear();
year = (currentYear - currentYear % 100) + year;
if (year > TWO_DIGIT_YEAR_MAX) {
year -= 100;
}
}
state.year = year;
};
parsers.h = function(state) {
lookAhead("h", state);
var hours = getNumber(2, state);
if (hours === 12) {
hours = 0;
}
if (hours === null || outOfRange(hours, 0, 11)) {
return true;
}
state.hours = hours;
};
parsers.K = function(state) {
lookAhead("K", state);
var hours = getNumber(2, state);
if (hours === null || outOfRange(hours, 0, 11)) {
return true;
}
state.hours = hours;
};
parsers.a = function(state, info) {
var count = lookAhead("a", state);
var periodFormats = formatNames(info, "dayPeriods", count, false, true);
var pmHour = getIndexByName([ periodFormats.pm ], state, true);
if (!pmHour && !getIndexByName([ periodFormats.am ], state, true)) {
return true;
}
state.pmHour = pmHour;
};
parsers.H = function(state) {
lookAhead("H", state);
var hours = getNumber(2, state);
if (hours === null || outOfRange(hours, 0, 23)) {
return true;
}
state.hours = hours;
};
parsers.k = function(state) {
lookAhead("k", state);
var hours = getNumber(2, state);
if (hours === null || outOfRange(hours, 1, 24)) {
return true;
}
state.hours = hours === 24 ? 0 : hours;
};
parsers.m = function(state) {
lookAhead("m", state);
var minutes = getNumber(2, state);
if (minutes === null || outOfRange(minutes, 0, 59)) {
return true;
}
state.minutes = minutes;
};
parsers.s = function(state) {
lookAhead("s", state);
var seconds = getNumber(2, state);
if (seconds === null || outOfRange(seconds, 0, 59)) {
return true;
}
state.seconds = seconds;
};
parsers.S = function(state) {
var count = lookAhead("S", state);
var match = state.value.substr(state.valueIdx, count);
var milliseconds = null;
if (!isNaN(parseInt(match, 10))) {
milliseconds = parseFloat("0." + match, 10);
milliseconds = round(milliseconds, 3);
milliseconds *= 1000;
state.valueIdx += count;
}
if (milliseconds === null || outOfRange(milliseconds, 0, 999)) {
return true;
}
state.milliseconds = milliseconds;
};
parsers.z = function(state, info) {
var count = lookAhead("z", state);
var shortFormat = count < 4;
var invalid = parseTimeZoneOffset(state, info, {
shortHours: shortFormat,
optionalMinutes: shortFormat,
localizedName: true
});
if (invalid) {
return invalid;
}
};
parsers.Z = function(state, info) {
var count = lookAhead("Z", state);
var invalid = parseTimeZoneOffset(state, info, {
noSeparator: count < 4,
zLiteral: count === 5,
localizedName: count === 4
});
if (invalid) {
return invalid;
}
};
parsers.x = function(state, info) {
var count = lookAhead("x", state);
var invalid = parseTimeZoneOffset(state, info, {
noSeparator: count !== 3 && count !== 5,
optionalMinutes: count === 1
});
if (invalid) {
return invalid;
}
};
parsers.X = function(state, info) {
var count = lookAhead("X", state);
var invalid = parseTimeZoneOffset(state, info, {
noSeparator: count !== 3 && count !== 5,
optionalMinutes: count === 1,
zLiteral: true
});
if (invalid) {
return invalid;
}
};
parsers.G = function(state, info) {
var count = lookAhead("G", state);
var eras = formatNames(info, "eras", count, false, true);
var era = getIndexByName([ eras[0], eras[1] ], state, true);
if (era === null) {
return true;
}
};
parsers.e = function(state, info) {
return parseDayOfWeek("e", state, info);
};
parsers.c = function(state, info) {
return parseDayOfWeek("c", state, info);
};
function createDate(state) {
var year = state.year;
var month = state.month;
var day = state.day;
var hours = state.hours;
var minutes = state.minutes;
var seconds = state.seconds;
var milliseconds = state.milliseconds;
var pmHour = state.pmHour;
var UTC = state.UTC;
var hoursOffset = state.hoursOffset;
var minutesOffset = state.minutesOffset;
var hasTime = hours !== null || minutes !== null || seconds || null;
var date = new Date();
var result;
if (year === null && month === null && day === null && hasTime) {
year = date.getFullYear();
month = date.getMonth();
day = date.getDate();
} else {
if (year === null) {
year = date.getFullYear();
}
if (day === null) {
day = 1;
}
}
if (pmHour && hours < 12) {
hours += 12;
}
if (UTC) {
if (hoursOffset) {
hours += -hoursOffset;
}
if (minutesOffset) {
minutes += -minutesOffset * (hoursOffset < 0 ? -1 : 1);
}
result = new Date(Date.UTC(year, month, day, hours, minutes, seconds, milliseconds));
} else {
result = new Date(year, month, day, hours, minutes, seconds, milliseconds);
adjustDST(result, hours);
}
if (year < 100) {
result.setFullYear(year);
}
if (result.getDate() !== day && UTC === undefined) {
return null;
}
return result;
}
function addFormatSpaces(value, format) {
var leadingSpaces = leadingSpacesRegex.exec(format)[0];
var trailingSpaces = trailingSpacesRegex.exec(format)[0];
return ("" + leadingSpaces + value + trailingSpaces);
}
function parseExact(value, format, info) {
var pattern = datePattern(format, info).split(EMPTY);
var state = {
format: pattern,
idx: 0,
value: addFormatSpaces(value, format),
valueIdx: 0,
year: null,
month: null,
day: null,
hours: null,
minutes: null,
seconds: null,
milliseconds: null
};
var length = pattern.length;
var literal = false;
for (; state.idx < length; state.idx++) {
var ch = pattern[state.idx];
if (literal) {
if (ch === "'") {
literal = false;
}
checkLiteral(state);
} else {
if (parsers[ch]) {
var invalid = parsers[ch](state, info);
if (invalid) {
return null;
}
} else if (ch === "'") {
literal = true;
checkLiteral(state);
} else if (!checkLiteral(state)) {
return null;
}
}
}
if (state.valueIdx < value.length) {
return null;
}
return createDate(state) || null;
}
function parseMicrosoftDateOffset(offset) {
var sign = offset.substr(0, 1) === "-" ? -1 : 1;
var result = offset.substring(1);
result = (parseInt(result.substr(0, 2), 10) * 60) + parseInt(result.substring(2), 10);
return sign * result;
}
function parseMicrosoftDateFormat(value) {
if (value && value.indexOf("/D") === 0) {
var date = dateRegExp.exec(value);
if (date) {
date = date[1];
var tzoffset = offsetRegExp.exec(date.substring(1));
date = new Date(parseInt(date, 10));
if (tzoffset) {
tzoffset = parseMicrosoftDateOffset(tzoffset[0]);
date = convertTimeZone(date, date.getTimezoneOffset(), 0);
date = convertTimeZone(date, 0, -1 * tzoffset);
}
return date;
}
}
}
function defaultFormats(calendar) {
var formats = [];
var patterns = calendar.patterns;
var length = FORMATS_SEQUENCE.length;
for (var idx = 0; idx < length; idx++) {
formats.push(patterns[FORMATS_SEQUENCE[idx]]);
}
return formats.concat(standardDateFormats);
}
export default function parseDate(value, formats, locale) {
if ( locale === void 0 ) locale = DEFAULT_LOCALE;
if (!value) {
return null;
}
if (isDate(value)) {
return value;
}
var parseValue = String(value).trim();
var date = parseMicrosoftDateFormat(parseValue);
if (date) {
return date;
}
var info = localeInfo(locale);
var parseFormats = formats || defaultFormats(info.calendar);
parseFormats = Array.isArray(parseFormats) ? parseFormats : [ parseFormats ];
var length = parseFormats.length;
for (var idx = 0; idx < length; idx++) {
date = parseExact(parseValue, parseFormats[idx], info);
if (date) {
return date;
}
}
return date;
}