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
JavaScript
"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