UNPKG

timezonecomplete

Version:

DateTime, TimeZone, Duration and Period library aimed at providing a consistent and complete date-time interface, away from the original JavaScript Date class.

713 lines 25.9 kB
"use strict"; /** * Copyright(c) 2014 ABB Switzerland Ltd. * * Functionality to parse a DateTime object to a string */ var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.parse = exports.parseable = void 0; var basics_1 = require("./basics"); var error_1 = require("./error"); var locale_1 = require("./locale"); var math_1 = require("./math"); var timezone_1 = require("./timezone"); var token_1 = require("./token"); /** * Checks if a given datetime string is according to the given format * @param dateTimeString The string to test * @param formatString LDML format string (see LDML.md) * @param allowTrailing Allow trailing string after the date+time * @param locale Locale-specific constants such as month names * @returns true iff the string is valid * @throws nothing */ function parseable(dateTimeString, formatString, allowTrailing, locale) { if (allowTrailing === void 0) { allowTrailing = true; } if (locale === void 0) { locale = {}; } try { parse(dateTimeString, formatString, undefined, allowTrailing, locale); return true; } catch (e) { return false; } } exports.parseable = parseable; /** * Parse the supplied dateTime assuming the given format. * * @param dateTimeString The string to parse * @param formatString The formatting string to be applied * @param overrideZone Use this zone in the result * @param allowTrailing Allow trailing characters in the source string * @param locale Locale-specific constants such as month names * @return string * @throws timezonecomplete.ParseError if the given dateTimeString is wrong or not according to the pattern * @throws timezonecomplete.Argument.FormatString if the given format string is invalid */ function parse(dateTimeString, formatString, overrideZone, allowTrailing, locale) { var _a; if (allowTrailing === void 0) { allowTrailing = true; } if (locale === void 0) { locale = {}; } if (!dateTimeString) { return (0, error_1.throwError)("ParseError", "no date given"); } if (!formatString) { return (0, error_1.throwError)("Argument.FormatString", "no format given"); } var mergedLocale = __assign(__assign({}, locale_1.DEFAULT_LOCALE), locale); var yearCutoff = (0, math_1.positiveModulo)((new Date().getFullYear() + 50), 100); try { var tokens = (0, token_1.tokenize)(formatString); var time = { year: undefined }; var zone = void 0; var pnr = void 0; var pzr = void 0; var dpr = void 0; var era = 1; var quarter = void 0; var remaining = dateTimeString; for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) { var token = tokens_1[_i]; switch (token.type) { case token_1.TokenType.ERA: _a = stripEra(token, remaining, mergedLocale), era = _a[0], remaining = _a[1]; break; case token_1.TokenType.QUARTER: { var r = stripQuarter(token, remaining, mergedLocale); quarter = r.n; remaining = r.remaining; } break; case token_1.TokenType.WEEKDAY: { remaining = stripWeekDay(token, remaining, mergedLocale); } break; case token_1.TokenType.WEEK: remaining = stripNumber(remaining, 2).remaining; break; // nothing to learn from this case token_1.TokenType.DAYPERIOD: dpr = stripDayPeriod(token, remaining, mergedLocale); remaining = dpr.remaining; break; case token_1.TokenType.YEAR: pnr = stripNumber(remaining, Infinity); remaining = pnr.remaining; if (token.length === 2) { if (pnr.n > yearCutoff) { time.year = 1900 + pnr.n; } else { time.year = 2000 + pnr.n; } } else { time.year = pnr.n; } break; case token_1.TokenType.MONTH: pnr = stripMonth(token, remaining, mergedLocale); remaining = pnr.remaining; time.month = pnr.n; break; case token_1.TokenType.DAY: pnr = stripNumber(remaining, 2); remaining = pnr.remaining; time.day = pnr.n; break; case token_1.TokenType.HOUR: pnr = stripHour(token, remaining); remaining = pnr.remaining; time.hour = pnr.n; break; case token_1.TokenType.MINUTE: pnr = stripNumber(remaining, 2); remaining = pnr.remaining; time.minute = pnr.n; break; case token_1.TokenType.SECOND: { pnr = stripSecond(token, remaining); remaining = pnr.remaining; switch (token.symbol) { case "s": time.second = pnr.n; break; case "S": time.milli = 1000 * parseFloat("0." + Math.floor(pnr.n).toString(10).slice(0, 3)); break; case "A": time.hour = Math.floor((pnr.n / 3600E3)); time.minute = Math.floor((0, math_1.positiveModulo)(pnr.n / 60E3, 60)); time.second = Math.floor((0, math_1.positiveModulo)(pnr.n / 1000, 60)); time.milli = (0, math_1.positiveModulo)(pnr.n, 1000); break; /* istanbul ignore next */ default: /* istanbul ignore next */ return (0, error_1.throwError)("ParseError", "unsupported second format '".concat(token.raw, "'")); } } break; case token_1.TokenType.ZONE: pzr = stripZone(token, remaining); remaining = pzr.remaining; zone = pzr.zone; break; /* istanbul ignore next */ default: case token_1.TokenType.IDENTITY: remaining = stripRaw(remaining, token.raw); break; } } if (dpr) { switch (dpr.type) { case "am": if (time.hour !== undefined && time.hour >= 12) { time.hour -= 12; } break; case "pm": if (time.hour !== undefined && time.hour < 12) { time.hour += 12; } break; case "noon": if (time.hour === undefined || time.hour === 0) { time.hour = 12; } if (time.minute === undefined) { time.minute = 0; } if (time.second === undefined) { time.second = 0; } if (time.milli === undefined) { time.milli = 0; } if (time.hour !== 12 || time.minute !== 0 || time.second !== 0 || time.milli !== 0) { return (0, error_1.throwError)("ParseError", "invalid time, contains 'noon' specifier but time differs from noon"); } break; case "midnight": if (time.hour === undefined || time.hour === 12) { time.hour = 0; } if (time.hour === 12) { time.hour = 0; } if (time.minute === undefined) { time.minute = 0; } if (time.second === undefined) { time.second = 0; } if (time.milli === undefined) { time.milli = 0; } if (time.hour !== 0 || time.minute !== 0 || time.second !== 0 || time.milli !== 0) { return (0, error_1.throwError)("ParseError", "invalid time, contains 'midnight' specifier but time differs from midnight"); } break; } } if (time.year !== undefined) { time.year *= era; } if (quarter !== undefined) { if (time.month === undefined) { switch (quarter) { case 1: time.month = 1; break; case 2: time.month = 4; break; case 3: time.month = 7; break; case 4: time.month = 10; break; } } else { var error_2 = false; switch (quarter) { case 1: error_2 = !(time.month >= 1 && time.month <= 3); break; case 2: error_2 = !(time.month >= 4 && time.month <= 6); break; case 3: error_2 = !(time.month >= 7 && time.month <= 9); break; case 4: error_2 = !(time.month >= 10 && time.month <= 12); break; } if (error_2) { return (0, error_1.throwError)("ParseError", "the quarter does not match the month"); } } } if (time.year === undefined) { time.year = 1970; } var result = { time: new basics_1.TimeStruct(time), zone: zone }; if (!result.time.validate()) { return (0, error_1.throwError)("ParseError", "invalid resulting date"); } // always overwrite zone with given zone if (overrideZone) { result.zone = overrideZone; } if (remaining && !allowTrailing) { return (0, error_1.throwError)("ParseError", "invalid date '".concat(dateTimeString, "' not according to format '").concat(formatString, "': trailing characters: '").concat(remaining, "'")); } return result; } catch (e) { return (0, error_1.throwError)("ParseError", "invalid date '".concat(dateTimeString, "' not according to format '").concat(formatString, "': ").concat(e.message)); } } exports.parse = parse; var WHITESPACE = [" ", "\t", "\r", "\v", "\n"]; /** * * @param token * @param s * @throws timezonecomplete.NotImplemented if a pattern is used that isn't implemented yet (z, Z, v, V, x, X) * @throws timezonecomplete.ParseError if the given string is not parseable */ function stripZone(token, s) { var unsupported = (token.symbol === "z") || (token.symbol === "Z" && token.length === 5) || (token.symbol === "v") || (token.symbol === "V" && token.length !== 2) || (token.symbol === "x" && token.length >= 4) || (token.symbol === "X" && token.length >= 4); if (unsupported) { return (0, error_1.throwError)("NotImplemented", "time zone pattern '" + token.raw + "' is not implemented"); } var result = { remaining: s }; // chop off "GMT" prefix if needed var hadGMT = false; if ((token.symbol === "Z" && token.length === 4) || token.symbol === "O") { if (result.remaining.toUpperCase().startsWith("GMT")) { result.remaining = result.remaining.slice(3); hadGMT = true; } } // parse any zone, regardless of specified format var zoneString = ""; while (result.remaining.length > 0 && WHITESPACE.indexOf(result.remaining.charAt(0)) === -1) { zoneString += result.remaining.charAt(0); result.remaining = result.remaining.substr(1); } zoneString = zoneString.trim(); if (zoneString) { // ensure chopping off GMT does not hide time zone errors (bit of a sloppy regex but OK) if (hadGMT && !zoneString.match(/[\+\-]?[\d\:]+/i)) { return (0, error_1.throwError)("ParseError", "invalid time zone 'GMT" + zoneString + "'"); } try { result.zone = timezone_1.TimeZone.zone(zoneString); } catch (e) { if ((0, error_1.errorIs)(e, ["Argument.S", "NotFound.Zone"])) { e = (0, error_1.error)("ParseError", e.message); } throw e; } } else { return (0, error_1.throwError)("ParseError", "no time zone given"); } return result; } /** * * @param s * @param expected * @throws timezonecomplete.ParseError */ function stripRaw(s, expected) { var remaining = s; var eremaining = expected; while (remaining.length > 0 && eremaining.length > 0 && remaining.charAt(0) === eremaining.charAt(0)) { remaining = remaining.substr(1); eremaining = eremaining.substr(1); } if (eremaining.length > 0) { return (0, error_1.throwError)("ParseError", "expected '".concat(expected, "'")); } return remaining; } /** * * @param token * @param remaining * @param locale * @throws timezonecomplete.ParseError */ function stripDayPeriod(token, remaining, locale) { var _a, _b, _c, _d, _e, _f; var offsets; switch (token.symbol) { case "a": switch (token.length) { case 4: offsets = (_a = {}, _a[locale.dayPeriodWide.am] = "am", _a[locale.dayPeriodWide.pm] = "pm", _a); break; case 5: offsets = (_b = {}, _b[locale.dayPeriodNarrow.am] = "am", _b[locale.dayPeriodNarrow.pm] = "pm", _b); break; default: offsets = (_c = {}, _c[locale.dayPeriodAbbreviated.am] = "am", _c[locale.dayPeriodAbbreviated.pm] = "pm", _c); break; } break; default: switch (token.length) { case 4: offsets = (_d = {}, _d[locale.dayPeriodWide.am] = "am", _d[locale.dayPeriodWide.midnight] = "midnight", _d[locale.dayPeriodWide.pm] = "pm", _d[locale.dayPeriodWide.noon] = "noon", _d); break; case 5: offsets = (_e = {}, _e[locale.dayPeriodNarrow.am] = "am", _e[locale.dayPeriodNarrow.midnight] = "midnight", _e[locale.dayPeriodNarrow.pm] = "pm", _e[locale.dayPeriodNarrow.noon] = "noon", _e); break; default: offsets = (_f = {}, _f[locale.dayPeriodAbbreviated.am] = "am", _f[locale.dayPeriodAbbreviated.midnight] = "midnight", _f[locale.dayPeriodAbbreviated.pm] = "pm", _f[locale.dayPeriodAbbreviated.noon] = "noon", _f); break; } break; } // match longest possible day period string; sort keys by length descending var sortedKeys = Object.keys(offsets) .sort(function (a, b) { return (a.length < b.length ? 1 : a.length > b.length ? -1 : 0); }); var upper = remaining.toUpperCase(); for (var _i = 0, sortedKeys_1 = sortedKeys; _i < sortedKeys_1.length; _i++) { var key = sortedKeys_1[_i]; if (upper.startsWith(key.toUpperCase())) { return { type: offsets[key], remaining: remaining.slice(key.length) }; } } return (0, error_1.throwError)("ParseError", "missing day period i.e. " + Object.keys(offsets).join(", ")); } /** * Returns factor -1 or 1 depending on BC or AD * @param token * @param remaining * @param locale * @returns [factor, remaining] * @throws timezonecomplete.ParseError */ function stripEra(token, remaining, locale) { var allowed; switch (token.length) { case 4: allowed = locale.eraWide; break; case 5: allowed = locale.eraNarrow; break; default: allowed = locale.eraAbbreviated; break; } var result = stripStrings(token, remaining, allowed); return [allowed.indexOf(result.chosen) === 0 ? 1 : -1, result.remaining]; } /** * * @param token * @param remaining * @param locale * @throws timezonecomplete.ParseError * @throws timezonecomplete.Argument.FormatString */ function stripQuarter(token, remaining, locale) { var quarterLetter; var quarterWord; var quarterAbbreviations; switch (token.symbol) { case "Q": quarterLetter = locale.quarterLetter; quarterWord = locale.quarterWord; quarterAbbreviations = locale.quarterAbbreviations; break; case "q": { quarterLetter = locale.standAloneQuarterLetter; quarterWord = locale.standAloneQuarterWord; quarterAbbreviations = locale.standAloneQuarterAbbreviations; break; } /* istanbul ignore next */ default: /* istanbul ignore next */ return (0, error_1.throwError)("Argument.FormatString", "invalid quarter pattern"); } var allowed; switch (token.length) { case 1: case 5: return stripNumber(remaining, 1); case 2: return stripNumber(remaining, 2); case 3: allowed = [1, 2, 3, 4].map(function (n) { return quarterLetter + n.toString(10); }); break; case 4: allowed = quarterAbbreviations.map(function (a) { return a + " " + quarterWord; }); break; /* istanbul ignore next */ default: /* istanbul ignore next */ return (0, error_1.throwError)("Argument.FormatString", "invalid quarter pattern"); } var r = stripStrings(token, remaining, allowed); return { n: allowed.indexOf(r.chosen) + 1, remaining: r.remaining }; } /** * * @param token * @param remaining * @param locale * @returns remaining string * @throws timezonecomplete.ParseError * @throws timezonecomplete.Argument.FormatString */ function stripWeekDay(token, remaining, locale) { var allowed; switch (token.length) { case 1: { if (token.symbol === "e") { return stripNumber(remaining, 1).remaining; } else { allowed = locale.shortWeekdayNames; } } break; case 2: { if (token.symbol === "e") { return stripNumber(remaining, 2).remaining; } else { allowed = locale.shortWeekdayNames; } } break; case 3: allowed = locale.shortWeekdayNames; break; case 4: allowed = locale.longWeekdayNames; break; case 5: allowed = locale.weekdayLetters; break; case 6: allowed = locale.weekdayTwoLetters; break; /* istanbul ignore next */ default: /* istanbul ignore next */ return (0, error_1.throwError)("Argument.FormatString", "invalid quarter pattern"); } var r = stripStrings(token, remaining, allowed); return r.remaining; } /** * * @param token * @param remaining * @param locale * @throws timezonecomplete.ParseError * @throws timezonecomplete.Argument.FormatString */ function stripMonth(token, remaining, locale) { var shortMonthNames; var longMonthNames; var monthLetters; switch (token.symbol) { case "M": shortMonthNames = locale.shortMonthNames; longMonthNames = locale.longMonthNames; monthLetters = locale.monthLetters; break; case "L": shortMonthNames = locale.standAloneShortMonthNames; longMonthNames = locale.standAloneLongMonthNames; monthLetters = locale.standAloneMonthLetters; break; /* istanbul ignore next */ default: /* istanbul ignore next */ return (0, error_1.throwError)("Argument.FormatString", "invalid month pattern"); } var allowed; switch (token.length) { case 1: case 2: return stripNumber(remaining, 2); case 3: allowed = shortMonthNames; break; case 4: allowed = longMonthNames; break; case 5: allowed = monthLetters; break; /* istanbul ignore next */ default: /* istanbul ignore next */ return (0, error_1.throwError)("Argument.FormatString", "invalid month pattern"); } var r = stripStrings(token, remaining, allowed); return { n: allowed.indexOf(r.chosen) + 1, remaining: r.remaining }; } /** * * @param token * @param remaining * @throws timezonecomplete.ParseError */ function stripHour(token, remaining) { var result = stripNumber(remaining, 2); switch (token.symbol) { case "h": if (result.n === 12) { result.n = 0; } break; case "H": // nothing, in range 0-23 break; case "K": // nothing, in range 0-11 break; case "k": result.n -= 1; break; } return result; } /** * * @param token * @param remaining * @throws timezonecomplete.ParseError * @throws timezonecomplete.Argument.FormatString */ function stripSecond(token, remaining) { switch (token.symbol) { case "s": return stripNumber(remaining, 2); case "S": return stripNumber(remaining, token.length); case "A": return stripNumber(remaining, 8); /* istanbul ignore next */ default: /* istanbul ignore next */ return (0, error_1.throwError)("Argument.FormatString", "invalid seconds pattern"); } } /** * * @param s * @param maxLength * @throws timezonecomplete.ParseError */ function stripNumber(s, maxLength) { var result = { n: NaN, remaining: s }; var numberString = ""; while (numberString.length < maxLength && result.remaining.length > 0 && result.remaining.charAt(0).match(/\d/)) { numberString += result.remaining.charAt(0); result.remaining = result.remaining.substr(1); } // remove leading zeroes while (numberString.charAt(0) === "0" && numberString.length > 1) { numberString = numberString.substr(1); } result.n = parseInt(numberString, 10); if (numberString === "" || !Number.isFinite(result.n)) { return (0, error_1.throwError)("ParseError", "expected a number but got '".concat(numberString, "'")); } return result; } /** * * @param token * @param remaining * @param allowed * @throws timezonecomplete.ParseError */ function stripStrings(token, remaining, allowed) { // match longest possible string; sort keys by length descending var sortedKeys = allowed.slice() .sort(function (a, b) { return (a.length < b.length ? 1 : a.length > b.length ? -1 : 0); }); var upper = remaining.toUpperCase(); for (var _i = 0, sortedKeys_2 = sortedKeys; _i < sortedKeys_2.length; _i++) { var key = sortedKeys_2[_i]; if (upper.startsWith(key.toUpperCase())) { return { chosen: key, remaining: remaining.slice(key.length) }; } } return (0, error_1.throwError)("ParseError", "invalid " + token_1.TokenType[token.type].toLowerCase() + ", expected one of " + allowed.join(", ")); } //# sourceMappingURL=parse.js.map