in globalize/globalize.ts [1044:1219]
parseExact = function (value, format, culture) {
// try to parse the date string by matching against the format string
// while using the specified culture for date field names.
value = trim(value);
var cal = culture.calendar,
// convert date formats into regular expressions with groupings.
// use the regexp to determine the input format and extract the date fields.
parseInfo = getParseRegExp(cal, format),
match = new RegExp(parseInfo.regExp).exec(value);
if (match === null) {
return null;
}
// found a date format that matches the input.
var groups = parseInfo.groups,
era = null, year = null, month = null, date = null, weekDay = null,
hour = 0, hourOffset, min = 0, sec = 0, msec = 0, tzMinOffset = null,
pmHour = false;
// iterate the format groups to extract and set the date fields.
for (var j = 0, jl = groups.length; j < jl; j++) {
var matchGroup = match[j + 1];
if (matchGroup) {
var current = groups[j],
clength = current.length,
matchInt = parseInt(matchGroup, 10);
switch (current) {
case "dd": case "d":
// Day of month.
date = matchInt;
// check that date is generally in valid range, also checking overflow below.
if (outOfRange(date, 1, 31)) return null;
break;
case "MMM": case "MMMM":
month = getMonthIndex(cal, matchGroup, clength === 3);
if (outOfRange(month, 0, 11)) return null;
break;
case "M": case "MM":
// Month.
month = matchInt - 1;
if (outOfRange(month, 0, 11)) return null;
break;
case "y": case "yy":
case "yyyy":
year = clength < 4 ? expandYear(cal, matchInt) : matchInt;
if (outOfRange(year, 0, 9999)) return null;
break;
case "h": case "hh":
// Hours (12-hour clock).
hour = matchInt;
if (hour === 12) hour = 0;
if (outOfRange(hour, 0, 11)) return null;
break;
case "H": case "HH":
// Hours (24-hour clock).
hour = matchInt;
if (outOfRange(hour, 0, 23)) return null;
break;
case "m": case "mm":
// Minutes.
min = matchInt;
if (outOfRange(min, 0, 59)) return null;
break;
case "s": case "ss":
// Seconds.
sec = matchInt;
if (outOfRange(sec, 0, 59)) return null;
break;
case "tt": case "t":
// AM/PM designator.
// see if it is standard, upper, or lower case PM. If not, ensure it is at least one of
// the AM tokens. If not, fail the parse for this format.
pmHour = cal.PM && (matchGroup === cal.PM[0] || matchGroup === cal.PM[1] || matchGroup === cal.PM[2]);
if (
!pmHour && (
!cal.AM || (matchGroup !== cal.AM[0] && matchGroup !== cal.AM[1] && matchGroup !== cal.AM[2])
)
) return null;
break;
case "f":
// Deciseconds.
case "ff":
// Centiseconds.
case "fff":
// Milliseconds.
msec = matchInt * Math.pow(10, 3 - clength);
if (outOfRange(msec, 0, 999)) return null;
break;
case "ddd":
// Day of week.
case "dddd":
// Day of week.
weekDay = getDayIndex(cal, matchGroup, clength === 3);
if (outOfRange(weekDay, 0, 6)) return null;
break;
case "zzz":
// Time zone offset in +/- hours:min.
var offsets = matchGroup.split(/:/);
if (offsets.length !== 2) return null;
hourOffset = parseInt(offsets[0], 10);
if (outOfRange(hourOffset, -12, 13)) return null;
var minOffset = parseInt(offsets[1], 10);
if (outOfRange(minOffset, 0, 59)) return null;
tzMinOffset = (hourOffset * 60) + (startsWith(matchGroup, "-") ? -minOffset : minOffset);
break;
case "z": case "zz":
// Time zone offset in +/- hours.
hourOffset = matchInt;
if (outOfRange(hourOffset, -12, 13)) return null;
tzMinOffset = hourOffset * 60;
break;
case "g": case "gg":
var eraName = matchGroup;
if (!eraName || !cal.eras) return null;
eraName = trim(eraName.toLowerCase());
for (var i = 0, l = cal.eras.length; i < l; i++) {
if (eraName === cal.eras[i].name.toLowerCase()) {
era = i;
break;
}
}
// could not find an era with that name
if (era === null) return null;
break;
}
}
}
var result = new Date(), defaultYear, convert = cal.convert;
defaultYear = convert ? convert.fromGregorian(result)[0] : result.getFullYear();
if (year === null) {
year = defaultYear;
}
else if (cal.eras) {
// year must be shifted to normal gregorian year
// but not if year was not specified, its already normal gregorian
// per the main if clause above.
year += cal.eras[(era || 0)].offset;
}
// set default day and month to 1 and January, so if unspecified, these are the defaults
// instead of the current day/month.
if (month === null) {
month = 0;
}
if (date === null) {
date = 1;
}
// now have year, month, and date, but in the culture's calendar.
// convert to gregorian if necessary
if (convert) {
result = convert.toGregorian(year, month, date);
// conversion failed, must be an invalid match
if (result === null) return null;
}
else {
// have to set year, month and date together to avoid overflow based on current date.
result.setFullYear(year, month, date);
// check to see if date overflowed for specified month (only checked 1-31 above).
if (result.getDate() !== date) return null;
// invalid day of week.
if (weekDay !== null && result.getDay() !== weekDay) {
return null;
}
}
// if pm designator token was found make sure the hours fit the 24-hour clock.
if (pmHour && hour < 12) {
hour += 12;
}
result.setHours(hour, min, sec, msec);
if (tzMinOffset !== null) {
// adjust timezone to utc before applying local offset.
var adjustedMin = result.getMinutes() - (tzMinOffset + result.getTimezoneOffset());
// Safari limits hours and minutes to the range of -127 to 127. We need to use setHours
// to ensure both these fields will not exceed this range. adjustedMin will range
// somewhere between -1440 and 1500, so we only need to split this into hours.
result.setHours(result.getHours() + parseInt((adjustedMin / 60).toString(), 10), adjustedMin % 60);
}
return result;
};