src/chronos/create/from-string.ts
// tslint:disable-next-line
import { defaultLocaleMonthsShort, defaultLocaleWeekdaysShort } from '../locale/locale.class';
import { DateArray } from '../types';
import { DateParsingConfig } from './parsing.types';
import { isString } from '../utils/type-checks';
import { configFromStringAndFormat } from './from-string-and-format';
import { createUTCDate } from './date-from-array';
import { createInvalid, markInvalid } from './valid';
import { getParsingFlags } from './parsing-flags';
// iso 8601 regex
// 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
// tslint:disable-next-line
const extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
// tslint:disable-next-line
const basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
const tzRegex = /Z|[+-]\d\d(?::?\d\d)?/;
const isoDates: [[string, RegExp, boolean]] = [
['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/, true],
['YYYY-MM-DD', /\d{4}-\d\d-\d\d/, true],
['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/, true],
['GGGG-[W]WW', /\d{4}-W\d\d/, false],
['YYYY-DDD', /\d{4}-\d{3}/, true],
['YYYY-MM', /\d{4}-\d\d/, false],
['YYYYYYMMDD', /[+-]\d{10}/, true],
['YYYYMMDD', /\d{8}/, true],
// YYYYMM is NOT allowed by the standard
['GGGG[W]WWE', /\d{4}W\d{3}/, true],
['GGGG[W]WW', /\d{4}W\d{2}/, false],
['YYYYDDD', /\d{7}/, true]
];
// iso time formats and regexes
const isoTimes: [[string, RegExp]] = [
['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/],
['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/],
['HH:mm:ss', /\d\d:\d\d:\d\d/],
['HH:mm', /\d\d:\d\d/],
['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/],
['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/],
['HHmmss', /\d\d\d\d\d\d/],
['HHmm', /\d\d\d\d/],
['HH', /\d\d/]
];
const aspNetJsonRegex = /^\/?Date\((\-?\d+)/i;
const obsOffsets: { [key: string]: number } = {
UT: 0,
GMT: 0,
EDT: -4 * 60,
EST: -5 * 60,
CDT: -5 * 60,
CST: -6 * 60,
MDT: -6 * 60,
MST: -7 * 60,
PDT: -7 * 60,
PST: -8 * 60
};
// RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3
// tslint:disable-next-line
const rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/;
// date from iso format
export function configFromISO(config: DateParsingConfig): DateParsingConfig {
if (!isString(config._i)) {
return config;
}
const input = config._i;
const match = extendedIsoRegex.exec(input) || basicIsoRegex.exec(input);
let allowTime;
let dateFormat;
let timeFormat;
let tzFormat;
if (!match) {
config._isValid = false;
return config;
}
// getParsingFlags(config).iso = true;
let i;
let l;
for (i = 0, l = isoDates.length; i < l; i++) {
if (isoDates[i][1].exec(match[1])) {
dateFormat = isoDates[i][0];
allowTime = isoDates[i][2] !== false;
break;
}
}
if (dateFormat == null) {
config._isValid = false;
return config;
}
if (match[3]) {
for (i = 0, l = isoTimes.length; i < l; i++) {
if (isoTimes[i][1].exec(match[3])) {
// match[2] should be 'T' or space
timeFormat = (match[2] || ' ') + isoTimes[i][0];
break;
}
}
if (timeFormat == null) {
config._isValid = false;
return config;
}
}
if (!allowTime && timeFormat != null) {
config._isValid = false;
return config;
}
if (match[4]) {
if (tzRegex.exec(match[4])) {
tzFormat = 'Z';
} else {
config._isValid = false;
return config;
}
}
config._f = dateFormat + (timeFormat || '') + (tzFormat || '');
return configFromStringAndFormat(config);
}
// tslint:disable-next-line
function extractFromRFC2822Strings(yearStr: string, monthStr: string, dayStr: string, hourStr: string, minuteStr: string, secondStr: string): DateArray {
const result = [
untruncateYear(yearStr),
defaultLocaleMonthsShort.indexOf(monthStr),
parseInt(dayStr, 10),
parseInt(hourStr, 10),
parseInt(minuteStr, 10)
];
if (secondStr) {
result.push(parseInt(secondStr, 10));
}
return result;
}
function untruncateYear(yearStr: string): number {
const year = parseInt(yearStr, 10);
return year <= 49 ? year + 2000 : year;
}
function preprocessRFC2822(str: string): string {
// Remove comments and folding whitespace and replace multiple-spaces with a single space
return str
.replace(/\([^)]*\)|[\n\t]/g, ' ')
.replace(/(\s\s+)/g, ' ').trim();
}
function checkWeekday(weekdayStr: string, parsedInput: DateArray, config: DateParsingConfig): boolean {
if (weekdayStr) {
// TODO: Replace the vanilla JS Date object with an indepentent day-of-week check.
const weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr);
const weekdayActual = new Date(parsedInput[0], parsedInput[1], parsedInput[2]).getDay();
if (weekdayProvided !== weekdayActual) {
getParsingFlags(config).weekdayMismatch = true;
config._isValid = false;
return false;
}
}
return true;
}
function calculateOffset(obsOffset: string, militaryOffset: string, numOffset: string) {
if (obsOffset) {
return obsOffsets[obsOffset];
} else if (militaryOffset) {
// the only allowed military tz is Z
return 0;
} else {
const hm = parseInt(numOffset, 10);
const m = hm % 100;
const h = (hm - m) / 100;
return h * 60 + m;
}
}
// date and time from ref 2822 format
export function configFromRFC2822(config: DateParsingConfig): DateParsingConfig {
if (!isString(config._i)) {
return config;
}
const match = rfc2822.exec(preprocessRFC2822(config._i));
if (!match) {
return markInvalid(config);
}
const parsedArray = extractFromRFC2822Strings(match[4], match[3], match[2], match[5], match[6], match[7]);
if (!checkWeekday(match[1], parsedArray, config)) {
return config;
}
config._a = parsedArray;
config._tzm = calculateOffset(match[8], match[9], match[10]);
config._d = createUTCDate.apply(null, config._a);
config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
getParsingFlags(config).rfc2822 = true;
return config;
}
// date from iso format or fallback
export function configFromString(config: DateParsingConfig): DateParsingConfig {
if (!isString(config._i)) {
return config;
}
const matched = aspNetJsonRegex.exec(config._i);
if (matched !== null) {
config._d = new Date(+matched[1]);
return config;
}
// todo: update logic processing
// isISO -> configFromISO
// isRFC -> configFromRFC
configFromISO(config);
if (config._isValid === false) {
delete config._isValid;
} else {
return config;
}
configFromRFC2822(config);
if (config._isValid === false) {
delete config._isValid;
} else {
return config;
}
// Final attempt, use Input Fallback
// hooks.createFromInputFallback(config);
return createInvalid(config);
}
// hooks.createFromInputFallback = deprecate(
// 'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' +
// 'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' +
// 'discouraged and will be removed in an upcoming major release. Please refer to ' +
// 'http://momentjs.com/guides/#/warnings/js-date/ for more info.',
// function (config) {
// config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
// }
// );