// file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
import {
+ abs,
bind,
call,
+ get8BitUnsignedIntegralItem,
+ getByteLength,
+ getFirstSubstringIndex,
getPrototype,
isArrayBuffer,
isFiniteNumber,
isIntegralNumber,
ITERATOR,
+ Matcher,
+ min,
objectCreate,
+ rawString,
sameValue,
+ set8BitIntegralItem,
+ stringCatenate,
+ stringIncludes,
+ stringPadEnd,
+ stringPadStart,
+ stringSlice,
+ stringSplit,
+ substring,
+ toExponentialNotation,
toFloat32,
+ toNumber,
type,
} from "../deps.js";
import { div, mod } from "./operators.js";
),
};
})();
+
+/**
+ * Maps each digit to its numerical value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-digitVal>.
+ *
+ * ☡ This function throws if the provided value is not a digit string.
+ *
+ * ※ This function is not exposed.
+ */
+const digitValue = (d) => +ensureMatches(lexicalDigit, d);
+
+/**
+ * Maps a sequence of digits to the position‐weighted sum of the terms
+ * numerical values.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-digitSeqVal>.
+ *
+ * ☡ This function throws if the provided value is not an iterable of
+ * digit strings.
+ *
+ * ※ Although elsewhere the X·S·D specification claims that sequences
+ * count from zero, this sequence clearly must count from one.
+ *
+ * ※ This function is not exposed.
+ */
+const digitSequenceValue = (S) => {
+ let sum = 0;
+ for (const S_i of S) {
+ sum = sum * 10 + digitValue(S_i);
+ }
+ return sum;
+};
+
+/**
+ * Maps a sequence of digits to the position‐weighted sum of the terms
+ * numerical values, weighted appropriately for fractional digits.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-fracDigitSeqVal>.
+ *
+ * ☡ This function throws if the provided value is not an iterable of
+ * digit strings.
+ *
+ * ※ The X·S·D specification erroneously specifies ·digitValue·(Si) −
+ * 10^(−i), but ·digitValue·(Si) × 10^(−i) is correct.
+ *
+ * ※ Although elsewhere the X·S·D specification claims that sequences
+ * count from zero, this sequence clearly must count from one.
+ *
+ * ※ This function is not exposed.
+ */
+const fractionDigitSequenceValue = (S) => {
+ let sum = 0;
+ let i = 0;
+ for (const S_i of S) {
+ sum += digitValue(S_i) * 10 ** --i;
+ }
+ return sum;
+};
+
+/**
+ * Maps a fracFrag to the appropriate fractional decimal number.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-fracFragVal>.
+ *
+ * ☡ This function throws if the provided value is not a fracFrag
+ * string.
+ *
+ * ※ This function is not exposed.
+ */
+//deno-lint-ignore no-unused-vars
+const fractionFragValue = (N) =>
+ fractionDigitSequenceValue(
+ literalSequence(ensureMatches(fracFrag, N)),
+ );
+
+/**
+ * Maps an unsignedNoDecimalPtNumeral to its numerical value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-unsNoDecVal>.
+ *
+ * ☡ This function throws if the provided value is not a
+ * unsignedNoDecimalPtNumeral string.
+ */
+export const unsignedNoDecimalMap = (N) =>
+ digitSequenceValue(
+ literalSequence(ensureMatches(unsignedNoDecimalPtNumeral, N)),
+ );
+
+/**
+ * Maps an noDecimalPtNumeral to its numerical value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-noDecVal>.
+ *
+ * ☡ This function throws if the provided value is not a
+ * noDecimalPtNumeral string.
+ */
+export const noDecimalMap = (N) => {
+ switch (ensureMatches(noDecimalPtNumeral, N)[0]) {
+ case "-":
+ return -unsignedNoDecimalMap(substring(N, 1)) || 0;
+ case "+":
+ return unsignedNoDecimalMap(substring(N, 1));
+ default:
+ return unsignedNoDecimalMap(N);
+ }
+};
+
+/**
+ * Maps an unsignedDecimalPtNumeral to its numerical value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-unsDecVal>.
+ *
+ * ☡ This function throws if the provided value is not a
+ * unsignedDecimalPtNumeral string.
+ *
+ * ※ This function makes use of the built·in Ecmascript float parsing
+ * to avoid rounding errors.
+ */
+export const unsignedDecimalPtMap = (D) => {
+ // const N = substring(
+ // ensureMatches(unsignedDecimalPtNumeral, D),
+ // 0,
+ // getFirstSubstringIndex(D, "."),
+ // );
+ // const F = substring(D, N.length + 1);
+ // if (F === "") {
+ // return unsignedNoDecimalMap(N);
+ // } else if (N === "") {
+ // return fractionFragValue(F);
+ // } else {
+ // return unsignedNoDecimalMap(N) + fractionFragValue(F);
+ // }
+ return stringToFloat(ensureMatches(unsignedDecimalPtNumeral, D));
+};
+
+/**
+ * Maps an decimalPtNumeral to its numerical value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-decVal>.
+ *
+ * ☡ This function throws if the provided value is not a
+ * decimalPtNumeral string.
+ */
+export const decimalPtMap = (N) => {
+ switch (ensureMatches(decimalPtNumeral, N)[0]) {
+ case "-":
+ return -unsignedDecimalPtMap(substring(N, 1)) || 0;
+ case "+":
+ return unsignedDecimalPtMap(substring(N, 1));
+ default:
+ return unsignedDecimalPtMap(N);
+ }
+};
+
+/**
+ * Maps a scientificNotationNumeral to its numerical value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-sciVal>.
+ *
+ * ☡ This function throws if the provided value is not a
+ * scientificNotationNumeral string.
+ *
+ * ※ The X·S·D specification erroneously specifies
+ * ·unsignedDecimalPtMap·(E), but ·noDecimalMap·(E) is correct.
+ *
+ * ※ This function makes use of the built·in Ecmascript float parsing
+ * to avoid rounding errors.
+ */
+export const scientificMap = (N) => {
+ // const C = substring(
+ // ensureMatches(scientificNotationNumeral, N),
+ // 0,
+ // (() => {
+ // let index = 0;
+ // for (const char of literalSequence(N)) {
+ // if (char === "e" || char === "E") {
+ // return index;
+ // } else {
+ // ++index;
+ // continue;
+ // }
+ // }
+ // })(),
+ // );
+ // const E = substring(N, C.length + 1);
+ // return getFirstSubstringIndex(N, ".") !== -1
+ // ? decimalPtMap(C) * 10 ** noDecimalMap(E)
+ // : noDecimalMap(C) * 10 ** noDecimalMap(E);
+ return stringToFloat(ensureMatches(scientificNotationNumeral, N)) ||
+ 0;
+};
+
+/**
+ * Maps each integer between 0 and 9 to the corresponding digit.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-digit>.
+ *
+ * ☡ This function throws if the provided value is not an integral
+ * number between 0 and 9 inclusive.
+ *
+ * ※ This function is not exposed.
+ */
+const digit = (i) => {
+ if (!(isIntegralNumber(i) && i >= 0 && i <= 9)) {
+ throw new TypeError(
+ `सूत्र: Expected an integral number between 0 and 9 inclusive, but got: ${i}.`,
+ );
+ } else {
+ return `${i}`;
+ }
+};
+
+/**
+ * Maps each nonnegative integer to a sequence of integers used by
+ * ·digitSeq· to ultimately create an unsignedNoDecimalPtNumeral.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-digitRemSeq>.
+ *
+ * ☡ This function throws if the provided value is not a nonnegative
+ * integer.
+ *
+ * ※ The sequence produced by this function is infinite.
+ *
+ * ※ This function is not exposed.
+ */
+const digitRemainderSeq = (i) => {
+ nonnegativeInteger(i);
+ return generatorSequence(function* () {
+ let s_j = i;
+ while (true) {
+ yield s_j;
+ s_j = div(s_j, 10);
+ }
+ });
+};
+
+/**
+ * Maps each nonnegative integer to a sequence of integers used by
+ * ·unsignedNoDecimalPtCanonicalMap· to create an
+ * unsignedNoDecimalPtNumeral.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-digitSeq>.
+ *
+ * ☡ This function throws if the provided value is not a nonnegative
+ * integer.
+ *
+ * ※ The sequence produced by this function is infinite.
+ *
+ * ※ This function is not exposed.
+ */
+const digitSeq = (i) => {
+ nonnegativeInteger(i);
+ return generatorSequence(function* () {
+ for (const i_j of digitRemainderSeq(i)) {
+ yield mod(i_j, 10);
+ }
+ });
+};
+
+/**
+ * Maps a sequence of nonnegative integers to the index before the
+ * first zero term.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-lastSigDigit>.
+ *
+ * ☡ This function throws if the values of the provided sequence are
+ * not nonnegative integers.
+ *
+ * ※ The X·S·D specification erroneously describes this function as
+ * giving the index *of* the first zero term, but it is actually the
+ * index of the last nonzero term.
+ *
+ * ※ The X·S·D specification erroneously describes this function as
+ * taking a sequence of nonnegative integers, but it is called with
+ * ·FractionDigitRemainderSeq·, which is a list of nonnegative decimal
+ * numbers.
+ *
+ * ※ This function is not exposed.
+ */
+const lastSignificantDigit = (s) => {
+ let j = 0;
+ for (const s_j of s) {
+ if (nonnegativeDecimalNumber(s_j) === 0) {
+ return j === 0 ? 0 : j - 1;
+ } else {
+ ++j;
+ continue;
+ }
+ }
+};
+
+/**
+ * Maps each nonnegative decimal number less than 1 to a sequence of
+ * decimal numbers used by ·fractionDigitSeq· to ultimately create an
+ * unsignedNoDecimalPtNumeral.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-fracDigitRemSeq>.
+ *
+ * ☡ This function throws if the provided value is not a nonnegative
+ * finite number less than 1.
+ *
+ * ※ The X·S·D specification erroneously specifies s_0 = f − 10 and
+ * s_(j+1) = (s_j ·mod· 1) − 10, but s_0 = f × 10 and s_(j+1) = (s_j
+ * ·mod· 1) × 10 is correct.
+ *
+ * ※ The implementation of this function uses string operations
+ * because Ecmascript does not (currently) have a lossless decimal
+ * type.
+ *
+ * ※ The sequence produced by this function is infinite.
+ *
+ * ※ This function is not exposed.
+ */
+const FractionDigitRemainderSeq = (f) => {
+ if (!(isFiniteNumber(f) && f >= 0 && f < 1)) {
+ throw new TypeError(
+ `सूत्र: Expected a nonnegative finite number less than 1, but got: ${f}.`,
+ );
+ } else {
+ return generatorSequence(function* () {
+ let s_j = f * 10;
+ while (true) {
+ yield s_j;
+ s_j = mod(s_j, 1) * 10;
+ }
+ });
+ }
+};
+
+/**
+ * Maps each nonnegative decimal number less than 1 to a sequence of
+ * integers used by ·fractionDigitsCanonicalFragmentMap· to ultimately
+ * create an unsignedNoDecimalPtNumeral.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-fracDigitSeq>.
+ *
+ * ☡ This function throws if the provided value is not a nonnegative
+ * finite number less than 1.
+ *
+ * ※ The sequence produced by this function is infinite.
+ *
+ * ※ This function is not exposed.
+ */
+const fractionDigitSeq = (f) => {
+ if (!(isFiniteNumber(f) && f >= 0 && f < 1)) {
+ throw new TypeError(
+ `सूत्र: Expected a nonnegative finite number less than 1, but got: ${f}.`,
+ );
+ } else {
+ return generatorSequence(function* () {
+ for (const s_j of FractionDigitRemainderSeq(f)) {
+ yield div(s_j, 1);
+ }
+ });
+ }
+};
+
+/**
+ * Maps each nonnegative decimal number less than 1 to a ·literal· used
+ * by ·unsignedDecimalPtCanonicalMap· to create an
+ * unsignedDecimalPtNumeral.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-fracDigitsMap>.
+ *
+ * ☡ This function throws if the provided value is not nonnegative and
+ * less than 1.
+ *
+ * ※ This function is not exposed.
+ */
+//deno-lint-ignore no-unused-vars
+const fractionDigitsCanonicalFragmentMap = (f) =>
+ stringCatenate(...generatorSequence(function* () {
+ const l = lastSignificantDigit(FractionDigitRemainderSeq(f));
+ let j = 0;
+ for (const f_j of fractionDigitSeq(f)) {
+ yield digit(f_j);
+ if (j === l) {
+ break;
+ } else {
+ ++j;
+ continue;
+ }
+ }
+ }));
+
+/**
+ * Maps a nonnegative integer to a unsignedNoDecimalPtNumeral, its
+ * ·canonical representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-unsNoDecCanFragMap>.
+ *
+ * ☡ This function throws if the provided value is not a nonnegative
+ * integer.
+ */
+export const unsignedNoDecimalPtCanonicalMap = (i) => {
+ nonnegativeInteger(i);
+ return stringCatenate(...generatorSequence(function* () {
+ const l = lastSignificantDigit(digitRemainderSeq(i));
+ const digits = new Array(l + 1);
+ let j = 0;
+ for (const i_j of digitSeq(i)) {
+ digits[l - j] = digit(i_j);
+ if (j === l) {
+ break;
+ } else {
+ ++j;
+ continue;
+ }
+ }
+ for (j = 0; j <= l; ++j) {
+ yield digits[j];
+ }
+ }));
+};
+
+/**
+ * Maps an integer to a noDecimalPtNumeral, its ·canonical
+ * representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-noDecCanMap>.
+ *
+ * ☡ This function throws if the provided value is not an integer.
+ */
+export const noDecimalPtCanonicalMap = (i) =>
+ integer(i) < 0
+ ? stringCatenate("-", unsignedNoDecimalPtCanonicalMap(-i))
+ : unsignedNoDecimalPtCanonicalMap(i);
+
+/**
+ * Maps a nonnegative decimal number to a unsignedDecimalPtNumeral, its
+ * ·canonical representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-unsDecCanFragMap>.
+ *
+ * ☡ This function throws if the provided value is not a nonnegative
+ * finite number.
+ *
+ * ※ This function makes use of the built·in Ecmascript float
+ * serialization to avoid rounding errors.
+ */
+export const unsignedDecimalPtCanonicalMap = (n) => {
+ // nonnegativeDecimalNumber(n);
+ // return stringCatenate(
+ // unsignedNoDecimalPtCanonicalMap(div(n, 1)),
+ // ".",
+ // fractionDigitsCanonicalFragmentMap(mod(n, 1)),
+ // );
+ const exp = toExponentialNotation(nonnegativeDecimalNumber(n));
+ const eIndex = getFirstSubstringIndex(exp, "e");
+ const exponent = +substring(exp, eIndex + 1);
+ const zeroPaddedMantissa = stringPadEnd(
+ stringPadStart(
+ exp[0],
+ -exponent + 1,
+ "0",
+ ) + (eIndex > 1 ? substring(exp, 2, eIndex) : ""),
+ exponent + 2,
+ "0",
+ );
+ const decimalPoint = exponent < 0 ? 1 : exponent + 1;
+ return stringCatenate(
+ substring(zeroPaddedMantissa, 0, decimalPoint),
+ ".",
+ substring(zeroPaddedMantissa, decimalPoint),
+ );
+};
+
+/**
+ * Maps a decimal number to a decimalPtNumeral, its ·canonical
+ * representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-decCanFragMap>.
+ *
+ * ☡ This function throws if the provided value is not a finite number.
+ */
+export const decimalPtCanonicalMap = (n) =>
+ decimalNumber(n) < 0
+ ? stringCatenate("-", unsignedDecimalPtCanonicalMap(-n))
+ : unsignedDecimalPtCanonicalMap(n);
+
+/**
+ * Maps a nonnegative decimal number to a
+ * unsignedScientificNotationNumeral, its ·canonical representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-unsSciCanFragMap>.
+ *
+ * ☡ This function throws if the provided value is not a nonnegative
+ * finite number.
+ *
+ * ※ This function makes use of the built·in Ecmascript float
+ * serialization to avoid rounding errors.
+ */
+export const unsignedScientificCanonicalMap = (n) => {
+ // nonnegativeDecimalNumber(n);
+ // return stringCatenate(
+ // unsignedDecimalPtCanonicalMap(n / 10 ** div(log10(n), 1)),
+ // "E",
+ // noDecimalPtCanonicalMap(div(log10(n), 1)),
+ // );
+ const exp = toExponentialNotation(nonnegativeDecimalNumber(n));
+ const [mantissa, exponent] = stringSplit(exp, "e");
+ return stringCatenate(
+ mantissa,
+ stringIncludes(mantissa, ".") ? "E" : ".0E",
+ exponent[0] === "+" ? substring(exponent, 1) : exponent,
+ );
+};
+
+/**
+ * Maps a decimal number to a scientificNotationNumeral, its ·canonical
+ * representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-sciCanFragMap>.
+ *
+ * ☡ This function throws if the provided value is not a finite number.
+ */
+export const scientificCanonicalMap = (n) =>
+ decimalNumber(n) < 0
+ ? stringCatenate("-", unsignedScientificCanonicalMap(-n))
+ : unsignedScientificCanonicalMap(n);
+
+/**
+ * Maps the ·lexical representations· of ·special values· used with
+ * some numerical datatypes to those ·special values·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-specRepVal>.
+ *
+ * ☡ This function throws if the provided value is not a
+ * numericalSpecialRep string.
+ */
+export const specialRepValue = (c) => {
+ switch (ensureMatches(numericalSpecialRep, c)) {
+ case "INF":
+ case "+INF":
+ return positiveInfinity;
+ case "-INF":
+ return negativeInfinity;
+ case "NaN":
+ return notANumber;
+ }
+};
+
+/**
+ * Maps the ·special values· used with some numerical datatypes to
+ * their ·canonical representations·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-specValCanMap>.
+ *
+ * ☡ This function throws if the provided value is not nan or positive
+ * or negative infinity.
+ */
+export const specialRepCanonicalMap = (c) => {
+ if (sameValue(c, positiveInfinity)) {
+ return "INF";
+ } else if (sameValue(c, negativeInfinity)) {
+ return "-INF";
+ } else if (sameValue(c, notANumber)) {
+ return "NaN";
+ } else {
+ throw new TypeError(
+ `सूत्र: Expected a special value, but got: ${c}.`,
+ );
+ }
+};
+
+/**
+ * Maps a decimalLexicalRep onto a decimal value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-decimalLexmap>.
+ *
+ * ☡ This function throws if the provided value is not a
+ * decimalLexicalRep string.
+ */
+export const decimalLexicalMap = (LEX) => {
+ ensureMatches(decimalLexicalRep, LEX);
+ if (noDecimalPtNumeral(LEX)) {
+ return noDecimalMap(LEX);
+ } else if (decimalPtNumeral(LEX)) {
+ return decimalPtMap(LEX);
+ }
+};
+
+/**
+ * Maps a decimal to its ·canonical representation·, a
+ * decimalLexicalRep.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-decimalCanmap>.
+ *
+ * ☡ This function throws if the provided value is not a finite number.
+ */
+export const decimalCanonicalMap = (d) =>
+ isIntegralNumber(decimalNumber(d))
+ ? noDecimalPtCanonicalMap(d)
+ : decimalPtCanonicalMap(d);
+
+/**
+ * Rounds a non-zero decimal number to the nearest floating-point
+ * value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-floatPtRound>.
+ *
+ * ☡ This function throws if the provided value is zero or is not a
+ * finite number.
+ *
+ * ※ This function uses native Ecmascript float rounding methods and
+ * only supports a cWidth of 24 or 53.
+ *
+ * ※ This function is not exposed.
+ */
+const floatingPointRound = (nV, cWidth, eMin, eMax) => {
+ if (!isFiniteNumber(nV) || nV == 0) {
+ throw new TypeError(
+ `सूत्र: Expected a finite nonzero number, but got: ${nV}.`,
+ );
+ } else if (cWidth !== 24 && cWidth !== 53) {
+ throw new TypeError(
+ `सूत्र: Unsupported cWidth: ${cWidth}.`,
+ );
+ } else if (
+ cWidth === 24 && (eMin !== -149 || eMax !== 104) ||
+ cWidth === 53 && (eMin !== -1074 || eMax !== 971)
+ ) {
+ throw new TypeError(
+ `सूत्र: Unsupported eMin and eMax for cWidth: ${cWidth}.`,
+ );
+ } else {
+ return cWidth <= 24 ? toFloat32(nV) : +nV;
+ }
+};
+
+/**
+ * Maps a decimal number to that value rounded by some power of 10.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-round>.
+ *
+ * ☡ This function throws if the provided value is not a finite number.
+ *
+ * ☡ This function throws if the provided power of 10 is not a
+ * nonnegative integer.
+ *
+ * ※ This function is not exposed.
+ */
+const round = (n, k) => {
+ decimalNumber(n);
+ nonnegativeInteger(k);
+ return div(n / 10 ** k + 0.5, 1) * 10 ** k;
+};
+
+/**
+ * Maps a decimal number (c × 10e) to successive approximations.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-round>.
+ *
+ * ☡ This function throws if the provided values are not a nonnegative
+ * integer, an integer, an a nonnegative integer.
+ *
+ * ※ This function is not exposed.
+ */
+//deno-lint-ignore no-unused-vars
+const floatApprox = (c, e, j) => {
+ nonnegativeInteger(c);
+ integer(e);
+ nonnegativeInteger(j);
+ return round(c, j) * 10 ** e;
+};
+
+/**
+ * Maps a floatRep onto a float value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-floatLexmap>.
+ *
+ * ☡ This function throws if the provided value is not a floatRep
+ * string.
+ */
+export const floatLexicalMap = (LEX) => {
+ ensureMatches(floatRep, LEX);
+ if (numericalSpecialRep(LEX)) {
+ return specialRepValue(LEX);
+ } else {
+ const nV = noDecimalPtNumeral(LEX)
+ ? noDecimalMap(LEX)
+ : decimalPtNumeral(LEX)
+ ? decimalPtMap(LEX)
+ : scientificMap(LEX);
+ const v = nV == 0 ? 0 : floatingPointRound(nV, 24, -149, 104);
+ return v == 0 ? LEX[0] === "-" ? negativeZero : positiveZero : v;
+ }
+};
+
+/**
+ * Maps a doubleRep onto a double value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-doubleLexmap>.
+ *
+ * ☡ This function throws if the provided value is not a doubleRep
+ * string.
+ */
+export const doubleLexicalMap = (LEX) => {
+ ensureMatches(doubleRep, LEX);
+ if (numericalSpecialRep(LEX)) {
+ return specialRepValue(LEX);
+ } else {
+ const nV = noDecimalPtNumeral(LEX)
+ ? noDecimalMap(LEX)
+ : decimalPtNumeral(LEX)
+ ? decimalPtMap(LEX)
+ : scientificMap(LEX);
+ const v = nV == 0 ? 0 : floatingPointRound(nV, 53, -1074, 971);
+ return v == 0 ? LEX[0] === "-" ? negativeZero : positiveZero : v;
+ }
+};
+
+/**
+ * Maps a float to its ·canonical representation·, a floatRep.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-floatCanmap>.
+ *
+ * ☡ This function throws if the provided value is not a 32‐bit float.
+ */
+export const floatCanonicalMap = (f) => {
+ float(f);
+ return f === positiveInfinity || f === negativeInfinity ||
+ sameValue(f, notANumber)
+ ? specialRepCanonicalMap(f)
+ : sameValue(f, 0)
+ ? "0.0E0"
+ : sameValue(f, -0)
+ ? "-0.0E0"
+ : scientificCanonicalMap(f);
+};
+
+/**
+ * Maps a double to its ·canonical representation·, a doubleRep.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-doubleCanmap>.
+ *
+ * ☡ This function throws if the provided value is not a number.
+ */
+export const doubleCanonicalMap = (f) => {
+ double(f);
+ return f === positiveInfinity || f === negativeInfinity ||
+ sameValue(f, notANumber)
+ ? specialRepCanonicalMap(f)
+ : sameValue(f, 0)
+ ? "0.0E0"
+ : sameValue(f, -0)
+ ? "-0.0E0"
+ : scientificCanonicalMap(f);
+};
+
+/**
+ * Maps a duYearFrag to an integer, intended as part of the value of
+ * the ·months· property of a duration value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-duYrMap>.
+ *
+ * ☡ This function throws if the provided value is not a duYearFrag
+ * string.
+ *
+ * ※ The X·S·D specification erroneously specifies the numeral as
+ * following the letter, but it precedes it.
+ *
+ * ※ This function is not exposed.
+ */
+const duYearFragmentMap = (Y) =>
+ noDecimalMap(stringSlice(ensureMatches(duYearFrag, Y), 0, -1));
+
+/**
+ * Maps a duMonthFrag to an integer, intended as part of the value of
+ * the ·months· property of a duration value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-duMoMap>.
+ *
+ * ☡ This function throws if the provided value is not a duMonthFrag
+ * string.
+ *
+ * ※ The X·S·D specification erroneously specifies the numeral as
+ * following the letter, but it precedes it.
+ *
+ * ※ This function is not exposed.
+ */
+const duMonthFragmentMap = (M) =>
+ noDecimalMap(stringSlice(ensureMatches(duMonthFrag, M), 0, -1));
+
+/**
+ * Maps a duDayFrag to an integer, intended as part of the value of the
+ * ·seconds· property of a duration value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-duDaMap>.
+ *
+ * ☡ This function throws if the provided value is not a duDayFrag
+ * string.
+ *
+ * ※ The X·S·D specification erroneously specifies the numeral as
+ * following the letter, but it precedes it.
+ *
+ * ※ This function is not exposed.
+ */
+const duDayFragmentMap = (D) =>
+ noDecimalMap(stringSlice(ensureMatches(duDayFrag, D), 0, -1));
+
+/**
+ * Maps a duHourFrag to an integer, intended as part of the value of
+ * the ·seconds· property of a duration value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-duHrMap>.
+ *
+ * ☡ This function throws if the provided value is not a duHourFrag
+ * string.
+ *
+ * ※ The X·S·D specification erroneously specifies the numeral as
+ * following the letter, but it precedes it.
+ *
+ * ※ The X·S·D specification has a copypasta typo in the definition of
+ * this function; it takes an argument H which necessarily is followed
+ * by an "H". There is no D which necessarily begins with "D".
+ *
+ * ※ This function is not exposed.
+ */
+const duHourFragmentMap = (H) =>
+ noDecimalMap(stringSlice(ensureMatches(duHourFrag, H), 0, -1));
+
+/**
+ * Maps a duMinuteFrag to an integer, intended as part of the value of
+ * the ·seconds· property of a duration value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-duMiMap>.
+ *
+ * ☡ This function throws if the provided value is not a duMinuteFrag
+ * string.
+ *
+ * ※ The X·S·D specification erroneously specifies the numeral as
+ * following the letter, but it precedes it.
+ *
+ * ※ This function is not exposed.
+ */
+const duMinuteFragmentMap = (M) =>
+ noDecimalMap(stringSlice(ensureMatches(duMinuteFrag, M), 0, -1));
+
+/**
+ * Maps a duSecondFrag to an integer, intended as part of the value of
+ * the ·seconds· property of a duration value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-duSeMap>.
+ *
+ * ☡ This function throws if the provided value is not a duMinuteFrag
+ * string.
+ *
+ * ※ The X·S·D specification erroneously specifies the numeral as
+ * following the letter, but it precedes it.
+ *
+ * ※ This function is not exposed.
+ */
+const duSecondFragmentMap = (S) =>
+ getFirstSubstringIndex(ensureMatches(duSecondFrag, S), ".") !== -1
+ ? decimalPtMap(stringSlice(S, 0, -1))
+ : noDecimalMap(stringSlice(S, 0, -1));
+
+/**
+ * Maps a duYearMonthFrag into an integer, intended as part of the
+ * ·months· property of a duration value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-duYMMap>.
+ *
+ * ☡ This function throws if the provided value is not a
+ * duYearMonthFrag string.
+ *
+ * ※ This function is not exposed.
+ */
+const duYearMonthFragmentMap = (YM) => {
+ let Y, M;
+ new Matcher(
+ rawString`(?<Y>\d+Y)?(?<M>\d+M)?`,
+ undefined,
+ (_, { groups }) => {
+ Y = groups.Y;
+ M = groups.M;
+ return true;
+ },
+ )(ensureMatches(duYearMonthFrag, YM));
+ const y = Y ? duYearFragmentMap(Y) : 0;
+ const m = M ? duMonthFragmentMap(M) : 0;
+ return 12 * y + m;
+};
+
+/**
+ * Maps a duTimeFrag into an integer, intended as part of the ·seconds·
+ * property of a duration value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-duTMap>.
+ *
+ * ☡ This function throws if the provided value is not a duTimeFrag
+ * string.
+ *
+ * ※ The X·S·D specification erroneously applies ·duDayFragmentMap· to
+ * H; ·duHourFragmentMap· is correct.
+ *
+ * ※ This function is not exposed.
+ */
+const duTimeFragmentMap = (T) => {
+ let H, M, S;
+ new Matcher(
+ rawString`T(?<H>\d+H)?(?<M>\d+M)?(?<S>[\d.]+S)?`,
+ undefined,
+ (_, { groups }) => {
+ H = groups.H;
+ M = groups.M;
+ S = groups.S;
+ return true;
+ },
+ )(ensureMatches(duTimeFrag, T));
+ const h = H ? duHourFragmentMap(H) : 0;
+ const m = M ? duMinuteFragmentMap(M) : 0;
+ const s = S ? duSecondFragmentMap(S) : 0;
+ return 3600 * h + 60 * m + s;
+};
+
+/**
+ * Maps a duDayTimeFrag into a decimal number, which is the potential
+ * value of the ·seconds· property of a duration value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-duDTMap>.
+ *
+ * ☡ This function throws if the provided value is not a duDayTimeFrag
+ * string.
+ *
+ * ※ This function is not exposed.
+ */
+const duDayTimeFragmentMap = (DT) => {
+ let D, T;
+ new Matcher(
+ rawString`(?<D>\d+D)?(?<T>T.+)?`,
+ undefined,
+ (_, { groups }) => {
+ D = groups.D;
+ T = groups.T;
+ return true;
+ },
+ )(ensureMatches(duDayTimeFrag, DT));
+ const d = D ? duDayFragmentMap(D) : 0;
+ const t = T ? duTimeFragmentMap(T) : 0;
+ return 86400 * d + t;
+};
+
+/**
+ * Separates the durationLexicalRep into the month part and the seconds
+ * part, then maps them into the ·months· and ·seconds· of the duration
+ * value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-durationMap>.
+ *
+ * ☡ This function throws if the provided value is not a
+ * durationLexicalRep string.
+ */
+export const durationMap = (DUR) => {
+ let Y, D;
+ new Matcher(
+ rawString`-?P(?<Y>(?:\d+Y)?(?:\d+M)?)(?<D>(?:\d+D)?(?:T.+)?)`,
+ undefined,
+ (_, { groups }) => {
+ Y = groups.Y;
+ D = groups.D;
+ return true;
+ },
+ )(ensureMatches(durationLexicalRep, DUR));
+ const s = DUR[0] === "-" ? -1 : 1;
+ return {
+ months: Y ? s * duYearMonthFragmentMap(Y) + 0 : 0, // cast −0 to 0
+ seconds: D ? s * duDayTimeFragmentMap(D) + 0 : 0, // cast −0 to 0
+ };
+};
+
+/**
+ * Maps the lexical representation into the ·months· of a
+ * yearMonthDuration value. (A yearMonthDuration's ·seconds· is always
+ * zero.) ·yearMonthDurationMap· is a restriction of ·durationMap·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-yearMonthDurationMap>.
+ *
+ * ☡ This function throws if the provided value is not a
+ * yearMonthDurationLexicalRep string.
+ */
+export const yearMonthDurationMap = (YM) => {
+ const s = YM[0] === "-" ? -1 : 1;
+ const Y = substring(
+ ensureMatches(yearMonthDurationLexicalRep, YM),
+ s === -1 ? 2 : 1,
+ );
+ return {
+ months: s * duYearMonthFragmentMap(Y) + 0, // cast −0 to 0
+ seconds: 0,
+ };
+};
+
+/**
+ * Maps the lexical representation into the ·seconds· of a
+ * dayTimeDuration value. (A dayTimeDuration's ·months· is always
+ * zero.) ·dayTimeDurationMap· is a restriction of ·durationMap·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-yearMonthDurationMap>.
+ *
+ * ☡ This function throws if the provided value is not a
+ * dayTimeDurationLexicalRep string.
+ *
+ * ※ The X·S·D specification erroneously describes the argument as a
+ * dayTimeDuration value rather than a dayTimeDurationLexicalRep.
+ */
+export const dayTimeDurationMap = (DT) => {
+ const s = DT[0] === "-" ? -1 : 1;
+ const D = substring(
+ ensureMatches(dayTimeDurationLexicalRep, DT),
+ s === -1 ? 2 : 1,
+ );
+ return {
+ months: 0,
+ seconds: s * duDayTimeFragmentMap(D) + 0, // cast −0 to 0
+ };
+};
+
+/**
+ * Maps a nonnegative integer, presumably the absolute value of the
+ * ·months· of a duration value, to a duYearMonthFrag, a fragment of a
+ * duration ·lexical representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-duYMCan>.
+ *
+ * ☡ This function throws if the provided value is not a nonnegative
+ * integer.
+ *
+ * ※ This function is not exposed.
+ */
+const duYearMonthCanonicalFragmentMap = (ym) => {
+ nonnegativeInteger(ym);
+ const y = div(ym, 12);
+ const m = mod(ym, 12);
+ return y !== 0 && m !== 0
+ ? stringCatenate(
+ unsignedNoDecimalPtCanonicalMap(y),
+ "Y",
+ unsignedNoDecimalPtCanonicalMap(m),
+ "M",
+ )
+ : y !== 0
+ ? stringCatenate(
+ unsignedNoDecimalPtCanonicalMap(y),
+ "Y",
+ )
+ : stringCatenate(
+ unsignedNoDecimalPtCanonicalMap(m),
+ "M",
+ );
+};
+
+/**
+ * Maps a nonnegative integer, presumably the day normalized value from
+ * the ·seconds· of a duration value, to a duDayFrag, a fragment of a
+ * duration ·lexical representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-duDCan>.
+ *
+ * ☡ This function throws if the provided value is not a nonnegative
+ * integer.
+ *
+ * ※ Despite the description, this function does not necessarily
+ * return a duDayFrag (it can return an empty string).
+ *
+ * ※ This function is not exposed.
+ */
+const duDayCanonicalFragmentMap = (d) =>
+ d !== 0
+ ? stringCatenate(
+ unsignedNoDecimalPtCanonicalMap(d),
+ "D",
+ )
+ : "";
+
+/**
+ * Maps a nonnegative integer, presumably the hour normalized value
+ * from the ·seconds· of a duration value, to a duHourFrag, a fragment
+ * of a duration ·lexical representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-duHCan>.
+ *
+ * ☡ This function throws if the provided value is not a nonnegative
+ * integer.
+ *
+ * ※ Despite the description, this function does not necessarily
+ * return a duHourFrag (it can return an empty string).
+ *
+ * ※ This function is not exposed.
+ */
+const duHourCanonicalFragmentMap = (h) =>
+ h !== 0
+ ? stringCatenate(
+ unsignedNoDecimalPtCanonicalMap(h),
+ "H",
+ )
+ : "";
+
+/**
+ * Maps a nonnegative integer, presumably the minute normalized value
+ * from the ·seconds· of a duration value, to a duMinuteFrag, a
+ * fragment of a duration ·lexical representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-duMCan>.
+ *
+ * ☡ This function throws if the provided value is not a nonnegative
+ * integer.
+ *
+ * ※ Despite the description, this function does not necessarily
+ * return a duMinuteFrag (it can return an empty string).
+ *
+ * ※ This function is not exposed.
+ */
+const duMinuteCanonicalFragmentMap = (m) =>
+ m !== 0
+ ? stringCatenate(
+ unsignedNoDecimalPtCanonicalMap(m),
+ "M",
+ )
+ : "";
+
+/**
+ * Maps a nonnegative decimal number, presumably the second normalized
+ * value from the ·seconds· of a duration value, to a duSecondFrag, a
+ * fragment of a duration ·lexical representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-duSCan>.
+ *
+ * ☡ This function throws if the provided value is not a nonnegative
+ * finite number.
+ *
+ * ※ Despite the description, this function does not necessarily
+ * return a duSecondFrag (it can return an empty string).
+ *
+ * ※ This function is not exposed.
+ */
+const duSecondCanonicalFragmentMap = (s) =>
+ s !== 0
+ ? stringCatenate(
+ isIntegralNumber(s)
+ ? unsignedNoDecimalPtCanonicalMap(s)
+ : unsignedDecimalPtCanonicalMap(s),
+ "S",
+ )
+ : "";
+
+/**
+ * Maps three nonnegative numbers, presumably the hour, minute, and
+ * second normalized values from a duration's ·seconds·, to a
+ * duTimeFrag, a fragment of a duration ·lexical representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-duTCan>.
+ *
+ * ☡ This function throws if either of the first two provided values
+ * are not nonnegative integers, or if the final provided value is not
+ * a nonnegative finite number.
+ *
+ * ※ Despite the description, this function does not necessarily
+ * return a duTimeFrag (it can return an empty string).
+ *
+ * ※ This function is not exposed.
+ */
+const duTimeCanonicalFragmentMap = (h, m, s) =>
+ h !== 0 || m !== 0 || s !== 0
+ ? stringCatenate(
+ "T",
+ duHourCanonicalFragmentMap(h),
+ duMinuteCanonicalFragmentMap(m),
+ duSecondCanonicalFragmentMap(s),
+ )
+ : "";
+
+/**
+ * Maps a nonnegative decimal number, presumably the absolute value of
+ * the ·seconds· of a duration value, to a duDayTimeFrag, a fragment of
+ * a duration ·lexical representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-duDTCan>.
+ *
+ * ☡ This function throws if the provided value is not a nonnegative
+ * finite number.
+ *
+ * ※ This function is not exposed.
+ */
+const duDayTimeCanonicalFragmentMap = (ss) => {
+ nonnegativeDecimalNumber(ss);
+ const d = div(ss, 86400);
+ const h = div(mod(ss, 86400), 3600);
+ const m = div(mod(ss, 3600), 60);
+ const s = mod(ss, 60);
+ return ss !== 0
+ ? stringCatenate(
+ duDayCanonicalFragmentMap(d),
+ duTimeCanonicalFragmentMap(h, m, s),
+ )
+ : "T0S";
+};
+
+/**
+ * Maps a duration's property values to durationLexicalRep fragments
+ * and combines the fragments into a complete durationLexicalRep.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-durationCanMap>.
+ *
+ * ☡ This function throws if the provided value is not a complete
+ * duration value.
+ */
+export const durationCanonicalMap = (v) => {
+ const { months: m, seconds: s } = duration(v);
+ const sgn = m < 0 || s < 0 ? "-" : "";
+ return m !== 0 && s !== 0
+ ? stringCatenate(
+ sgn,
+ "P",
+ duYearMonthCanonicalFragmentMap(abs(m)),
+ duDayTimeCanonicalFragmentMap(abs(s)),
+ )
+ : m !== 0
+ ? stringCatenate(
+ sgn,
+ "P",
+ duYearMonthCanonicalFragmentMap(abs(m)),
+ )
+ : stringCatenate(
+ sgn,
+ "P",
+ duDayTimeCanonicalFragmentMap(abs(s)),
+ );
+};
+
+/**
+ * Maps a yearMonthDuration's ·months· value to a
+ * yearMonthDurationLexicalRep. (The ·seconds· value is necessarily
+ * zero and is ignored.) ·yearMonthDurationCanonicalMap· is a
+ * restriction of ·durationCanonicalMap·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-yearMonthDurationCanMap>.
+ *
+ * ☡ This function throws if the provided value is not a complete
+ * yearMonthDuration value.
+ */
+export const yearMonthDurationCanonicalMap = (ym) => {
+ const { months: m, seconds: s } = duration(ym);
+ if (s !== 0) {
+ throw new TypeError(
+ `सूत्र: Expected the provided yearMonthDuration to have a value of zero for seconds, but got: ${s}.`,
+ );
+ } else {
+ const sgn = m < 0 ? "-" : "";
+ return stringCatenate(
+ sgn,
+ "P",
+ duYearMonthCanonicalFragmentMap(abs(m)),
+ );
+ }
+};
+
+/**
+ * Maps a dayTimeDuration's ·seconds· value to a
+ * dayTimeDurationLexicalRep. (The ·months· value is necessarily zero
+ * and is ignored.) ·dayTimeDurationCanonicalMap· is a restriction of
+ * ·durationCanonicalMap·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-dayTimeDurationCanMap>.
+ *
+ * ☡ This function throws if the provided value is not a complete
+ * dayTimeDuration value.
+ */
+export const dayTimeDurationCanonicalMap = (dt) => {
+ const { months: m, seconds: s } = duration(dt);
+ if (m !== 0) {
+ throw new TypeError(
+ `सूत्र: Expected the provided dayTimeDuration to have a value of zero for months, but got: ${m}.`,
+ );
+ } else {
+ const sgn = s < 0 ? "-" : "";
+ return stringCatenate(
+ sgn,
+ "P",
+ duDayTimeCanonicalFragmentMap(abs(s)),
+ );
+ }
+};
+
+/**
+ * If month (mo) is out of range, adjust month and year (yr)
+ * accordingly; otherwise, make no change.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-dt-normMo>.
+ *
+ * ☡ This function throws if the provided values are not integers.
+ *
+ * ※ This function is not exposed.
+ */
+const normalizeMonth = (yr, mo) => {
+ integer(yr);
+ integer(mo);
+ return [yr + div(mo - 1, 12), mod(mo - 1, 12) + 1];
+};
+
+/**
+ * If month (mo) is out of range, or day (da) is out of range for the
+ * appropriate month, then adjust values accordingly, otherwise make no
+ * change.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-dt-normDa>.
+ *
+ * ☡ This function throws if the provided values are not integers.
+ *
+ * ※ This function is not exposed.
+ */
+const normalizeDay = (yr, mo, da) => {
+ integer(yr);
+ integer(mo);
+ integer(da);
+ [yr, mo] = normalizeMonth(yr, mo);
+ let limit = daysInMonth(yr, mo);
+ while (da > limit || da <= 0) {
+ if (da > limit) {
+ da -= limit;
+ mo += 1;
+ [yr, mo] = normalizeMonth(yr, mo);
+ limit = daysInMonth(yr, mo);
+ }
+ if (da <= 0) {
+ mo -= 1;
+ [yr, mo] = normalizeMonth(yr, mo);
+ limit = daysInMonth(yr, mo);
+ da += limit;
+ }
+ }
+ return [yr, mo, da];
+};
+
+/**
+ * Normalizes minute, hour, month, and year values to values that obey
+ * the appropriate constraints.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-dt-normMi>.
+ *
+ * ☡ This function throws if the provided values are not integers.
+ *
+ * ※ This function is not exposed.
+ */
+const normalizeMinute = (yr, mo, da, hr, mi) => {
+ integer(yr);
+ integer(mo);
+ integer(da);
+ integer(hr);
+ integer(mi);
+ hr += div(mi, 60);
+ mi = mod(mi, 60);
+ da += div(hr, 24);
+ hr = mod(hr, 24);
+ [yr, mo, da] = normalizeDay(yr, mo, da);
+ return [yr, mo, da, hr, mi];
+};
+
+/**
+ * Normalizes minute, hour, month, and year values to values that obey
+ * the appropriate constraints. (This algorithm ignores leap seconds.)
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-dt-normSe>.
+ *
+ * ☡ This function throws if the first five provided values are not
+ * integers or the sixth is not a finite number.
+ *
+ * ※ This function is not exposed.
+ */
+const normalizeSecond = (yr, mo, da, hr, mi, se) => {
+ integer(yr);
+ integer(mo);
+ integer(da);
+ integer(hr);
+ integer(mi);
+ decimalNumber(se);
+ mi += div(se, 60);
+ se = mod(se, 60);
+ [yr, mo, da, hr, mi] = normalizeMinute(yr, mo, da, hr, mi);
+ return [yr, mo, da, hr, mi, se];
+};
+
+/**
+ * Normalizes minute, hour, month, and year values to values that obey
+ * the appropriate constraints. (This algorithm ignores leap seconds.)
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-daysInMonth>.
+ *
+ * ☡ This function throws if the first value is not absent or an
+ * integer or if the second value is not an integer between 1 and 12
+ * inclusive.
+ */
+export const daysInMonth = (y, m) => {
+ if (y !== absent && !isIntegralNumber(y)) {
+ throw new TypeError(
+ `सूत्र: Expected an optional integer, but got: ${y}.`,
+ );
+ } else if (!(isIntegralNumber(m) && m >= 1 && m <= 12)) {
+ throw new TypeError(
+ `सूत्र: Expected an integer between 1 and 12 inclusive, but got: ${m}.`,
+ );
+ } else {
+ return m === 2
+ ? y === absent || y % 4 || !(y % 100) && y % 400 ? 28 : 29
+ : m === 4 || m === 6 || m === 9 || m === 11
+ ? 30
+ : 31;
+ }
+};
+
+/**
+ * Returns an instance of the date/timeSevenPropertyModel with property
+ * values as specified in the arguments. If an argument is omitted, the
+ * corresponding property is set to absent.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#p-setDTFromRaw>.
+ *
+ * ※ The X·S·D specification erroneously fails to account for the
+ * value `"--02-29"`, which `·newDateTime·` treats as 01 March because
+ * it assumes February has 28 days when the year is absent. This is a
+ * correct assumption generally, but February should be treated as
+ * having 29 days when a value of `"--02-29"` is explicity given.
+ *
+ * ☡ This function throws if the provided values do not match the
+ * ranges expected by the Date∕time Seven‐Property Model.
+ */
+export const newDateTime = (Yr, Mo, Da, Hr, Mi, Se, Tz) => {
+ if (Yr !== absent && !isIntegralNumber(Yr)) {
+ throw new TypeError(
+ `सूत्र: Expected an optional integer, but got: ${Yr}.`,
+ );
+ } else if (
+ Mo !== absent && !(isIntegralNumber(Mo) && Mo >= 1 && Mo <= 12)
+ ) {
+ throw new TypeError(
+ `सूत्र: Expected an optional integer between 1 and 12 inclusive, but got: ${Mo}.`,
+ );
+ } else if (
+ Da !== absent && !(isIntegralNumber(Da) && Da >= 1 && Da <= 31)
+ ) {
+ throw new TypeError(
+ `सूत्र: Expected an optional integer between 1 and 31 inclusive, but got: ${Da}.`,
+ );
+ } else if (
+ Hr !== absent && !(isIntegralNumber(Hr) && Hr >= 0 && Hr <= 24)
+ ) {
+ throw new TypeError(
+ `सूत्र: Expected an optional integer between 0 and 24 inclusive, but got: ${Hr}.`,
+ );
+ } else if (
+ Mi !== absent && !(isIntegralNumber(Mi) && Mi >= 0 && Mi <= 59)
+ ) {
+ throw new TypeError(
+ `सूत्र: Expected an optional integer between 0 and 59 inclusive, but got: ${Mi}.`,
+ );
+ } else if (
+ Se !== absent && !(isFiniteNumber(Se) && Se >= 0 && Se < 60)
+ ) {
+ throw new TypeError(
+ `सूत्र: Expected an optional finite number greater than or equal to 0 and less than 60, but got: ${Se}.`,
+ );
+ } else if (
+ Tz !== absent && !(isIntegralNumber(Tz) && Tz >= -840 && Tz <= 840)
+ ) {
+ throw new TypeError(
+ `सूत्र: Expected an optional integer between -840 and 840 inclusive, but got: ${Tz}.`,
+ );
+ } else {
+ let yr = Yr !== absent ? Yr : Mo === 2 && Da === 29 ? 0 : 1;
+ let mo = Mo !== absent ? Mo : 1;
+ let da = Da !== absent ? Da : 1;
+ let hr = Hr !== absent ? Hr : 0;
+ let mi = Mi !== absent ? Mi : 0;
+ let se = Se !== absent ? Se : 0;
+ [yr, mo, da, hr, mi, se] = normalizeSecond(yr, mo, da, hr, mi, se);
+ return {
+ year: Yr === absent ? absent : yr,
+ month: Mo === absent ? absent : mo,
+ day: Da === absent ? absent : da,
+ hour: Hr === absent ? absent : hr,
+ minute: Mi === absent ? absent : mi,
+ second: Se === absent ? absent : se,
+ timezoneOffset: Tz,
+ };
+ }
+};
+
+/**
+ * Adds a duration to a dateTime value, producing another dateTime
+ * value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#vp-dt-dateTimePlusDuration>.
+ *
+ * ※ The X·S·D specification erroneously fails to account for the
+ * value `"--02-29"`, which `·dateTimePlusDuration·` treats as 01 March
+ * because it assumes February has 28 days when the year is absent.
+ * This is a correct assumption generally, but February should be
+ * treated as having 29 days when a value of `"--02-29"` is explicity
+ * given.
+ *
+ * ☡ This function throws if the provided duration or
+ * date/timeSevenPropertyModel values are not valid.
+ *
+ * ※ Despite the name and description, this function can add a
+ * duration to any date/timeSevenPropertyModel value.
+ */
+export const dateTimePlusDuration = (du, dt) => {
+ const { months, seconds } = duration(du);
+ const {
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ timezoneOffset,
+ } = date·timeSevenPropertyModel(dt);
+ let yr = year !== absent ? year : month === 2 && day === 29 ? 0 : 1;
+ let mo = month !== absent ? month : 1;
+ let da = day !== absent ? day : 1;
+ let hr = hour !== absent ? hour : 0;
+ let mi = minute !== absent ? minute : 0;
+ let se = second !== absent ? second : 0;
+ mo += months;
+ [yr, mo] = normalizeMonth(yr, mo);
+ da = min(da, daysInMonth(yr, mo));
+ se += seconds;
+ [yr, mo, da, hr, mi, se] = normalizeSecond(yr, mo, da, hr, mi, se);
+ return newDateTime(
+ year === absent ? absent : yr,
+ month === absent ? absent : mo,
+ day === absent ? absent : da,
+ hour === absent ? absent : hr,
+ minute === absent ? absent : mi,
+ second === absent ? absent : se,
+ timezoneOffset,
+ );
+};
+
+/**
+ * Maps a date/timeSevenPropertyModel value to the decimal number
+ * representing its position on the "time line".
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#vp-dt-timeOnTimeline>.
+ *
+ * ☡ This function throws if the provided date/timeSevenPropertyModel
+ * value is not valid.
+ */
+export const timeOnTimeline = (dt) => {
+ const {
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ timezoneOffset,
+ } = date·timeSevenPropertyModel(dt);
+ const yr = year === absent ? 1971 : year - 1;
+ const mo = month === absent ? 12 : month;
+ const da = day === absent ? daysInMonth(yr + 1, mo) - 1 : day - 1;
+ const hr = hour === absent ? 0 : hour;
+ const mi = (minute === absent ? 0 : minute) -
+ (timezoneOffset === absent ? 0 : timezoneOffset);
+ const se = second === absent ? 0 : second;
+ let ToTI = 31536000 * yr;
+ ToTI += 86400 * (div(yr, 400) - div(yr, 100) + div(yr, 4));
+ for (let m = 1; m < mo; ++m) {
+ ToTI += 86400 * daysInMonth(yr + 1, m);
+ }
+ ToTI += 86400 * da;
+ ToTI += 3600 * hr + 60 * mi + se;
+ return ToTI;
+};
+
+/**
+ * Maps a yearFrag, part of a date/timeSevenPropertyModel's ·lexical
+ * representation·, onto an integer, presumably the ·year· property of
+ * a date/timeSevenPropertyModel value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-dt-yrMap>.
+ *
+ * ☡ This function throws if the provided value is not a yearFrag
+ * string.
+ */
+export const yearFragValue = (YR) =>
+ noDecimalMap(ensureMatches(yearFrag, YR));
+
+/**
+ * Maps a monthFrag, part of a date/timeSevenPropertyModel's ·lexical
+ * representation·, onto an integer, presumably the ·month· property of
+ * a date/timeSevenPropertyModel value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-dt-moMap>.
+ *
+ * ☡ This function throws if the provided value is not a monthFrag
+ * string.
+ */
+export const monthFragValue = (MO) =>
+ unsignedNoDecimalMap(ensureMatches(monthFrag, MO));
+
+/**
+ * Maps a dayFrag, part of a date/timeSevenPropertyModel's ·lexical
+ * representation·, onto an integer, presumably the ·day· property of a
+ * date/timeSevenPropertyModel value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-dt-daMap>.
+ *
+ * ☡ This function throws if the provided value is not a dayFrag
+ * string.
+ */
+export const dayFragValue = (DA) =>
+ unsignedNoDecimalMap(ensureMatches(dayFrag, DA));
+
+/**
+ * Maps a hourFrag, part of a date/timeSevenPropertyModel's ·lexical
+ * representation·, onto an integer, presumably the ·hour· property of
+ * a date/timeSevenPropertyModel value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-dt-hrMap>.
+ *
+ * ☡ This function throws if the provided value is not a hourFrag
+ * string.
+ */
+export const hourFragValue = (HR) =>
+ unsignedNoDecimalMap(ensureMatches(hourFrag, HR));
+
+/**
+ * Maps a minuteFrag, part of a date/timeSevenPropertyModel's ·lexical
+ * representation·, onto an integer, presumably the ·minute· property
+ * of a date/timeSevenPropertyModel value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-dt-miMap>.
+ *
+ * ☡ This function throws if the provided value is not a minuteFrag
+ * string.
+ */
+export const minuteFragValue = (MI) =>
+ unsignedNoDecimalMap(ensureMatches(minuteFrag, MI));
+
+/**
+ * Maps a secondFrag, part of a date/timeSevenPropertyModel's ·lexical
+ * representation·, onto a decimal number, presumably the ·second·
+ * property of a date/timeSevenPropertyModel value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-dt-seMap>.
+ *
+ * ☡ This function throws if the provided value is not a secondFrag
+ * string.
+ */
+export const secondFragValue = (SE) =>
+ getFirstSubstringIndex(ensureMatches(secondFrag, SE), ".") === -1
+ ? unsignedNoDecimalMap(SE)
+ : unsignedDecimalPtMap(SE);
+
+/**
+ * Maps a timezoneFrag, part of a date/timeSevenPropertyModel's
+ * ·lexical representation·, onto an integer, presumably the
+ * ·timezoneOffset· property of a date/timeSevenPropertyModel value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-dt-tzMap>.
+ *
+ * ※ The X·S·D specification erroneously specifies
+ * ·unsignedDecimalPtMap·(H) and ·unsignedDecimalPtMap·(M), but
+ * ·unsignedNoDecimalMap·(H) and ·unsignedDecimalPtMap·(M) is correct.
+ *
+ * ☡ This function throws if the provided value is not a timezoneFrag
+ * string.
+ */
+export const timezoneFragValue = (TZ) => {
+ const s = ensureMatches(timezoneFrag, TZ)[0];
+ if (s === "Z") {
+ return 0;
+ } else {
+ let H, M;
+ new Matcher(
+ rawString`[+-](?<H>\d{2}):(?<M>\d{2})`,
+ undefined,
+ (_, { groups }) => {
+ H = groups.H;
+ M = groups.M;
+ return true;
+ },
+ )(TZ);
+ return s === "-"
+ ? -(unsignedNoDecimalMap(H) * 60 + unsignedNoDecimalMap(M)) + 0
+ : unsignedNoDecimalMap(H) * 60 + unsignedNoDecimalMap(M);
+ }
+};
+
+/**
+ * Maps a dateTimeLexicalRep to a dateTime value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#vp-dateTimeLexRep>.
+ *
+ * ☡ This function throws if the provided value is not a
+ * dateTimeLexicalRep string.
+ */
+export const dateTimeLexicalMap = (LEX) => {
+ let Y, MO, D, H, MI, S, T;
+ new Matcher(
+ rawString`(?<Y>-?\d+)-(?<MO>\d+)-(?<D>\d+)T(?:24:00:00(?:\.0+)?|(?<H>\d+):(?<MI>\d+):(?<S>[\d.]+))(?<T>.+)?`,
+ undefined,
+ (_, { groups }) => {
+ Y = groups.Y;
+ MO = groups.MO;
+ D = groups.D;
+ H = groups.H ?? absent;
+ MI = groups.MI ?? absent;
+ S = groups.S ?? absent;
+ T = groups.T ?? absent;
+ return true;
+ },
+ )(ensureMatches(dateTimeLexicalRep, LEX));
+ const tz = T === absent ? absent : timezoneFragValue(T);
+ return H === absent
+ ? newDateTime(
+ yearFragValue(Y),
+ monthFragValue(MO),
+ dayFragValue(D),
+ 24,
+ 0,
+ 0,
+ tz,
+ )
+ : newDateTime(
+ yearFragValue(Y),
+ monthFragValue(MO),
+ dayFragValue(D),
+ hourFragValue(H),
+ minuteFragValue(MI),
+ secondFragValue(S),
+ tz,
+ );
+};
+
+/**
+ * Maps a timeLexicalMap to a time value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#vp-timeLexRep>.
+ *
+ * ☡ This function throws if the provided value is not a timeLexicalMap
+ * string.
+ */
+export const timeLexicalMap = (LEX) => {
+ let H, MI, S, T;
+ new Matcher(
+ rawString`(?:24:00:00(?:\.0+)?|(?<H>\d+):(?<MI>\d+):(?<S>[\d.]+))(?<T>.+)?`,
+ undefined,
+ (_, { groups }) => {
+ H = groups.H ?? absent;
+ MI = groups.MI ?? absent;
+ S = groups.S ?? absent;
+ T = groups.T ?? absent;
+ return true;
+ },
+ )(ensureMatches(timeLexicalRep, LEX));
+ const tz = T === absent ? absent : timezoneFragValue(T);
+ return H === absent
+ ? newDateTime(
+ absent,
+ absent,
+ absent,
+ 0,
+ 0,
+ 0,
+ tz,
+ )
+ : newDateTime(
+ absent,
+ absent,
+ absent,
+ hourFragValue(H),
+ minuteFragValue(MI),
+ secondFragValue(S),
+ tz,
+ );
+};
+
+/**
+ * Maps a dateLexicalRep to a date value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#vp-dateLexRep>.
+ *
+ * ☡ This function throws if the provided value is not a dateLexicalRep
+ * string.
+ */
+export const dateLexicalMap = (LEX) => {
+ let Y, M, D, T;
+ new Matcher(
+ rawString`(?<Y>-?\d+)-(?<M>\d+)-(?<D>\d+)(?<T>.+)?`,
+ undefined,
+ (_, { groups }) => {
+ Y = groups.Y;
+ M = groups.M;
+ D = groups.D;
+ T = groups.T ?? absent;
+ return true;
+ },
+ )(ensureMatches(dateLexicalRep, LEX));
+ const tz = T === absent ? absent : timezoneFragValue(T);
+ return newDateTime(
+ yearFragValue(Y),
+ monthFragValue(M),
+ dayFragValue(D),
+ absent,
+ absent,
+ absent,
+ tz,
+ );
+};
+
+/**
+ * Maps a gYearMonthLexicalRep to a date value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#vp-gYearMonthLexRep>.
+ *
+ * ☡ This function throws if the provided value is not a
+ * gYearMonthLexicalRep string.
+ */
+export const gYearMonthLexicalMap = (LEX) => {
+ let Y, M, T;
+ new Matcher(
+ rawString`(?<Y>-?\d+)-(?<M>\d+)(?<T>.+)?`,
+ undefined,
+ (_, { groups }) => {
+ Y = groups.Y;
+ M = groups.M;
+ T = groups.T ?? absent;
+ return true;
+ },
+ )(ensureMatches(gYearMonthLexicalRep, LEX));
+ const tz = T === absent ? absent : timezoneFragValue(T);
+ return newDateTime(
+ yearFragValue(Y),
+ monthFragValue(M),
+ absent,
+ absent,
+ absent,
+ absent,
+ tz,
+ );
+};
+
+/**
+ * Maps a gYearLexicalRep to a date value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#vp-gYearLexRep>.
+ *
+ * ☡ This function throws if the provided value is not a
+ * gYearLexicalRep string.
+ */
+export const gYearLexicalMap = (LEX) => {
+ let Y, T;
+ new Matcher(
+ rawString`(?<Y>-?\d+)(?<T>.+)?`,
+ undefined,
+ (_, { groups }) => {
+ Y = groups.Y;
+ T = groups.T ?? absent;
+ return true;
+ },
+ )(ensureMatches(gYearLexicalRep, LEX));
+ const tz = T === absent ? absent : timezoneFragValue(T);
+ return newDateTime(
+ yearFragValue(Y),
+ absent,
+ absent,
+ absent,
+ absent,
+ absent,
+ tz,
+ );
+};
+
+/**
+ * Maps a gMonthDayLexicalRep to a date value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#vp-gMonthDayLexRep>.
+ *
+ * ☡ This function throws if the provided value is not a
+ * gMonthDayLexicalRep string.
+ */
+export const gMonthDayLexicalMap = (LEX) => {
+ let M, D, T;
+ new Matcher(
+ rawString`--(?<M>\d+)-(?<D>\d+)(?<T>.+)?`,
+ undefined,
+ (_, { groups }) => {
+ M = groups.M;
+ D = groups.D;
+ T = groups.T ?? absent;
+ return true;
+ },
+ )(ensureMatches(gMonthDayLexicalRep, LEX));
+ const tz = T === absent ? absent : timezoneFragValue(T);
+ return newDateTime(
+ absent,
+ monthFragValue(M),
+ dayFragValue(D),
+ absent,
+ absent,
+ absent,
+ tz,
+ );
+};
+
+/**
+ * Maps a gDayLexicalRep to a date value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#vp-gDayLexRep>.
+ *
+ * ☡ This function throws if the provided value is not a gDayLexicalRep
+ * string.
+ */
+export const gDayLexicalMap = (LEX) => {
+ let D, T;
+ new Matcher(
+ rawString`---(?<D>\d+)(?<T>.+)?`,
+ undefined,
+ (_, { groups }) => {
+ D = groups.D;
+ T = groups.T ?? absent;
+ return true;
+ },
+ )(ensureMatches(gDayLexicalRep, LEX));
+ const tz = T === absent ? absent : timezoneFragValue(T);
+ return newDateTime(
+ absent,
+ absent,
+ dayFragValue(D),
+ absent,
+ absent,
+ absent,
+ tz,
+ );
+};
+
+/**
+ * Maps a gMonthLexicalRep to a date value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#vp-gMonthLexRep>.
+ *
+ * ☡ This function throws if the provided value is not a
+ * gMonthLexicalRep string.
+ */
+export const gMonthLexicalMap = (LEX) => {
+ let M, T;
+ new Matcher(
+ rawString`--(?<M>\d+)(?<T>.+)?`,
+ undefined,
+ (_, { groups }) => {
+ M = groups.M;
+ T = groups.T ?? absent;
+ return true;
+ },
+ )(ensureMatches(gMonthLexicalRep, LEX));
+ const tz = T === absent ? absent : timezoneFragValue(T);
+ return newDateTime(
+ absent,
+ monthFragValue(M),
+ absent,
+ absent,
+ absent,
+ absent,
+ tz,
+ );
+};
+
+/**
+ * Maps a nonnegative integer less than 100 onto an unsigned
+ * always-two-digit numeral.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-unsTwoDigCanFragMap>.
+ *
+ * ☡ This function throws if the provided value is not a nonnegative
+ * integer less than 100.
+ *
+ * ※ This function is not exposed.
+ */
+const unsTwoDigitCanonicalFragmentMap = (i) => {
+ if (!(isIntegralNumber(i) && i >= 0 && i < 100)) {
+ throw new TypeError(
+ `सूत्र: Expected a nonnegative integer less than 100, but got: ${i}.`,
+ );
+ } else {
+ return stringCatenate(digit(div(i, 10)), digit(mod(i, 10)));
+ }
+};
+
+/**
+ * Maps an integer between -10000 and 10000 onto an always-four-digit numeral.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-fourDigCanFragMap>.
+ *
+ * ☡ This function throws if the provided value is not an integer whose
+ * absolute value is less than 10000.
+ *
+ * ※ This function is not exposed.
+ */
+const fourDigitCanonicalFragmentMap = (i) => {
+ if (!(isIntegralNumber(i) && abs(i) < 10000)) {
+ throw new TypeError(
+ `सूत्र: Expected an integer whose absolute value is less than 10000, but got: ${i}.`,
+ );
+ } else {
+ return i < 0
+ ? stringCatenate(
+ "-",
+ unsTwoDigitCanonicalFragmentMap(div(-i, 100)),
+ unsTwoDigitCanonicalFragmentMap(mod(-i, 100)),
+ )
+ : stringCatenate(
+ unsTwoDigitCanonicalFragmentMap(div(i, 100)),
+ unsTwoDigitCanonicalFragmentMap(mod(i, 100)),
+ );
+ }
+};
+
+/**
+ * Maps an integer, presumably the ·year· property of a
+ * date/timeSevenPropertyModel value, onto a yearFrag, part of a
+ * date/timeSevenPropertyModel's ·lexical representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-yrCanFragMap>.
+ *
+ * ☡ This function throws if the provided value is not an integer.
+ */
+export const yearCanonicalFragmentMap = (y) =>
+ abs(integer(y)) > 9999
+ ? noDecimalPtCanonicalMap(y)
+ : fourDigitCanonicalFragmentMap(y);
+
+/**
+ * Maps an integer, presumably the ·month· property of a
+ * date/timeSevenPropertyModel value, onto a monthFrag, part of a
+ * date/timeSevenPropertyModel's ·lexical representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-moCanFragMap>.
+ *
+ * ☡ This function throws if the provided value is not an integer
+ * between 1 and 12 inclusive.
+ */
+export const monthCanonicalFragmentMap = (m) => {
+ if (!(isIntegralNumber(m) && m >= 1 && m <= 12)) {
+ throw new TypeError(
+ `सूत्र: Expected an integer between 1 and 12 inclusive, but got: ${m}.`,
+ );
+ } else {
+ return unsTwoDigitCanonicalFragmentMap(m);
+ }
+};
+
+/**
+ * Maps an integer, presumably the ·day· property of a
+ * date/timeSevenPropertyModel value, onto a dayFrag, part of a
+ * date/timeSevenPropertyModel's ·lexical representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-daCanFragMap>.
+ *
+ * ☡ This function throws if the provided value is not an integer
+ * between 1 and 31 inclusive.
+ */
+export const dayCanonicalFragmentMap = (d) => {
+ if (!(isIntegralNumber(d) && d >= 1 && d <= 31)) {
+ throw new TypeError(
+ `सूत्र: Expected an integer between 1 and 31 inclusive, but got: ${d}.`,
+ );
+ } else {
+ return unsTwoDigitCanonicalFragmentMap(d);
+ }
+};
+
+/**
+ * Maps an integer, presumably the ·hour· property of a
+ * date/timeSevenPropertyModel value, onto an hourFrag, part of a
+ * date/timeSevenPropertyModel's ·lexical representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-hrCanFragMap>.
+ *
+ * ☡ This function throws if the provided value is not an integer
+ * between 0 and 23 inclusive.
+ */
+export const hourCanonicalFragmentMap = (h) => {
+ if (!(isIntegralNumber(h) && h >= 0 && h <= 23)) {
+ throw new TypeError(
+ `सूत्र: Expected an integer between 0 and 23 inclusive, but got: ${h}.`,
+ );
+ } else {
+ return unsTwoDigitCanonicalFragmentMap(h);
+ }
+};
+
+/**
+ * Maps an integer, presumably the ·minute· property of a
+ * date/timeSevenPropertyModel value, onto a minuteFrag, part of a
+ * date/timeSevenPropertyModel's ·lexical representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-miCanFragMap>.
+ *
+ * ☡ This function throws if the provided value is not an integer
+ * between 0 and 59 inclusive.
+ */
+export const minuteCanonicalFragmentMap = (m) => {
+ if (!(isIntegralNumber(m) && m >= 0 && m <= 59)) {
+ throw new TypeError(
+ `सूत्र: Expected an integer between 0 and 59 inclusive, but got: ${m}.`,
+ );
+ } else {
+ return unsTwoDigitCanonicalFragmentMap(m);
+ }
+};
+
+/**
+ * Maps a decimal number, presumably the ·second· property of a
+ * date/timeSevenPropertyModel value, onto a secondFrag, part of a
+ * date/timeSevenPropertyModel's ·lexical representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-seCanFragMap>.
+ *
+ * ☡ This function throws if the provided value is not a nonnegative
+ * finite number less than 60.
+ *
+ * ※ The X·S·D specification identifies the argument to this function
+ * as “a nonnegative decimal number less than 70”, but second values 60
+ * or larger are not supported by X·S·D.
+ *
+ * ※ This function makes use of the built·in Ecmascript float
+ * serialization to avoid rounding errors.
+ */
+export const secondCanonicalFragmentMap = (s) => {
+ if (!(isFiniteNumber(s) && s < 60)) {
+ throw new TypeError(
+ `सूत्र: Expected a nonnegative finite number less than 60, but got: ${s}.`,
+ );
+ } else {
+ // return isIntegralNumber(s)
+ // ? unsTwoDigitCanonicalFragmentMap(s)
+ // : stringCatenate(
+ // unsTwoDigitCanonicalFragmentMap(div(s, 1)),
+ // ".",
+ // fractionDigitsCanonicalFragmentMap(mod(s, 1)),
+ // );
+ return isIntegralNumber(s)
+ ? unsTwoDigitCanonicalFragmentMap(s)
+ : s < 10
+ ? stringCatenate("0", decimalPtCanonicalMap(s))
+ : decimalPtCanonicalMap(s);
+ }
+};
+
+/**
+ * Maps an integer, presumably the ·timezoneOffset· property of a
+ * date/timeSevenPropertyModel value, onto a timezoneFrag, part of a
+ * date/timeSevenPropertyModel's ·lexical representation·.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-tzCanFragMap>.
+ *
+ * ☡ This function throws if the provided value is not an integer
+ * between −840 and 840 inclusive.
+ */
+export const timezoneCanonicalFragmentMap = (t) => {
+ if (!(isIntegralNumber(t) && t >= -840 && t <= 840)) {
+ throw new TypeError(
+ `सूत्र: Expected an integer between −840 and 840 inclusive, but got: ${t}.`,
+ );
+ } else {
+ return t === 0 ? "Z" : t < 0
+ ? stringCatenate(
+ "-",
+ unsTwoDigitCanonicalFragmentMap(div(-t, 60)),
+ ":",
+ unsTwoDigitCanonicalFragmentMap(mod(-t, 60)),
+ )
+ : stringCatenate(
+ "+",
+ unsTwoDigitCanonicalFragmentMap(div(t, 60)),
+ ":",
+ unsTwoDigitCanonicalFragmentMap(mod(t, 60)),
+ );
+ }
+};
+
+/**
+ * Maps a dateTime value to a dateTimeLexicalRep.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#vp-dateTimeCanRep>.
+ *
+ * ☡ This function throws if the provided value is not a complete
+ * dateTime.
+ */
+export const dateTimeCanonicalMap = (dt) => {
+ const {
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ timezoneOffset,
+ } = date·timeSevenPropertyModel(dt);
+ ensurePresence("year", year, true);
+ ensurePresence("month", month, true);
+ ensurePresence("day", day, true);
+ ensurePresence("hour", hour, true);
+ ensurePresence("minute", minute, true);
+ ensurePresence("second", second, true);
+ const DT = stringCatenate(
+ yearCanonicalFragmentMap(year),
+ "-",
+ monthCanonicalFragmentMap(month),
+ "-",
+ dayCanonicalFragmentMap(day),
+ "T",
+ hourCanonicalFragmentMap(hour),
+ ":",
+ minuteCanonicalFragmentMap(minute),
+ ":",
+ secondCanonicalFragmentMap(second),
+ );
+ return timezoneOffset === absent
+ ? DT
+ : stringCatenate(DT, timezoneCanonicalFragmentMap(timezoneOffset));
+};
+
+/**
+ * Maps a time value to a timeLexicalRep.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#vp-timeCanRep>.
+ *
+ * ☡ This function throws if the provided value is not a complete time.
+ */
+export const timeCanonicalMap = (ti) => {
+ const {
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ timezoneOffset,
+ } = date·timeSevenPropertyModel(ti);
+ ensurePresence("year", year, false);
+ ensurePresence("month", month, false);
+ ensurePresence("day", day, false);
+ ensurePresence("hour", hour, true);
+ ensurePresence("minute", minute, true);
+ ensurePresence("second", second, true);
+ const T = stringCatenate(
+ hourCanonicalFragmentMap(hour),
+ ":",
+ minuteCanonicalFragmentMap(minute),
+ ":",
+ secondCanonicalFragmentMap(second),
+ );
+ return timezoneOffset === absent
+ ? T
+ : stringCatenate(T, timezoneCanonicalFragmentMap(timezoneOffset));
+};
+
+/**
+ * Maps a date value to a dateLexicalRep.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#vp-dateCanRep>.
+ *
+ * ☡ This function throws if the provided value is not a complete date.
+ */
+export const dateCanonicalMap = (da) => {
+ const {
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ timezoneOffset,
+ } = date·timeSevenPropertyModel(da);
+ ensurePresence("year", year, true);
+ ensurePresence("month", month, true);
+ ensurePresence("day", day, true);
+ ensurePresence("hour", hour, false);
+ ensurePresence("minute", minute, false);
+ ensurePresence("second", second, false);
+ const D = stringCatenate(
+ yearCanonicalFragmentMap(year),
+ "-",
+ monthCanonicalFragmentMap(month),
+ "-",
+ dayCanonicalFragmentMap(day),
+ );
+ return timezoneOffset === absent
+ ? D
+ : stringCatenate(D, timezoneCanonicalFragmentMap(timezoneOffset));
+};
+
+/**
+ * Maps a gYearMonth value to a gYearMonthLexicalRep.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#vp-gYearMonthCanRep>.
+ *
+ * ☡ This function throws if the provided value is not a complete
+ * gYearMonth.
+ */
+export const gYearMonthCanonicalMap = (ym) => {
+ const {
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ timezoneOffset,
+ } = date·timeSevenPropertyModel(ym);
+ ensurePresence("year", year, true);
+ ensurePresence("month", month, true);
+ ensurePresence("day", day, false);
+ ensurePresence("hour", hour, false);
+ ensurePresence("minute", minute, false);
+ ensurePresence("second", second, false);
+ const YM = stringCatenate(
+ yearCanonicalFragmentMap(year),
+ "-",
+ monthCanonicalFragmentMap(month),
+ );
+ return timezoneOffset === absent
+ ? YM
+ : stringCatenate(YM, timezoneCanonicalFragmentMap(timezoneOffset));
+};
+
+/**
+ * Maps a gYear value to a gYearLexicalRep.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#vp-gYearCanRep>.
+ *
+ * ☡ This function throws if the provided value is not a complete
+ * gYear.
+ */
+export const gYearCanonicalMap = (gY) => {
+ const {
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ timezoneOffset,
+ } = date·timeSevenPropertyModel(gY);
+ ensurePresence("year", year, true);
+ ensurePresence("month", month, false);
+ ensurePresence("day", day, false);
+ ensurePresence("hour", hour, false);
+ ensurePresence("minute", minute, false);
+ ensurePresence("second", second, false);
+ return timezoneOffset === absent
+ ? yearCanonicalFragmentMap(year)
+ : stringCatenate(
+ yearCanonicalFragmentMap(year),
+ timezoneCanonicalFragmentMap(timezoneOffset),
+ );
+};
+
+/**
+ * Maps a gMonthDay value to a gMonthDayLexicalRep.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#vp-gMonthDayCanRep>.
+ *
+ * ☡ This function throws if the provided value is not a complete
+ * gMonthDay.
+ */
+export const gMonthDayCanonicalMap = (md) => {
+ const {
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ timezoneOffset,
+ } = date·timeSevenPropertyModel(md);
+ ensurePresence("year", year, false);
+ ensurePresence("month", month, true);
+ ensurePresence("day", day, true);
+ ensurePresence("hour", hour, false);
+ ensurePresence("minute", minute, false);
+ ensurePresence("second", second, false);
+ const MD = stringCatenate(
+ "--",
+ monthCanonicalFragmentMap(month),
+ "-",
+ dayCanonicalFragmentMap(day),
+ );
+ return timezoneOffset === absent
+ ? MD
+ : stringCatenate(MD, timezoneCanonicalFragmentMap(timezoneOffset));
+};
+
+/**
+ * Maps a gDay value to a gDayLexicalRep.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#vp-gDayCanRep>.
+ *
+ * ☡ This function throws if the provided value is not a complete gDay.
+ */
+export const gDayCanonicalMap = (gD) => {
+ const {
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ timezoneOffset,
+ } = date·timeSevenPropertyModel(gD);
+ ensurePresence("year", year, false);
+ ensurePresence("month", month, false);
+ ensurePresence("day", day, true);
+ ensurePresence("hour", hour, false);
+ ensurePresence("minute", minute, false);
+ ensurePresence("second", second, false);
+ return timezoneOffset === absent
+ ? stringCatenate("---", dayCanonicalFragmentMap(day))
+ : stringCatenate(
+ "---",
+ dayCanonicalFragmentMap(day),
+ timezoneCanonicalFragmentMap(timezoneOffset),
+ );
+};
+
+/**
+ * Maps a gMonth value to a gMonthLexicalRep.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#vp-gMonthCanRep>.
+ *
+ * ☡ This function throws if the provided value is not a complete
+ * gMonth.
+ */
+export const gMonthCanonicalMap = (gM) => {
+ const {
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ timezoneOffset,
+ } = date·timeSevenPropertyModel(gM);
+ ensurePresence("year", year, false);
+ ensurePresence("month", month, true);
+ ensurePresence("day", day, false);
+ ensurePresence("hour", hour, false);
+ ensurePresence("minute", minute, false);
+ ensurePresence("second", second, false);
+ return timezoneOffset === absent
+ ? stringCatenate("--", dayCanonicalFragmentMap(month))
+ : stringCatenate(
+ "--",
+ dayCanonicalFragmentMap(month),
+ timezoneCanonicalFragmentMap(timezoneOffset),
+ );
+};
+
+/**
+ * Maps a ·literal· matching the stringRep production to a string
+ * value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-stringLexmap>.
+ *
+ * ☡ This function throws if the provided value is not a stringRep
+ * string.
+ */
+export const stringLexicalMap = (LEX) => ensureMatches(stringRep, LEX);
+
+/**
+ * Maps a ·literal· matching the booleanRep production to a boolean
+ * value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-booleanLexmap>.
+ *
+ * ☡ This function throws if the provided value is not a booleanRep
+ * string.
+ */
+export const booleanLexicalMap = (LEX) => {
+ ensureMatches(booleanRep, LEX);
+ return LEX === "true" || LEX === "1";
+};
+
+/**
+ * Maps a string value to a stringRep.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-stringCanmap>.
+ *
+ * ☡ This function throws if the provided value is not a valid string.
+ */
+export const stringCanonicalMap = (s) => ensureMatches(stringRep, s);
+
+/**
+ * Maps a boolean value to a booleanRep.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-booleanCanmap>.
+ *
+ * ☡ This function throws if the provided value is not a boolean.
+ */
+export const booleanCanonicalMap = (b) => {
+ if (typeof b !== "boolean") {
+ throw new TypeError(`सूत्र: Expected a boolean, but got: ${b}.`);
+ } else {
+ return b ? "true" : "false";
+ }
+};
+
+/**
+ * Maps a ·literal· matching the hexBinary production to a sequence of
+ * octets in the form of a hexBinary value.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-hexBinaryMap>.
+ *
+ * ☡ This function throws if the provided value is not a hexBinary
+ * string.
+ *
+ * ※ This function returns an `ArrayBuffer`.
+ */
+export const hexBinaryMap = (LEX) => {
+ ensureMatches(hexBinary, LEX);
+ const bufferLength = LEX.length / 2;
+ const o = new ArrayBuffer(bufferLength);
+ for (let bufferIndex = 0, index = 0; bufferIndex < bufferLength;) {
+ set8BitIntegralItem(
+ o,
+ bufferIndex,
+ hexOctetMap(substring(LEX, index, index = ++bufferIndex * 2)),
+ );
+ }
+ return o;
+};
+
+/**
+ * Maps a ·literal· matching the hexOctet production to a single octet.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-hexOctetMap>.
+ *
+ * ☡ This function throws if the provided value is not a hexOctet
+ * string.
+ *
+ * ※ This function is not exposed.
+ */
+const hexOctetMap = (LEX) => {
+ const [d0, d1] = ensureMatches(hexOctet, LEX);
+ let octet = 0;
+ let bit = 8;
+ for (const digit of hexDigitMap(d0)) {
+ octet |= digit << --bit;
+ }
+ for (const digit of hexDigitMap(d1)) {
+ octet |= digit << --bit;
+ }
+ return octet;
+};
+
+/**
+ * Maps a hexadecimal digit (a character matching the hexDigit
+ * production) to a sequence of four binary digits.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-hexDigitMap>.
+ *
+ * ☡ This function throws if the provided value is not a hexDigit
+ * string.
+ *
+ * ※ It would be way simpler and more efficient to just return a
+ * single value here rather than a sequence of digits.
+ *
+ * ※ This function is not exposed.
+ */
+const hexDigitMap = (d) => {
+ switch (ensureMatches(hexDigit, d)) {
+ case "0":
+ return binaryDigitSequence("0000");
+ case "1":
+ return binaryDigitSequence("0001");
+ case "2":
+ return binaryDigitSequence("0010");
+ case "3":
+ return binaryDigitSequence("0011");
+ case "4":
+ return binaryDigitSequence("0100");
+ case "5":
+ return binaryDigitSequence("0101");
+ case "6":
+ return binaryDigitSequence("0110");
+ case "7":
+ return binaryDigitSequence("0111");
+ case "8":
+ return binaryDigitSequence("1000");
+ case "9":
+ return binaryDigitSequence("1001");
+ case "A":
+ case "a":
+ return binaryDigitSequence("1010");
+ case "B":
+ case "b":
+ return binaryDigitSequence("1011");
+ case "C":
+ case "c":
+ return binaryDigitSequence("1100");
+ case "D":
+ case "d":
+ return binaryDigitSequence("1101");
+ case "E":
+ case "e":
+ return binaryDigitSequence("1110");
+ case "F":
+ case "f":
+ return binaryDigitSequence("1111");
+ }
+};
+
+/**
+ * Maps a hexBinary value to a literal matching the hexBinary
+ * production.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-hexBinaryCanonical>.
+ *
+ * ☡ This function throws if the provided value is not an
+ * `ArrayBuffer`.
+ */
+export const hexBinaryCanonical = (o) => {
+ hexBinaryValue(o);
+ const length = getByteLength(o);
+ return stringCatenate(
+ "",
+ ...generatorSequence(function* () {
+ for (let index = 0; index < length; ++index) {
+ yield hexOctetCanonical(
+ toNumber(get8BitUnsignedIntegralItem(o, index)),
+ );
+ }
+ }),
+ );
+};
+
+/**
+ * Maps a binary octet to a literal matching the hexOctet production.
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-hexOctetCanonical>.
+ *
+ * ☡ This function throws if the provided value is not a hexOctet
+ * string.
+ *
+ * ※ This function is not exposed.
+ */
+const hexOctetCanonical = (o) => {
+ if (!(isIntegralNumber(o) && o >= 0 && o <= 0xFF)) {
+ throw new TypeError(
+ `सूत्र: Expected a binary octet but got: ${o}.`,
+ );
+ } else {
+ const lo = generatorSequence(function* () {
+ for (let i = 1; i <= 4; ++i) {
+ const offset = 4 - i;
+ yield (o & 1 << offset) >> offset;
+ }
+ });
+ const hi = generatorSequence(function* () {
+ for (let i = 1; i <= 4; ++i) {
+ const offset = 8 - i;
+ yield (o & 1 << offset) >> offset;
+ }
+ });
+ return hexDigitCanonical(hi) + hexDigitCanonical(lo);
+ }
+};
+
+/**
+ * Maps a four-bit sequence to a hexadecimal digit (a literal matching
+ * the hexDigit production).
+ *
+ * See <https://www.w3.org/TR/xmlschema11-2/#f-hexDigitCanonical>.
+ *
+ * ☡ This function throws if the provided value is not an iterable of
+ * binary digits.
+ *
+ * ※ It would be way simpler and more efficient to just call this with
+ * a single value rather than a sequence of digits.
+ *
+ * ※ This function is not exposed.
+ */
+const hexDigitCanonical = (d) => {
+ let sum = 0;
+ for (const digit of d) {
+ if (digit != 0 && digit != 1) {
+ throw new TypeError(
+ `सूत्र: Expected a binary digit but got: ${digit}.`,
+ );
+ } else {
+ sum = (sum << 1) | digit;
+ }
+ }
+ switch (sum) {
+ case 0b0000:
+ return "0";
+ case 0b0001:
+ return "1";
+ case 0b0010:
+ return "2";
+ case 0b0011:
+ return "3";
+ case 0b0100:
+ return "4";
+ case 0b0101:
+ return "5";
+ case 0b0110:
+ return "6";
+ case 0b0111:
+ return "7";
+ case 0b1000:
+ return "8";
+ case 0b1001:
+ return "9";
+ case 0b1010:
+ return "A";
+ case 0b1011:
+ return "B";
+ case 0b1100:
+ return "C";
+ case 0b1101:
+ return "D";
+ case 0b1110:
+ return "E";
+ case 0b1111:
+ return "F";
+ }
+};
--- /dev/null
+// ♓️🪡 सूत्र ∷ xsd/functions.test.js
+// ====================================================================
+//
+// Copyright © 2023 Lady [@ Lady’s Computer].
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
+
+import {
+ assertAlmostEquals,
+ assertEquals,
+ assertObjectMatch,
+ assertStrictEquals,
+ assertThrows,
+ describe,
+ it,
+} from "../dev-deps.js";
+import {
+ booleanCanonicalMap,
+ booleanLexicalMap,
+ dateCanonicalMap,
+ dateLexicalMap,
+ dateTimeCanonicalMap,
+ dateTimeLexicalMap,
+ dateTimePlusDuration,
+ dayCanonicalFragmentMap,
+ dayFragValue,
+ daysInMonth,
+ dayTimeDurationCanonicalMap,
+ dayTimeDurationMap,
+ decimalCanonicalMap,
+ decimalLexicalMap,
+ decimalPtCanonicalMap,
+ decimalPtMap,
+ doubleCanonicalMap,
+ doubleLexicalMap,
+ durationCanonicalMap,
+ durationMap,
+ floatCanonicalMap,
+ floatLexicalMap,
+ gDayCanonicalMap,
+ gDayLexicalMap,
+ gMonthCanonicalMap,
+ gMonthDayCanonicalMap,
+ gMonthDayLexicalMap,
+ gMonthLexicalMap,
+ gYearCanonicalMap,
+ gYearLexicalMap,
+ gYearMonthCanonicalMap,
+ gYearMonthLexicalMap,
+ hexBinaryCanonical,
+ hexBinaryMap,
+ hourCanonicalFragmentMap,
+ hourFragValue,
+ minuteCanonicalFragmentMap,
+ minuteFragValue,
+ monthCanonicalFragmentMap,
+ monthFragValue,
+ newDateTime,
+ noDecimalMap,
+ noDecimalPtCanonicalMap,
+ scientificCanonicalMap,
+ scientificMap,
+ secondCanonicalFragmentMap,
+ secondFragValue,
+ specialRepCanonicalMap,
+ specialRepValue,
+ stringCanonicalMap,
+ stringLexicalMap,
+ timeCanonicalMap,
+ timeLexicalMap,
+ timeOnTimeline,
+ timezoneCanonicalFragmentMap,
+ timezoneFragValue,
+ unsignedDecimalPtCanonicalMap,
+ unsignedDecimalPtMap,
+ unsignedNoDecimalMap,
+ unsignedNoDecimalPtCanonicalMap,
+ unsignedScientificCanonicalMap,
+ yearCanonicalFragmentMap,
+ yearFragValue,
+ yearMonthDurationCanonicalMap,
+ yearMonthDurationMap,
+} from "./functions.js";
+
+describe("unsignedNoDecimalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(unsignedNoDecimalMap("231"), 231);
+ });
+
+ it("throws an error if the value has a decimal point", () => {
+ assertThrows(() => {
+ unsignedNoDecimalMap("231.");
+ });
+ assertThrows(() => {
+ unsignedNoDecimalMap(".231");
+ });
+ assertThrows(() => {
+ unsignedNoDecimalMap("23.1");
+ });
+ });
+
+ it("throws an error if the value has a sign", () => {
+ assertThrows(() => {
+ unsignedNoDecimalMap("+231");
+ });
+ assertThrows(() => {
+ unsignedNoDecimalMap("-231");
+ });
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ unsignedNoDecimalMap(231);
+ });
+ });
+});
+
+describe("noDecimalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(noDecimalMap("231"), 231);
+ assertStrictEquals(noDecimalMap("-69"), -69);
+ assertStrictEquals(noDecimalMap("+72"), +72);
+ assertStrictEquals(noDecimalMap("0"), 0);
+ assertStrictEquals(noDecimalMap("-0"), 0);
+ });
+
+ it("throws an error if the value has a decimal point", () => {
+ assertThrows(() => {
+ noDecimalMap("231.");
+ });
+ assertThrows(() => {
+ noDecimalMap(".231");
+ });
+ assertThrows(() => {
+ noDecimalMap("23.1");
+ });
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ noDecimalMap(231);
+ });
+ });
+});
+
+describe("unsignedDecimalPtMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(unsignedDecimalPtMap("231."), 231);
+ assertStrictEquals(unsignedDecimalPtMap(".231"), .231);
+ assertStrictEquals(unsignedDecimalPtMap("23.1"), 23.1);
+ });
+
+ it("throws an error if the value has no decimal point", () => {
+ assertThrows(() => {
+ unsignedDecimalPtMap("231");
+ });
+ });
+
+ it("throws an error if the value has a sign", () => {
+ assertThrows(() => {
+ unsignedDecimalPtMap("+231.");
+ });
+ assertThrows(() => {
+ unsignedDecimalPtMap("-231.");
+ });
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ unsignedDecimalPtMap(231);
+ });
+ });
+});
+
+describe("decimalPtMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(decimalPtMap("231."), 231);
+ assertStrictEquals(decimalPtMap("+.231"), .231);
+ assertStrictEquals(decimalPtMap("-23.1"), -23.1);
+ assertStrictEquals(decimalPtMap("0.0"), 0);
+ assertStrictEquals(decimalPtMap("-0.0"), 0);
+ });
+
+ it("throws an error if the value has no decimal point", () => {
+ assertThrows(() => {
+ decimalPtMap("231");
+ });
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ decimalPtMap(231);
+ });
+ });
+});
+
+describe("scientificMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(scientificMap("23E1"), 230);
+ assertStrictEquals(scientificMap("+2.3E-1"), .23);
+ assertStrictEquals(scientificMap("-.23E1"), -2.3);
+ assertStrictEquals(scientificMap("0.0E0"), 0);
+ assertStrictEquals(scientificMap("-0.0E-0"), 0);
+ });
+
+ it("throws an error if the value has no E", () => {
+ assertThrows(() => {
+ scientificMap("231");
+ });
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ decimalPtMap(231);
+ });
+ });
+});
+
+describe("unsignedNoDecimalPtCanonicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(unsignedNoDecimalPtCanonicalMap(231), "231");
+ assertStrictEquals(
+ unsignedNoDecimalPtCanonicalMap(1e23),
+ "100000000000000000000000",
+ );
+ assertStrictEquals(unsignedNoDecimalPtCanonicalMap(0), "0");
+ assertStrictEquals(unsignedNoDecimalPtCanonicalMap(-0), "0");
+ });
+
+ it("throws an error if the value has a fractional part", () => {
+ assertThrows(() => {
+ unsignedNoDecimalPtCanonicalMap(23.1);
+ });
+ });
+
+ it("throws an error if the value is negative", () => {
+ assertThrows(() => {
+ unsignedNoDecimalPtCanonicalMap(-231);
+ });
+ });
+
+ it("throws an error if the value is not a number", () => {
+ assertThrows(() => {
+ unsignedNoDecimalPtCanonicalMap("231");
+ });
+ });
+});
+
+describe("noDecimalPtCanonicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(noDecimalPtCanonicalMap(231), "231");
+ assertStrictEquals(noDecimalPtCanonicalMap(-231), "-231");
+ assertStrictEquals(noDecimalPtCanonicalMap(0), "0");
+ assertStrictEquals(noDecimalPtCanonicalMap(-0), "0");
+ });
+
+ it("throws an error if the value has a fractional part", () => {
+ assertThrows(() => {
+ noDecimalPtCanonicalMap(23.1);
+ });
+ });
+
+ it("throws an error if the value is not a number", () => {
+ assertThrows(() => {
+ noDecimalPtCanonicalMap("231");
+ });
+ });
+});
+
+describe("unsignedDecimalPtCanonicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(unsignedDecimalPtCanonicalMap(4), "4.0");
+ assertStrictEquals(unsignedDecimalPtCanonicalMap(231), "231.0");
+ assertStrictEquals(unsignedDecimalPtCanonicalMap(23.1), "23.1");
+ assertStrictEquals(unsignedDecimalPtCanonicalMap(.24), "0.24");
+ assertStrictEquals(
+ unsignedDecimalPtCanonicalMap(10 ** 21.5),
+ "3162277660168379400000.0",
+ );
+ assertStrictEquals(unsignedDecimalPtCanonicalMap(.0085), "0.0085");
+ assertStrictEquals(
+ unsignedDecimalPtCanonicalMap(10 ** -6.5),
+ "0.0000003162277660168379",
+ );
+ assertStrictEquals(unsignedDecimalPtCanonicalMap(0), "0.0");
+ assertStrictEquals(unsignedDecimalPtCanonicalMap(-0), "0.0");
+ });
+
+ it("throws an error if the value is negative", () => {
+ assertThrows(() => {
+ unsignedDecimalPtCanonicalMap(-231);
+ });
+ });
+
+ it("throws an error if the value is not a number", () => {
+ assertThrows(() => {
+ unsignedDecimalPtCanonicalMap("231.0");
+ });
+ });
+});
+
+describe("decimalPtCanonicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(decimalPtCanonicalMap(231), "231.0");
+ assertStrictEquals(decimalPtCanonicalMap(-23.1), "-23.1");
+ assertStrictEquals(decimalPtCanonicalMap(0), "0.0");
+ assertStrictEquals(decimalPtCanonicalMap(-0), "0.0");
+ });
+
+ it("throws an error if the value is not a number", () => {
+ assertThrows(() => {
+ decimalPtCanonicalMap("231.0");
+ });
+ });
+});
+
+describe("unsignedScientificCanonicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(unsignedScientificCanonicalMap(4), "4.0E0");
+ assertStrictEquals(unsignedScientificCanonicalMap(231), "2.31E2");
+ assertStrictEquals(unsignedScientificCanonicalMap(23), "2.3E1");
+ assertStrictEquals(unsignedScientificCanonicalMap(.24), "2.4E-1");
+ assertStrictEquals(
+ unsignedScientificCanonicalMap(10 ** 21.5),
+ "3.1622776601683794E21",
+ );
+ assertStrictEquals(
+ unsignedScientificCanonicalMap(.0085),
+ "8.5E-3",
+ );
+ assertStrictEquals(
+ unsignedScientificCanonicalMap(10 ** -6.5),
+ "3.162277660168379E-7",
+ );
+ assertStrictEquals(unsignedScientificCanonicalMap(0), "0.0E0");
+ assertStrictEquals(unsignedScientificCanonicalMap(-0), "0.0E0");
+ });
+
+ it("throws an error if the value is negative", () => {
+ assertThrows(() => {
+ unsignedScientificCanonicalMap(-231);
+ });
+ });
+
+ it("throws an error if the value is not a number", () => {
+ assertThrows(() => {
+ unsignedScientificCanonicalMap("2.31E2");
+ });
+ });
+});
+
+describe("scientificCanonicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(scientificCanonicalMap(231), "2.31E2");
+ assertStrictEquals(scientificCanonicalMap(-.231), "-2.31E-1");
+ assertStrictEquals(scientificCanonicalMap(0), "0.0E0");
+ assertStrictEquals(scientificCanonicalMap(-0), "0.0E0");
+ });
+
+ it("throws an error if the value is not a number", () => {
+ assertThrows(() => {
+ scientificCanonicalMap("2.31E2");
+ });
+ });
+});
+
+describe("specialRepValue", () => {
+ it("maps the value", () => {
+ assertStrictEquals(specialRepValue("INF"), Infinity);
+ assertStrictEquals(specialRepValue("+INF"), Infinity);
+ assertStrictEquals(specialRepValue("-INF"), -Infinity);
+ assertStrictEquals(specialRepValue("NaN"), NaN);
+ });
+
+ it("throws an error if the value is not a special represention", () => {
+ assertThrows(() => {
+ specialRepValue("231");
+ });
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ specialRepValue(Infinity);
+ });
+ });
+});
+
+describe("specialRepCanonicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(specialRepCanonicalMap(Infinity), "INF");
+ assertStrictEquals(specialRepCanonicalMap(-Infinity), "-INF");
+ assertStrictEquals(specialRepCanonicalMap(NaN), "NaN");
+ });
+
+ it("throws an error if the value is not a special value", () => {
+ assertThrows(() => {
+ specialRepCanonicalMap(231);
+ });
+ });
+
+ it("throws an error if the value is not a number", () => {
+ assertThrows(() => {
+ specialRepCanonicalMap("NaN");
+ });
+ });
+});
+
+describe("decimalLexicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(decimalLexicalMap("4.0"), 4);
+ assertStrictEquals(decimalLexicalMap("-231"), -231);
+ assertStrictEquals(decimalLexicalMap("+23.1"), 23.1);
+ assertStrictEquals(decimalLexicalMap("-0.24"), -0.24);
+ assertStrictEquals(decimalLexicalMap(".008500"), 0.0085);
+ assertStrictEquals(decimalLexicalMap("0"), 0);
+ assertStrictEquals(decimalLexicalMap("-0"), 0);
+ });
+
+ it("throws an error if the value is in exponential notation", () => {
+ assertThrows(() => {
+ decimalLexicalMap("23E1");
+ });
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ decimalLexicalMap(231);
+ });
+ });
+});
+
+describe("decimalCanonicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(decimalCanonicalMap(4), "4");
+ assertStrictEquals(decimalCanonicalMap(-231), "-231");
+ assertStrictEquals(decimalCanonicalMap(23.1), "23.1");
+ assertStrictEquals(decimalCanonicalMap(-.24), "-0.24");
+ assertStrictEquals(
+ decimalCanonicalMap(10 ** 21.5),
+ "3162277660168379400000",
+ );
+ assertStrictEquals(decimalCanonicalMap(.0085), "0.0085");
+ assertStrictEquals(
+ decimalCanonicalMap(10 ** -6.5),
+ "0.0000003162277660168379",
+ );
+ assertStrictEquals(decimalCanonicalMap(0), "0");
+ assertStrictEquals(decimalCanonicalMap(-0), "0");
+ });
+
+ it("throws an error if the value is not a number", () => {
+ assertThrows(() => {
+ decimalCanonicalMap("231");
+ });
+ });
+});
+
+describe("floatLexicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(floatLexicalMap("4.0"), Math.fround(4));
+ assertStrictEquals(floatLexicalMap("-231"), Math.fround(-231));
+ assertStrictEquals(floatLexicalMap("+23.1"), Math.fround(23.1));
+ assertStrictEquals(floatLexicalMap("-0.24"), Math.fround(-0.24));
+ assertStrictEquals(
+ floatLexicalMap(".008500"),
+ Math.fround(0.0085),
+ );
+ assertStrictEquals(floatLexicalMap("23E1"), Math.fround(230));
+ assertStrictEquals(floatLexicalMap("4E-20"), Math.fround(4e-20));
+ assertStrictEquals(doubleLexicalMap("+0"), 0);
+ assertStrictEquals(doubleLexicalMap("-0"), -0);
+ assertStrictEquals(doubleLexicalMap("INF"), Infinity);
+ assertStrictEquals(doubleLexicalMap("+INF"), Infinity);
+ assertStrictEquals(doubleLexicalMap("-INF"), -Infinity);
+ assertStrictEquals(doubleLexicalMap("NaN"), NaN);
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ floatLexicalMap(231);
+ });
+ });
+});
+
+describe("doubleLexicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(doubleLexicalMap("4.0"), 4);
+ assertStrictEquals(doubleLexicalMap("-231"), -231);
+ assertStrictEquals(doubleLexicalMap("+23.1"), 23.1);
+ assertStrictEquals(doubleLexicalMap("-0.24"), -0.24);
+ assertStrictEquals(doubleLexicalMap(".008500"), 0.0085);
+ assertStrictEquals(doubleLexicalMap("23E1"), 230);
+ assertStrictEquals(doubleLexicalMap("4E-20"), 4e-20);
+ assertStrictEquals(doubleLexicalMap("+0"), 0);
+ assertStrictEquals(doubleLexicalMap("-0"), -0);
+ assertStrictEquals(doubleLexicalMap("INF"), Infinity);
+ assertStrictEquals(doubleLexicalMap("+INF"), Infinity);
+ assertStrictEquals(doubleLexicalMap("-INF"), -Infinity);
+ assertStrictEquals(doubleLexicalMap("NaN"), NaN);
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ doubleLexicalMap(231);
+ });
+ });
+});
+
+describe("floatCanonicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(floatCanonicalMap(4), "4.0E0");
+ assertStrictEquals(floatCanonicalMap(-231), "-2.31E2");
+ assertStrictEquals(
+ floatCanonicalMap(23.100000381469727),
+ "2.3100000381469727E1",
+ );
+ assertStrictEquals(
+ floatCanonicalMap(-0.23999999463558197),
+ "-2.3999999463558197E-1",
+ );
+ assertStrictEquals(
+ floatCanonicalMap(0.008500000461935997),
+ "8.500000461935997E-3",
+ );
+ assertStrictEquals(
+ floatCanonicalMap(3.1622776321769755e21),
+ "3.1622776321769755E21",
+ );
+ assertStrictEquals(
+ floatCanonicalMap(3.1622775509276835e-7),
+ "3.1622775509276835E-7",
+ );
+ assertStrictEquals(floatCanonicalMap(0), "0.0E0");
+ assertStrictEquals(floatCanonicalMap(-0), "-0.0E0");
+ assertStrictEquals(floatCanonicalMap(Infinity), "INF");
+ assertStrictEquals(floatCanonicalMap(-Infinity), "-INF");
+ assertStrictEquals(floatCanonicalMap(NaN), "NaN");
+ });
+
+ it("throws an error if the value is not a float", () => {
+ assertThrows(() => {
+ floatCanonicalMap(23.1);
+ });
+ });
+
+ it("throws an error if the value is not a number", () => {
+ assertThrows(() => {
+ floatCanonicalMap("231");
+ });
+ });
+});
+
+describe("doubleCanonicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(doubleCanonicalMap(4), "4.0E0");
+ assertStrictEquals(doubleCanonicalMap(-231), "-2.31E2");
+ assertStrictEquals(doubleCanonicalMap(23.1), "2.31E1");
+ assertStrictEquals(doubleCanonicalMap(-0.24), "-2.4E-1");
+ assertStrictEquals(doubleCanonicalMap(.0085), "8.5E-3");
+ assertStrictEquals(
+ doubleCanonicalMap(10 ** 21.5),
+ "3.1622776601683794E21",
+ );
+ assertStrictEquals(
+ doubleCanonicalMap(10 ** -6.5),
+ "3.162277660168379E-7",
+ );
+ assertStrictEquals(doubleCanonicalMap(0), "0.0E0");
+ assertStrictEquals(doubleCanonicalMap(-0), "-0.0E0");
+ assertStrictEquals(doubleCanonicalMap(Infinity), "INF");
+ assertStrictEquals(doubleCanonicalMap(-Infinity), "-INF");
+ assertStrictEquals(doubleCanonicalMap(NaN), "NaN");
+ });
+
+ it("throws an error if the value is not a number", () => {
+ assertThrows(() => {
+ doubleCanonicalMap("231");
+ });
+ });
+});
+
+describe("durationMap", () => {
+ it("maps the value", () => {
+ assertEquals(durationMap("P0Y0M0DT0H0M0.0S"), {
+ months: 0,
+ seconds: 0,
+ });
+ assertStrictEquals(durationMap("-P0Y0M0DT0H0M0.0S").months, 0);
+ assertStrictEquals(durationMap("-P0Y0M0DT0H0M0.0S").seconds, 0);
+ assertEquals(durationMap("-P1Y2M"), { months: -14, seconds: 0 });
+ assertStrictEquals(durationMap("-P1Y2M").seconds, 0);
+ assertEquals(durationMap("-P3DT5H7M11S"), {
+ months: 0,
+ seconds: -277631,
+ });
+ assertStrictEquals(durationMap("-P3DT5H7M11S").months, 0);
+ assertEquals(durationMap("-P1Y2M3DT5H7M11.13S"), {
+ months: -14,
+ seconds: -277631.13,
+ });
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ durationMap(0);
+ });
+ assertThrows(() => {
+ durationMap({ months: 0, seconds: 0 });
+ });
+ });
+
+ it("throws an error if the value has no fields", () => {
+ assertThrows(() => {
+ durationMap("P");
+ });
+ });
+
+ it("throws an error if the value has a T but no time", () => {
+ assertThrows(() => {
+ durationMap("P0Y0M0DT");
+ });
+ });
+});
+
+describe("yearMonthDurationMap", () => {
+ it("maps the value", () => {
+ assertEquals(yearMonthDurationMap("P0Y0M"), {
+ months: 0,
+ seconds: 0,
+ });
+ assertStrictEquals(yearMonthDurationMap("-P0Y0M").months, 0);
+ assertStrictEquals(yearMonthDurationMap("-P0Y0M").seconds, 0);
+ assertEquals(yearMonthDurationMap("-P1Y2M"), {
+ months: -14,
+ seconds: 0,
+ });
+ assertStrictEquals(yearMonthDurationMap("-P1Y2M").seconds, 0);
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ yearMonthDurationMap(0);
+ });
+ assertThrows(() => {
+ yearMonthDurationMap({ months: 0, seconds: 0 });
+ });
+ });
+
+ it("throws an error if the value has no fields", () => {
+ assertThrows(() => {
+ yearMonthDurationMap("P");
+ });
+ });
+});
+
+describe("dayTimeDurationMap", () => {
+ it("maps the value", () => {
+ assertEquals(dayTimeDurationMap("P0DT0H0M0.0S"), {
+ months: 0,
+ seconds: 0,
+ });
+ assertStrictEquals(dayTimeDurationMap("-P0DT0H0M0.0S").months, 0);
+ assertStrictEquals(dayTimeDurationMap("-P0DT0H0M0.0S").seconds, 0);
+ assertEquals(dayTimeDurationMap("-P3DT5H7M11.13S"), {
+ months: 0,
+ seconds: -277631.13,
+ });
+ assertStrictEquals(
+ dayTimeDurationMap("-P3DT5H7M11.13S").months,
+ 0,
+ );
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ dayTimeDurationMap(0);
+ });
+ assertThrows(() => {
+ dayTimeDurationMap({ months: 0, seconds: 0 });
+ });
+ });
+
+ it("throws an error if the value has no fields", () => {
+ assertThrows(() => {
+ dayTimeDurationMap("P");
+ });
+ });
+
+ it("throws an error if the value has a T but no time", () => {
+ assertThrows(() => {
+ dayTimeDurationMap("P0DT");
+ });
+ });
+});
+
+describe("durationCanonicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(
+ durationCanonicalMap({ months: 0, seconds: 0 }),
+ "PT0S",
+ );
+ assertStrictEquals(
+ durationCanonicalMap({ months: -0, seconds: -0 }),
+ "PT0S",
+ );
+ assertStrictEquals(
+ durationCanonicalMap({ months: 23, seconds: 1234.5 }),
+ "P1Y11MT20M34.5S",
+ );
+ assertStrictEquals(
+ durationCanonicalMap({ months: -14, seconds: -277631 }),
+ "-P1Y2M3DT5H7M11S",
+ );
+ });
+
+ it("throws an error if the value is not a duration", () => {
+ assertThrows(() => {
+ durationCanonicalMap("PT0S");
+ });
+ assertThrows(() => {
+ durationCanonicalMap({});
+ });
+ assertThrows(() => {
+ durationCanonicalMap(0);
+ });
+ });
+
+ it("throws an error if the signs of the terms do not match", () => {
+ assertThrows(() => {
+ durationCanonicalMap({ months: 1, seconds: -1 });
+ });
+ });
+});
+
+describe("yearMonthDurationCanonicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(
+ yearMonthDurationCanonicalMap({ months: 0, seconds: 0 }),
+ "P0M",
+ );
+ assertStrictEquals(
+ yearMonthDurationCanonicalMap({ months: -0, seconds: -0 }),
+ "P0M",
+ );
+ assertStrictEquals(
+ yearMonthDurationCanonicalMap({ months: 23, seconds: 0 }),
+ "P1Y11M",
+ );
+ assertStrictEquals(
+ yearMonthDurationCanonicalMap({ months: -14, seconds: 0 }),
+ "-P1Y2M",
+ );
+ });
+
+ it("throws an error if the value is not a duration", () => {
+ assertThrows(() => {
+ yearMonthDurationCanonicalMap("P0M");
+ });
+ assertThrows(() => {
+ yearMonthDurationCanonicalMap({});
+ });
+ assertThrows(() => {
+ yearMonthDurationCanonicalMap(0);
+ });
+ });
+
+ it("throws an error if the value of seconds is not zero", () => {
+ assertThrows(() => {
+ yearMonthDurationCanonicalMap({ months: 1, seconds: 1 });
+ });
+ });
+});
+
+describe("dayTimeDurationCanonicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(
+ dayTimeDurationCanonicalMap({ months: 0, seconds: 0 }),
+ "PT0S",
+ );
+ assertStrictEquals(
+ dayTimeDurationCanonicalMap({ months: -0, seconds: -0 }),
+ "PT0S",
+ );
+ assertStrictEquals(
+ dayTimeDurationCanonicalMap({ months: 0, seconds: 1234.5 }),
+ "PT20M34.5S",
+ );
+ assertStrictEquals(
+ dayTimeDurationCanonicalMap({ months: 0, seconds: -277631 }),
+ "-P3DT5H7M11S",
+ );
+ });
+
+ it("throws an error if the value is not a duration", () => {
+ assertThrows(() => {
+ dayTimeDurationCanonicalMap("PT0S");
+ });
+ assertThrows(() => {
+ dayTimeDurationCanonicalMap({});
+ });
+ assertThrows(() => {
+ dayTimeDurationCanonicalMap(0);
+ });
+ });
+
+ it("throws an error if the value of months is not zero", () => {
+ assertThrows(() => {
+ dayTimeDurationCanonicalMap({ months: 1, seconds: 1 });
+ });
+ });
+});
+
+describe("daysInMonth", () => {
+ it("returns the correct value when no year is supplied", () => {
+ assertStrictEquals(daysInMonth(undefined, 1), 31);
+ assertStrictEquals(daysInMonth(undefined, 2), 28);
+ assertStrictEquals(daysInMonth(undefined, 3), 31);
+ assertStrictEquals(daysInMonth(undefined, 4), 30);
+ assertStrictEquals(daysInMonth(undefined, 5), 31);
+ assertStrictEquals(daysInMonth(undefined, 6), 30);
+ assertStrictEquals(daysInMonth(undefined, 7), 31);
+ assertStrictEquals(daysInMonth(undefined, 8), 31);
+ assertStrictEquals(daysInMonth(undefined, 9), 30);
+ assertStrictEquals(daysInMonth(undefined, 10), 31);
+ assertStrictEquals(daysInMonth(undefined, 11), 30);
+ assertStrictEquals(daysInMonth(undefined, 12), 31);
+ });
+
+ it("returns the correct value when a year is supplied", () => {
+ assertStrictEquals(daysInMonth(1972, 12), 31);
+ assertStrictEquals(daysInMonth(1972, 2), 29);
+ assertStrictEquals(daysInMonth(1970, 2), 28);
+ assertStrictEquals(daysInMonth(2000, 2), 29);
+ assertStrictEquals(daysInMonth(1000, 2), 28);
+ assertStrictEquals(daysInMonth(800, 2), 29);
+ assertStrictEquals(daysInMonth(100, 2), 28);
+ assertStrictEquals(daysInMonth(0, 2), 29);
+ });
+
+ it("throws an error if the month is not an integer between 1 and 12", () => {
+ assertThrows(() => {
+ daysInMonth(undefined, 0);
+ });
+ assertThrows(() => {
+ daysInMonth(undefined, 13);
+ });
+ assertThrows(() => {
+ daysInMonth(undefined, "1");
+ });
+ });
+
+ it("throws an error if the year is not undefined or an integer", () => {
+ assertThrows(() => {
+ daysInMonth(null, 1);
+ });
+ assertThrows(() => {
+ daysInMonth(2.31, 1);
+ });
+ assertThrows(() => {
+ daysInMonth("undefined", 1);
+ });
+ assertThrows(() => {
+ daysInMonth("1", 1);
+ });
+ });
+});
+
+describe("newDateTime", () => {
+ it("returns a new datetime object", () => {
+ assertEquals(newDateTime(1, 1, 1, 0, 0, 0, 0), {
+ year: 1,
+ month: 1,
+ day: 1,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: 0,
+ });
+ assertEquals(newDateTime(-1, 12, 31, 23, 59, 59.99, -840), {
+ year: -1,
+ month: 12,
+ day: 31,
+ hour: 23,
+ minute: 59,
+ second: 59.99,
+ timezoneOffset: -840,
+ });
+ });
+
+ it("fills absent values with undefined", () => {
+ assertEquals(newDateTime(), {
+ year: undefined,
+ month: undefined,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ });
+ });
+
+ it("normalizes the date", () => {
+ assertEquals(newDateTime(1, 2, 31, 24), {
+ year: 1,
+ month: 3,
+ day: 4,
+ hour: 0,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ });
+ assertEquals(newDateTime(1, undefined, 31, 24), {
+ year: 1,
+ month: undefined,
+ day: 1,
+ hour: 0,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ });
+ });
+
+ it("forbids out‐of‐range values", () => {
+ assertThrows(() => {
+ newDateTime(undefined, 0);
+ });
+ assertThrows(() => {
+ newDateTime(undefined, 13);
+ });
+ assertThrows(() => {
+ newDateTime(undefined, undefined, 0);
+ });
+ assertThrows(() => {
+ newDateTime(undefined, undefined, 32);
+ });
+ assertThrows(() => {
+ newDateTime(undefined, undefined, undefined, -1);
+ });
+ assertThrows(() => {
+ newDateTime(undefined, undefined, undefined, 25);
+ });
+ assertThrows(() => {
+ newDateTime(undefined, undefined, undefined, undefined, -1);
+ });
+ assertThrows(() => {
+ newDateTime(undefined, undefined, undefined, undefined, 61);
+ });
+ assertThrows(() => {
+ newDateTime(
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ -1,
+ );
+ });
+ assertThrows(() => {
+ newDateTime(
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ 61,
+ );
+ });
+ assertThrows(() => {
+ newDateTime(
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ -841,
+ );
+ });
+ assertThrows(() => {
+ newDateTime(
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ 841,
+ );
+ });
+ });
+
+ it("forbids values of the incorrect type", () => {
+ assertThrows(() => {
+ newDateTime(1.23);
+ });
+ assertThrows(() => {
+ newDateTime("1");
+ });
+ assertThrows(() => {
+ newDateTime(undefined, 1.23);
+ });
+ assertThrows(() => {
+ newDateTime(undefined, "1");
+ });
+ assertThrows(() => {
+ newDateTime(undefined, undefined, 1.23);
+ });
+ assertThrows(() => {
+ newDateTime(undefined, undefined, "1");
+ });
+ assertThrows(() => {
+ newDateTime(undefined, undefined, undefined, 1.23);
+ });
+ assertThrows(() => {
+ newDateTime(undefined, undefined, undefined, "1");
+ });
+ assertThrows(() => {
+ newDateTime(undefined, undefined, undefined, undefined, 1.23);
+ });
+ assertThrows(() => {
+ newDateTime(undefined, undefined, undefined, undefined, "1");
+ });
+ assertThrows(() => {
+ newDateTime(
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ "1",
+ );
+ });
+ assertThrows(() => {
+ newDateTime(
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ 1.23,
+ );
+ });
+ assertThrows(() => {
+ newDateTime(
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ "1",
+ );
+ });
+ });
+});
+
+describe("dateTimePlusDuration", () => {
+ it("fills absent values with undefined", () => {
+ assertEquals(dateTimePlusDuration({ months: 0, seconds: 0 }, {}), {
+ year: undefined,
+ month: undefined,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ });
+ });
+
+ it("correctly adds the duration to the datetime", () => {
+ // These examples are taken from the X·S·D spec.
+ const fuzzyResult = dateTimePlusDuration({
+ months: 15,
+ seconds: 457803.3,
+ }, {
+ year: 2000,
+ month: 1,
+ day: 12,
+ hour: 12,
+ minute: 13,
+ second: 14,
+ timezoneOffset: 0,
+ });
+ assertObjectMatch(fuzzyResult, {
+ year: 2001,
+ month: 4,
+ day: 17,
+ hour: 19,
+ minute: 23,
+ // seconds must be matched fuzzily
+ timezoneOffset: 0,
+ });
+ assertAlmostEquals(fuzzyResult.second, 17.3);
+ assertEquals(
+ dateTimePlusDuration({ months: -3, seconds: 0 }, {
+ year: 2000,
+ month: 1,
+ }),
+ {
+ year: 1999,
+ month: 10,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ },
+ );
+ assertEquals(
+ dateTimePlusDuration({ months: 0, seconds: 118800 }, {
+ year: 2000,
+ month: 1,
+ day: 12,
+ }),
+ {
+ year: 2000,
+ month: 1,
+ day: 13,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ },
+ );
+ assertEquals(
+ dateTimePlusDuration(
+ { months: 1, seconds: 0 },
+ dateTimePlusDuration({ months: 0, seconds: 86400 }, {
+ year: 2000,
+ month: 3,
+ day: 30,
+ }),
+ ),
+ {
+ year: 2000,
+ month: 4,
+ day: 30,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ },
+ );
+ assertEquals(
+ dateTimePlusDuration(
+ { months: 0, seconds: 86400 },
+ dateTimePlusDuration({ months: 1, seconds: 0 }, {
+ year: 2000,
+ month: 3,
+ day: 30,
+ }),
+ ),
+ {
+ year: 2000,
+ month: 5,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ },
+ );
+ });
+
+ it("follows 28 February with 01 March", () => {
+ assertEquals(
+ dateTimePlusDuration({ months: 0, seconds: 86400 }, {
+ month: 2,
+ day: 28,
+ }),
+ {
+ year: undefined,
+ month: 3,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ },
+ );
+ });
+
+ it("allows 29 February without a year", () => {
+ assertEquals(
+ dateTimePlusDuration({ months: 0, seconds: 1 }, {
+ month: 2,
+ day: 29,
+ }),
+ {
+ year: undefined,
+ month: 2,
+ day: 29,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ },
+ );
+ });
+
+ it("follows 29 February with 01 March", () => {
+ assertEquals(
+ dateTimePlusDuration({ months: 0, seconds: 86400 }, {
+ month: 2,
+ day: 29,
+ }),
+ {
+ year: undefined,
+ month: 3,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ },
+ );
+ });
+
+ it("throws if not provided with a valid duration", () => {
+ assertThrows(() => {
+ dateTimePlusDuration({ months: 0 }, {});
+ });
+ assertThrows(() => {
+ dateTimePlusDuration({ seconds: 0 }, {});
+ });
+ assertThrows(() => {
+ dateTimePlusDuration({}, {});
+ });
+ assertThrows(() => {
+ dateTimePlusDuration(0, {});
+ });
+ assertThrows(() => {
+ dateTimePlusDuration("PT0S", {});
+ });
+ });
+
+ it("throws if not provided with a valid date·time", () => {
+ assertThrows(() => {
+ dateTimePlusDuration({ months: 0, seconds: 0 }, 0);
+ });
+ assertThrows(() => {
+ dateTimePlusDuration({ seconds: 0 }, "2000-01-12T12:13:14Z");
+ });
+ });
+});
+
+describe("timeOnTimeline", () => {
+ it("gets the time", () => {
+ assertStrictEquals(
+ timeOnTimeline({
+ year: 1,
+ month: 1,
+ day: 1,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: 0,
+ }),
+ 0,
+ );
+ assertStrictEquals(
+ timeOnTimeline({
+ year: 1,
+ month: 1,
+ day: 1,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: 840,
+ }),
+ -50400,
+ );
+ assertStrictEquals(timeOnTimeline({}), 62230204800);
+ assertStrictEquals(timeOnTimeline({ year: 0 }), -86400);
+ });
+
+ it("throws if not provided with a valid date·time", () => {
+ assertThrows(() => {
+ timeOnTimeline(0);
+ });
+ assertThrows(() => {
+ timeOnTimeline("2000-01-12T12:13:14Z");
+ });
+ });
+});
+
+describe("yearFragValue", () => {
+ it("maps the value", () => {
+ assertStrictEquals(yearFragValue("0001"), 1);
+ assertStrictEquals(yearFragValue("-0000"), 0);
+ assertStrictEquals(yearFragValue("-10000"), -10000);
+ assertStrictEquals(yearFragValue("99999"), 99999);
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ yearFragValue(1);
+ });
+ });
+
+ it("throws an error if the value is not a valid yearFrag", () => {
+ assertThrows(() => {
+ yearFragValue("");
+ });
+ assertThrows(() => {
+ yearFragValue("1");
+ });
+ assertThrows(() => {
+ yearFragValue("01000");
+ });
+ });
+});
+
+describe("monthFragValue", () => {
+ it("maps the value", () => {
+ assertStrictEquals(monthFragValue("01"), 1);
+ assertStrictEquals(monthFragValue("10"), 10);
+ assertStrictEquals(monthFragValue("12"), 12);
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ monthFragValue(1);
+ });
+ });
+
+ it("throws an error if the value is not a valid monthFrag", () => {
+ assertThrows(() => {
+ monthFragValue("");
+ });
+ assertThrows(() => {
+ monthFragValue("1");
+ });
+ assertThrows(() => {
+ monthFragValue("-1");
+ });
+ assertThrows(() => {
+ monthFragValue("-01");
+ });
+ assertThrows(() => {
+ monthFragValue("00");
+ });
+ assertThrows(() => {
+ monthFragValue("13");
+ });
+ assertThrows(() => {
+ monthFragValue("20");
+ });
+ });
+});
+
+describe("dayFragValue", () => {
+ it("maps the value", () => {
+ assertStrictEquals(dayFragValue("01"), 1);
+ assertStrictEquals(dayFragValue("10"), 10);
+ assertStrictEquals(dayFragValue("19"), 19);
+ assertStrictEquals(dayFragValue("20"), 20);
+ assertStrictEquals(dayFragValue("29"), 29);
+ assertStrictEquals(dayFragValue("30"), 30);
+ assertStrictEquals(dayFragValue("31"), 31);
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ dayFragValue(1);
+ });
+ });
+
+ it("throws an error if the value is not a valid dayFrag", () => {
+ assertThrows(() => {
+ dayFragValue("");
+ });
+ assertThrows(() => {
+ dayFragValue("1");
+ });
+ assertThrows(() => {
+ dayFragValue("-1");
+ });
+ assertThrows(() => {
+ dayFragValue("-01");
+ });
+ assertThrows(() => {
+ dayFragValue("00");
+ });
+ assertThrows(() => {
+ dayFragValue("32");
+ });
+ assertThrows(() => {
+ dayFragValue("40");
+ });
+ });
+});
+
+describe("hourFragValue", () => {
+ it("maps the value", () => {
+ assertStrictEquals(hourFragValue("00"), 0);
+ assertStrictEquals(hourFragValue("01"), 1);
+ assertStrictEquals(hourFragValue("10"), 10);
+ assertStrictEquals(hourFragValue("19"), 19);
+ assertStrictEquals(hourFragValue("20"), 20);
+ assertStrictEquals(hourFragValue("23"), 23);
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ hourFragValue(0);
+ });
+ });
+
+ it("throws an error if the value is not a valid hourFrag", () => {
+ assertThrows(() => {
+ hourFragValue("");
+ });
+ assertThrows(() => {
+ hourFragValue("1");
+ });
+ assertThrows(() => {
+ hourFragValue("-1");
+ });
+ assertThrows(() => {
+ hourFragValue("-01");
+ });
+ assertThrows(() => {
+ hourFragValue("24");
+ });
+ assertThrows(() => {
+ hourFragValue("30");
+ });
+ assertThrows(() => {
+ hourFragValue("01.23");
+ });
+ });
+});
+
+describe("minuteFragValue", () => {
+ it("maps the value", () => {
+ assertStrictEquals(minuteFragValue("00"), 0);
+ assertStrictEquals(minuteFragValue("01"), 1);
+ assertStrictEquals(minuteFragValue("10"), 10);
+ assertStrictEquals(minuteFragValue("19"), 19);
+ assertStrictEquals(minuteFragValue("50"), 50);
+ assertStrictEquals(minuteFragValue("59"), 59);
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ minuteFragValue(0);
+ });
+ });
+
+ it("throws an error if the value is not a valid minuteFrag", () => {
+ assertThrows(() => {
+ minuteFragValue("");
+ });
+ assertThrows(() => {
+ minuteFragValue("1");
+ });
+ assertThrows(() => {
+ minuteFragValue("-1");
+ });
+ assertThrows(() => {
+ minuteFragValue("-01");
+ });
+ assertThrows(() => {
+ minuteFragValue("60");
+ });
+ assertThrows(() => {
+ minuteFragValue("01.23");
+ });
+ });
+});
+
+describe("secondFragValue", () => {
+ it("maps the value", () => {
+ assertStrictEquals(secondFragValue("00"), 0);
+ assertStrictEquals(secondFragValue("00.00000"), 0);
+ assertStrictEquals(secondFragValue("01"), 1);
+ assertStrictEquals(secondFragValue("10"), 10);
+ assertStrictEquals(secondFragValue("19"), 19);
+ assertStrictEquals(secondFragValue("50"), 50);
+ assertStrictEquals(secondFragValue("59"), 59);
+ assertStrictEquals(secondFragValue("59.99"), 59.99);
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ secondFragValue(0);
+ });
+ });
+
+ it("throws an error if the value is not a valid secondFrag", () => {
+ assertThrows(() => {
+ secondFragValue("");
+ });
+ assertThrows(() => {
+ secondFragValue("1");
+ });
+ assertThrows(() => {
+ secondFragValue("-1");
+ });
+ assertThrows(() => {
+ secondFragValue("-01");
+ });
+ assertThrows(() => {
+ secondFragValue("60");
+ });
+ });
+});
+
+describe("timezoneFragValue", () => {
+ it("maps the value", () => {
+ assertStrictEquals(timezoneFragValue("Z"), 0);
+ assertStrictEquals(timezoneFragValue("+00:00"), 0);
+ assertStrictEquals(timezoneFragValue("-00:00"), 0);
+ assertStrictEquals(timezoneFragValue("+00:01"), 1);
+ assertStrictEquals(timezoneFragValue("+00:59"), 59);
+ assertStrictEquals(timezoneFragValue("+01:23"), 83);
+ assertStrictEquals(timezoneFragValue("-09:23"), -563);
+ assertStrictEquals(timezoneFragValue("-10:23"), -623);
+ assertStrictEquals(timezoneFragValue("+13:59"), 839);
+ assertStrictEquals(timezoneFragValue("+14:00"), 840);
+ assertStrictEquals(timezoneFragValue("-14:00"), -840);
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ timezoneFragValue(0);
+ });
+ });
+
+ it("throws an error if the value is not a valid timezoneFrag", () => {
+ assertThrows(() => {
+ timezoneFragValue("");
+ });
+ assertThrows(() => {
+ timezoneFragValue("0");
+ });
+ assertThrows(() => {
+ timezoneFragValue("+Z");
+ });
+ assertThrows(() => {
+ timezoneFragValue("-Z");
+ });
+ assertThrows(() => {
+ timezoneFragValue("Z+00:00");
+ });
+ assertThrows(() => {
+ timezoneFragValue("00:00");
+ });
+ assertThrows(() => {
+ timezoneFragValue("0000");
+ });
+ assertThrows(() => {
+ timezoneFragValue("+0000");
+ });
+ assertThrows(() => {
+ timezoneFragValue("840");
+ });
+ assertThrows(() => {
+ timezoneFragValue("+840");
+ });
+ assertThrows(() => {
+ timezoneFragValue("+-00:00");
+ });
+ assertThrows(() => {
+ timezoneFragValue("-+00:00");
+ });
+ assertThrows(() => {
+ timezoneFragValue("+14:01");
+ });
+ assertThrows(() => {
+ timezoneFragValue("+15:00");
+ });
+ });
+});
+
+describe("dateTimeLexicalMap", () => {
+ it("maps the value", () => {
+ assertEquals(dateTimeLexicalMap("0001-01-01T00:00:00.00Z"), {
+ year: 1,
+ month: 1,
+ day: 1,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: 0,
+ });
+ assertEquals(dateTimeLexicalMap("-0001-12-31T24:00:00Z"), {
+ year: 0,
+ month: 1,
+ day: 1,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: 0,
+ });
+ assertEquals(dateTimeLexicalMap("-0000-01-01T00:00:00-00:00"), {
+ year: 0,
+ month: 1,
+ day: 1,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: 0,
+ });
+ assertEquals(dateTimeLexicalMap("0001-01-01T00:00:00.00"), {
+ year: 1,
+ month: 1,
+ day: 1,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: undefined,
+ });
+ assertEquals(dateTimeLexicalMap("99999-12-31T23:59:59.99+14:00"), {
+ year: 99999,
+ month: 12,
+ day: 31,
+ hour: 23,
+ minute: 59,
+ second: 59.99,
+ timezoneOffset: 840,
+ });
+ assertEquals(dateTimeLexicalMap("1972-02-29T00:00:00"), {
+ year: 1972,
+ month: 2,
+ day: 29,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: undefined,
+ });
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ dateTimeLexicalMap(0);
+ });
+ });
+
+ it("throws an error if the value is not a valid dateTime", () => {
+ assertThrows(() => {
+ dateTimeLexicalMap("");
+ });
+ assertThrows(() => {
+ dateTimeLexicalMap("1");
+ });
+ assertThrows(() => {
+ dateTimeLexicalMap("10-10-10T00:00:00");
+ });
+ assertThrows(() => {
+ dateTimeLexicalMap("2345-67-89T01:23:45");
+ });
+ assertThrows(() => {
+ dateTimeLexicalMap("01000-10-10T10:10:10");
+ });
+ assertThrows(() => {
+ dateTimeLexicalMap("0001-01-01T00:00:00.00+0000");
+ });
+ assertThrows(() => {
+ dateTimeLexicalMap("0001-01-01T00:00:00.00+14:01");
+ });
+ assertThrows(() => {
+ dateTimeLexicalMap("1970-02-29T00:00:00.00");
+ });
+ });
+});
+
+describe("timeLexicalMap", () => {
+ it("maps the value", () => {
+ assertEquals(timeLexicalMap("00:00:00.00Z"), {
+ year: undefined,
+ month: undefined,
+ day: undefined,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: 0,
+ });
+ assertEquals(timeLexicalMap("24:00:00Z"), {
+ year: undefined,
+ month: undefined,
+ day: undefined,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: 0,
+ });
+ assertEquals(timeLexicalMap("23:59:59.99+14:00"), {
+ year: undefined,
+ month: undefined,
+ day: undefined,
+ hour: 23,
+ minute: 59,
+ second: 59.99,
+ timezoneOffset: 840,
+ });
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ timeLexicalMap(0);
+ });
+ });
+
+ it("throws an error if the value is not a valid time", () => {
+ assertThrows(() => {
+ timeLexicalMap("");
+ });
+ assertThrows(() => {
+ timeLexicalMap("1");
+ });
+ assertThrows(() => {
+ timeLexicalMap("00:00");
+ });
+ assertThrows(() => {
+ timeLexicalMap("23:45:67");
+ });
+ assertThrows(() => {
+ timeLexicalMap("-10:10:10");
+ });
+ assertThrows(() => {
+ timeLexicalMap("00:00:00.00+0000");
+ });
+ assertThrows(() => {
+ timeLexicalMap("00:00:00.00+14:01");
+ });
+ assertThrows(() => {
+ timeLexicalMap("T00:00:00");
+ });
+ });
+});
+
+describe("dateLexicalMap", () => {
+ it("maps the value", () => {
+ assertEquals(dateLexicalMap("0001-01-01Z"), {
+ year: 1,
+ month: 1,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ });
+ assertEquals(dateLexicalMap("-0001-12-31Z"), {
+ year: -1,
+ month: 12,
+ day: 31,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ });
+ assertEquals(dateLexicalMap("-0000-01-01-00:00"), {
+ year: 0,
+ month: 1,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ });
+ assertEquals(dateLexicalMap("0001-01-01"), {
+ year: 1,
+ month: 1,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ });
+ assertEquals(dateLexicalMap("99999-12-31+14:00"), {
+ year: 99999,
+ month: 12,
+ day: 31,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 840,
+ });
+ assertEquals(dateLexicalMap("1972-02-29"), {
+ year: 1972,
+ month: 2,
+ day: 29,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ });
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ dateLexicalMap(0);
+ });
+ });
+
+ it("throws an error if the value is not a valid date", () => {
+ assertThrows(() => {
+ dateLexicalMap("");
+ });
+ assertThrows(() => {
+ dateLexicalMap("1");
+ });
+ assertThrows(() => {
+ dateLexicalMap("10-10-10");
+ });
+ assertThrows(() => {
+ dateLexicalMap("2345-67-89");
+ });
+ assertThrows(() => {
+ dateLexicalMap("01000-10-10");
+ });
+ assertThrows(() => {
+ dateLexicalMap("0001-01-01+0000");
+ });
+ assertThrows(() => {
+ dateLexicalMap("0001-01-01+14:01");
+ });
+ assertThrows(() => {
+ dateLexicalMap("1970-02-29");
+ });
+ assertThrows(() => {
+ dateLexicalMap("1972-02-29T00:00:00");
+ });
+ });
+});
+
+describe("gYearMonthLexicalMap", () => {
+ it("maps the value", () => {
+ assertEquals(gYearMonthLexicalMap("0001-01Z"), {
+ year: 1,
+ month: 1,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ });
+ assertEquals(gYearMonthLexicalMap("-0001-12Z"), {
+ year: -1,
+ month: 12,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ });
+ assertEquals(gYearMonthLexicalMap("-0000-01-00:00"), {
+ year: 0,
+ month: 1,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ });
+ assertEquals(gYearMonthLexicalMap("0001-01"), {
+ year: 1,
+ month: 1,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ });
+ assertEquals(gYearMonthLexicalMap("99999-12+14:00"), {
+ year: 99999,
+ month: 12,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 840,
+ });
+ assertEquals(gYearMonthLexicalMap("1972-02"), {
+ year: 1972,
+ month: 2,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ });
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ gYearMonthLexicalMap(0);
+ });
+ });
+
+ it("throws an error if the value is not a valid gYearMonth", () => {
+ assertThrows(() => {
+ gYearMonthLexicalMap("");
+ });
+ assertThrows(() => {
+ gYearMonthLexicalMap("1");
+ });
+ assertThrows(() => {
+ gYearMonthLexicalMap("10-10");
+ });
+ assertThrows(() => {
+ gYearMonthLexicalMap("2345-67");
+ });
+ assertThrows(() => {
+ gYearMonthLexicalMap("01000-10");
+ });
+ assertThrows(() => {
+ gYearMonthLexicalMap("0001-01+0000");
+ });
+ assertThrows(() => {
+ gYearMonthLexicalMap("0001-01+14:01");
+ });
+ assertThrows(() => {
+ gYearMonthLexicalMap("1970-01-01");
+ });
+ assertThrows(() => {
+ gYearMonthLexicalMap("1972-02T00:00:00");
+ });
+ });
+});
+
+describe("gYearLexicalMap", () => {
+ it("maps the value", () => {
+ assertEquals(gYearLexicalMap("0001Z"), {
+ year: 1,
+ month: undefined,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ });
+ assertEquals(gYearLexicalMap("-0001Z"), {
+ year: -1,
+ month: undefined,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ });
+ assertEquals(gYearLexicalMap("-0000-00:00"), {
+ year: 0,
+ month: undefined,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ });
+ assertEquals(gYearLexicalMap("0001"), {
+ year: 1,
+ month: undefined,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ });
+ assertEquals(gYearLexicalMap("99999+14:00"), {
+ year: 99999,
+ month: undefined,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 840,
+ });
+ assertEquals(gYearLexicalMap("1972"), {
+ year: 1972,
+ month: undefined,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ });
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ gYearLexicalMap(0);
+ });
+ });
+
+ it("throws an error if the value is not a valid gYear", () => {
+ assertThrows(() => {
+ gYearLexicalMap("");
+ });
+ assertThrows(() => {
+ gYearLexicalMap("1");
+ });
+ assertThrows(() => {
+ gYearLexicalMap("10");
+ });
+ assertThrows(() => {
+ gYearLexicalMap("01000");
+ });
+ assertThrows(() => {
+ gYearLexicalMap("0001+0000");
+ });
+ assertThrows(() => {
+ gYearLexicalMap("0001+14:01");
+ });
+ assertThrows(() => {
+ gYearLexicalMap("1970-01");
+ });
+ assertThrows(() => {
+ gYearLexicalMap("1972T00:00:00");
+ });
+ });
+});
+
+describe("gMonthDayLexicalMap", () => {
+ it("maps the value", () => {
+ assertEquals(gMonthDayLexicalMap("--01-01Z"), {
+ year: undefined,
+ month: 1,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ });
+ assertEquals(gMonthDayLexicalMap("--12-31Z"), {
+ year: undefined,
+ month: 12,
+ day: 31,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ });
+ assertEquals(gMonthDayLexicalMap("--01-01-00:00"), {
+ year: undefined,
+ month: 1,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ });
+ assertEquals(gMonthDayLexicalMap("--01-01"), {
+ year: undefined,
+ month: 1,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ });
+ assertEquals(gMonthDayLexicalMap("--12-31+14:00"), {
+ year: undefined,
+ month: 12,
+ day: 31,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 840,
+ });
+ assertEquals(gMonthDayLexicalMap("--02-29"), {
+ year: undefined,
+ month: 2,
+ day: 29,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ });
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ gMonthDayLexicalMap(0);
+ });
+ });
+
+ it("throws an error if the value is not a valid gMonthDay", () => {
+ assertThrows(() => {
+ gMonthDayLexicalMap("");
+ });
+ assertThrows(() => {
+ gMonthDayLexicalMap("1");
+ });
+ assertThrows(() => {
+ gMonthDayLexicalMap("--67-89");
+ });
+ assertThrows(() => {
+ gMonthDayLexicalMap("---10-10");
+ });
+ assertThrows(() => {
+ gMonthDayLexicalMap("--01-01+0000");
+ });
+ assertThrows(() => {
+ gMonthDayLexicalMap("--01-01+14:01");
+ });
+ assertThrows(() => {
+ gMonthDayLexicalMap("--02-30");
+ });
+ assertThrows(() => {
+ gMonthDayLexicalMap("1970-01-01");
+ });
+ assertThrows(() => {
+ gMonthDayLexicalMap("--02-29T00:00:00");
+ });
+ });
+});
+
+describe("gDayLexicalMap", () => {
+ it("maps the value", () => {
+ assertEquals(gDayLexicalMap("---01Z"), {
+ year: undefined,
+ month: undefined,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ });
+ assertEquals(gDayLexicalMap("---31Z"), {
+ year: undefined,
+ month: undefined,
+ day: 31,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ });
+ assertEquals(gDayLexicalMap("---01-00:00"), {
+ year: undefined,
+ month: undefined,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ });
+ assertEquals(gDayLexicalMap("---01"), {
+ year: undefined,
+ month: undefined,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ });
+ assertEquals(gDayLexicalMap("---31+14:00"), {
+ year: undefined,
+ month: undefined,
+ day: 31,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 840,
+ });
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ gDayLexicalMap(0);
+ });
+ });
+
+ it("throws an error if the value is not a valid gDay", () => {
+ assertThrows(() => {
+ gDayLexicalMap("");
+ });
+ assertThrows(() => {
+ gDayLexicalMap("1");
+ });
+ assertThrows(() => {
+ gDayLexicalMap("10");
+ });
+ assertThrows(() => {
+ gDayLexicalMap("---89");
+ });
+ assertThrows(() => {
+ gDayLexicalMap("----10");
+ });
+ assertThrows(() => {
+ gDayLexicalMap("---01+0000");
+ });
+ assertThrows(() => {
+ gDayLexicalMap("---01+14:01");
+ });
+ assertThrows(() => {
+ gDayLexicalMap("--01-01");
+ });
+ assertThrows(() => {
+ gDayLexicalMap("1970-01-01");
+ });
+ assertThrows(() => {
+ gDayLexicalMap("---29T00:00:00");
+ });
+ });
+});
+
+describe("gMonthLexicalMap", () => {
+ it("maps the value", () => {
+ assertEquals(gMonthLexicalMap("--01Z"), {
+ year: undefined,
+ month: 1,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ });
+ assertEquals(gMonthLexicalMap("--12Z"), {
+ year: undefined,
+ month: 12,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ });
+ assertEquals(gMonthLexicalMap("--01-00:00"), {
+ year: undefined,
+ month: 1,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ });
+ assertEquals(gMonthLexicalMap("--01"), {
+ year: undefined,
+ month: 1,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ });
+ assertEquals(gMonthLexicalMap("--12+14:00"), {
+ year: undefined,
+ month: 12,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 840,
+ });
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ gMonthLexicalMap(0);
+ });
+ });
+
+ it("throws an error if the value is not a valid gMonth", () => {
+ assertThrows(() => {
+ gMonthLexicalMap("");
+ });
+ assertThrows(() => {
+ gMonthLexicalMap("1");
+ });
+ assertThrows(() => {
+ gMonthLexicalMap("01");
+ });
+ assertThrows(() => {
+ gMonthLexicalMap("--67");
+ });
+ assertThrows(() => {
+ gMonthLexicalMap("---10");
+ });
+ assertThrows(() => {
+ gMonthLexicalMap("--01+0000");
+ });
+ assertThrows(() => {
+ gMonthLexicalMap("--01+14:01");
+ });
+ assertThrows(() => {
+ gMonthLexicalMap("--13");
+ });
+ assertThrows(() => {
+ gMonthLexicalMap("1970-01-01");
+ });
+ assertThrows(() => {
+ gMonthLexicalMap("--02T00:00:00");
+ });
+ });
+});
+
+describe("yearCanonicalFragmentMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(yearCanonicalFragmentMap(1), "0001");
+ assertStrictEquals(yearCanonicalFragmentMap(0), "0000");
+ assertStrictEquals(yearCanonicalFragmentMap(-0), "0000");
+ assertStrictEquals(yearCanonicalFragmentMap(-1), "-0001");
+ assertStrictEquals(yearCanonicalFragmentMap(10000), "10000");
+ assertStrictEquals(yearCanonicalFragmentMap(-10000), "-10000");
+ });
+
+ it("throws an error if the value is not an integer", () => {
+ assertThrows(() => {
+ yearCanonicalFragmentMap("0001");
+ });
+ assertThrows(() => {
+ yearCanonicalFragmentMap(1.2);
+ });
+ });
+});
+
+describe("monthCanonicalFragmentMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(monthCanonicalFragmentMap(1), "01");
+ assertStrictEquals(monthCanonicalFragmentMap(12), "12");
+ });
+
+ it("throws an error if the value is not a positive integer less than 13", () => {
+ assertThrows(() => {
+ monthCanonicalFragmentMap("01");
+ });
+ assertThrows(() => {
+ monthCanonicalFragmentMap(0);
+ });
+ assertThrows(() => {
+ monthCanonicalFragmentMap(-1);
+ });
+ assertThrows(() => {
+ monthCanonicalFragmentMap(1.2);
+ });
+ assertThrows(() => {
+ monthCanonicalFragmentMap(13);
+ });
+ });
+});
+
+describe("dayCanonicalFragmentMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(dayCanonicalFragmentMap(1), "01");
+ assertStrictEquals(dayCanonicalFragmentMap(12), "12");
+ assertStrictEquals(dayCanonicalFragmentMap(21), "21");
+ assertStrictEquals(dayCanonicalFragmentMap(30), "30");
+ assertStrictEquals(dayCanonicalFragmentMap(31), "31");
+ });
+
+ it("throws an error if the value is not a positive integer less than 32", () => {
+ assertThrows(() => {
+ dayCanonicalFragmentMap("01");
+ });
+ assertThrows(() => {
+ dayCanonicalFragmentMap(0);
+ });
+ assertThrows(() => {
+ dayCanonicalFragmentMap(-1);
+ });
+ assertThrows(() => {
+ dayCanonicalFragmentMap(1.2);
+ });
+ assertThrows(() => {
+ dayCanonicalFragmentMap(32);
+ });
+ });
+});
+
+describe("hourCanonicalFragmentMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(hourCanonicalFragmentMap(0), "00");
+ assertStrictEquals(hourCanonicalFragmentMap(-0), "00");
+ assertStrictEquals(hourCanonicalFragmentMap(1), "01");
+ assertStrictEquals(hourCanonicalFragmentMap(12), "12");
+ assertStrictEquals(hourCanonicalFragmentMap(21), "21");
+ assertStrictEquals(hourCanonicalFragmentMap(23), "23");
+ });
+
+ it("throws an error if the value is not a nonnegative integer less than 24", () => {
+ assertThrows(() => {
+ hourCanonicalFragmentMap("00");
+ });
+ assertThrows(() => {
+ hourCanonicalFragmentMap(-1);
+ });
+ assertThrows(() => {
+ hourCanonicalFragmentMap(1.2);
+ });
+ assertThrows(() => {
+ hourCanonicalFragmentMap(24);
+ });
+ });
+});
+
+describe("minuteCanonicalFragmentMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(minuteCanonicalFragmentMap(0), "00");
+ assertStrictEquals(minuteCanonicalFragmentMap(-0), "00");
+ assertStrictEquals(minuteCanonicalFragmentMap(1), "01");
+ assertStrictEquals(minuteCanonicalFragmentMap(12), "12");
+ assertStrictEquals(minuteCanonicalFragmentMap(21), "21");
+ assertStrictEquals(minuteCanonicalFragmentMap(32), "32");
+ assertStrictEquals(minuteCanonicalFragmentMap(45), "45");
+ assertStrictEquals(minuteCanonicalFragmentMap(59), "59");
+ });
+
+ it("throws an error if the value is not a nonnegative integer less than 60", () => {
+ assertThrows(() => {
+ minuteCanonicalFragmentMap("00");
+ });
+ assertThrows(() => {
+ minuteCanonicalFragmentMap(-1);
+ });
+ assertThrows(() => {
+ minuteCanonicalFragmentMap(1.2);
+ });
+ assertThrows(() => {
+ minuteCanonicalFragmentMap(60);
+ });
+ });
+});
+
+describe("secondCanonicalFragmentMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(secondCanonicalFragmentMap(0), "00");
+ assertStrictEquals(secondCanonicalFragmentMap(-0), "00");
+ assertStrictEquals(secondCanonicalFragmentMap(1), "01");
+ assertStrictEquals(secondCanonicalFragmentMap(1.2), "01.2");
+ assertStrictEquals(secondCanonicalFragmentMap(12), "12");
+ assertStrictEquals(secondCanonicalFragmentMap(21), "21");
+ assertStrictEquals(secondCanonicalFragmentMap(23.1), "23.1");
+ assertStrictEquals(secondCanonicalFragmentMap(32), "32");
+ assertStrictEquals(secondCanonicalFragmentMap(45), "45");
+ assertStrictEquals(secondCanonicalFragmentMap(59), "59");
+ });
+
+ it("throws an error if the value is not a nonnegative decimal number less than 60", () => {
+ assertThrows(() => {
+ secondCanonicalFragmentMap("00");
+ });
+ assertThrows(() => {
+ secondCanonicalFragmentMap(-1);
+ });
+ assertThrows(() => {
+ secondCanonicalFragmentMap(60);
+ });
+ });
+});
+
+describe("timezoneCanonicalFragmentMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(timezoneCanonicalFragmentMap(0), "Z");
+ assertStrictEquals(timezoneCanonicalFragmentMap(-0), "Z");
+ assertStrictEquals(timezoneCanonicalFragmentMap(1), "+00:01");
+ assertStrictEquals(timezoneCanonicalFragmentMap(-1), "-00:01");
+ assertStrictEquals(timezoneCanonicalFragmentMap(839), "+13:59");
+ assertStrictEquals(timezoneCanonicalFragmentMap(-839), "-13:59");
+ assertStrictEquals(timezoneCanonicalFragmentMap(840), "+14:00");
+ assertStrictEquals(timezoneCanonicalFragmentMap(-840), "-14:00");
+ });
+
+ it("throws an error if the value is not an integer between -840 and 840 inclusive", () => {
+ assertThrows(() => {
+ timezoneCanonicalFragmentMap("00");
+ });
+ assertThrows(() => {
+ timezoneCanonicalFragmentMap(-841);
+ });
+ assertThrows(() => {
+ timezoneCanonicalFragmentMap(841);
+ });
+ assertThrows(() => {
+ timezoneCanonicalFragmentMap(1.2);
+ });
+ });
+});
+
+describe("dateTimeCanonicalMap", () => {
+ it("maps the value", () => {
+ assertEquals(
+ dateTimeCanonicalMap({
+ year: 1,
+ month: 1,
+ day: 1,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: 0,
+ }),
+ "0001-01-01T00:00:00Z",
+ );
+ assertEquals(
+ dateTimeCanonicalMap({
+ year: 0,
+ month: 1,
+ day: 1,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: 0,
+ }),
+ "0000-01-01T00:00:00Z",
+ );
+ assertEquals(
+ dateTimeCanonicalMap({
+ year: -0,
+ month: 1,
+ day: 1,
+ hour: -0,
+ minute: -0,
+ second: -0,
+ timezoneOffset: -0,
+ }),
+ "0000-01-01T00:00:00Z",
+ );
+ assertEquals(
+ dateTimeCanonicalMap({
+ year: 1,
+ month: 1,
+ day: 1,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: undefined,
+ }),
+ "0001-01-01T00:00:00",
+ );
+ assertEquals(
+ dateTimeCanonicalMap({
+ year: 99999,
+ month: 12,
+ day: 31,
+ hour: 23,
+ minute: 59,
+ second: 59.99,
+ timezoneOffset: 840,
+ }),
+ "99999-12-31T23:59:59.99+14:00",
+ );
+ assertEquals(
+ dateTimeCanonicalMap({
+ year: 1972,
+ month: 2,
+ day: 29,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: undefined,
+ }),
+ "1972-02-29T00:00:00",
+ );
+ });
+
+ it("throws an error if the value is not a date/timeSevenPropertyModel value", () => {
+ assertThrows(() => {
+ dateTimeCanonicalMap(0);
+ });
+ assertThrows(() => {
+ dateTimeCanonicalMap("0001-01-01T00:00:00Z");
+ });
+ assertThrows(() => {
+ dateTimeCanonicalMap({
+ year: null,
+ month: null,
+ day: null,
+ hour: null,
+ minute: null,
+ second: null,
+ });
+ });
+ });
+
+ it("throws an error if the value is not a complete dateTime value", () => {
+ assertThrows(() => {
+ dateTimeCanonicalMap({});
+ });
+ assertThrows(() => {
+ dateTimeCanonicalMap({
+ year: 1972,
+ month: 12,
+ day: 31,
+ timezoneOffset: 0,
+ });
+ });
+ });
+});
+
+describe("timeCanonicalMap", () => {
+ it("maps the value", () => {
+ assertEquals(
+ timeCanonicalMap({
+ year: undefined,
+ month: undefined,
+ day: undefined,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: 0,
+ }),
+ "00:00:00Z",
+ );
+ assertEquals(
+ timeCanonicalMap({
+ year: undefined,
+ month: undefined,
+ day: undefined,
+ hour: -0,
+ minute: -0,
+ second: -0,
+ timezoneOffset: -0,
+ }),
+ "00:00:00Z",
+ );
+ assertEquals(
+ timeCanonicalMap({
+ year: undefined,
+ month: undefined,
+ day: undefined,
+ hour: 23,
+ minute: 59,
+ second: 59.99,
+ timezoneOffset: 840,
+ }),
+ "23:59:59.99+14:00",
+ );
+ });
+
+ it("throws an error if the value is not a date/timeSevenPropertyModel value", () => {
+ assertThrows(() => {
+ timeCanonicalMap(0);
+ });
+ assertThrows(() => {
+ timeCanonicalMap("00:00:00Z");
+ });
+ assertThrows(() => {
+ timeCanonicalMap({
+ year: undefined,
+ month: undefined,
+ day: undefined,
+ hour: null,
+ minute: null,
+ second: null,
+ });
+ });
+ });
+
+ it("throws an error if the value is not a complete time value", () => {
+ assertThrows(() => {
+ timeCanonicalMap({});
+ });
+ assertThrows(() => {
+ timeCanonicalMap({
+ year: 1972,
+ month: 12,
+ day: 31,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: 0,
+ });
+ });
+ assertThrows(() => {
+ timeCanonicalMap({
+ year: undefined,
+ month: undefined,
+ day: undefined,
+ hour: 0,
+ minute: 0,
+ second: undefined,
+ timezoneOffset: 0,
+ });
+ });
+ });
+});
+
+describe("dateCanonicalMap", () => {
+ it("maps the value", () => {
+ assertEquals(
+ dateCanonicalMap({
+ year: 1,
+ month: 1,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ }),
+ "0001-01-01Z",
+ );
+ assertEquals(
+ dateCanonicalMap({
+ year: 0,
+ month: 1,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ }),
+ "0000-01-01Z",
+ );
+ assertEquals(
+ dateCanonicalMap({
+ year: -0,
+ month: 1,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: -0,
+ }),
+ "0000-01-01Z",
+ );
+ assertEquals(
+ dateCanonicalMap({
+ year: 1,
+ month: 1,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ }),
+ "0001-01-01",
+ );
+ assertEquals(
+ dateCanonicalMap({
+ year: 99999,
+ month: 12,
+ day: 31,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 840,
+ }),
+ "99999-12-31+14:00",
+ );
+ assertEquals(
+ dateCanonicalMap({
+ year: 1972,
+ month: 2,
+ day: 29,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ }),
+ "1972-02-29",
+ );
+ });
+
+ it("throws an error if the value is not a date/timeSevenPropertyModel value", () => {
+ assertThrows(() => {
+ dateCanonicalMap(0);
+ });
+ assertThrows(() => {
+ dateCanonicalMap("0001-01-01Z");
+ });
+ assertThrows(() => {
+ dateCanonicalMap({
+ year: null,
+ month: null,
+ day: null,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ });
+ });
+ });
+
+ it("throws an error if the value is not a complete date value", () => {
+ assertThrows(() => {
+ dateCanonicalMap({});
+ });
+ assertThrows(() => {
+ dateCanonicalMap({
+ year: 1972,
+ month: 12,
+ day: 31,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: 0,
+ });
+ });
+ assertThrows(() => {
+ dateCanonicalMap({ year: 1972 });
+ });
+ });
+});
+
+describe("gYearMonthCanonicalMap", () => {
+ it("maps the value", () => {
+ assertEquals(
+ gYearMonthCanonicalMap({
+ year: 1,
+ month: 1,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ }),
+ "0001-01Z",
+ );
+ assertEquals(
+ gYearMonthCanonicalMap({
+ year: 0,
+ month: 1,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ }),
+ "0000-01Z",
+ );
+ assertEquals(
+ gYearMonthCanonicalMap({
+ year: -0,
+ month: 1,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: -0,
+ }),
+ "0000-01Z",
+ );
+ assertEquals(
+ gYearMonthCanonicalMap({
+ year: 1,
+ month: 1,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ }),
+ "0001-01",
+ );
+ assertEquals(
+ gYearMonthCanonicalMap({
+ year: 99999,
+ month: 12,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 840,
+ }),
+ "99999-12+14:00",
+ );
+ assertEquals(
+ gYearMonthCanonicalMap({
+ year: 1972,
+ month: 2,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ }),
+ "1972-02",
+ );
+ });
+
+ it("throws an error if the value is not a date/timeSevenPropertyModel value", () => {
+ assertThrows(() => {
+ gYearMonthCanonicalMap(0);
+ });
+ assertThrows(() => {
+ gYearMonthCanonicalMap("0001-01Z");
+ });
+ assertThrows(() => {
+ gYearMonthCanonicalMap({
+ year: null,
+ month: null,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ });
+ });
+ });
+
+ it("throws an error if the value is not a complete yearMonth value", () => {
+ assertThrows(() => {
+ gYearMonthCanonicalMap({});
+ });
+ assertThrows(() => {
+ gYearMonthCanonicalMap({
+ year: 1972,
+ month: 12,
+ day: 31,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: 0,
+ });
+ });
+ assertThrows(() => {
+ gYearMonthCanonicalMap({ year: 1972, month: 12, day: 31 });
+ });
+ assertThrows(() => {
+ gYearMonthCanonicalMap({ year: 1972 });
+ });
+ });
+});
+
+describe("gYearCanonicalMap", () => {
+ it("maps the value", () => {
+ assertEquals(
+ gYearCanonicalMap({
+ year: 1,
+ month: undefined,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ }),
+ "0001Z",
+ );
+ assertEquals(
+ gYearCanonicalMap({
+ year: 0,
+ month: undefined,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ }),
+ "0000Z",
+ );
+ assertEquals(
+ gYearCanonicalMap({
+ year: -0,
+ month: undefined,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: -0,
+ }),
+ "0000Z",
+ );
+ assertEquals(
+ gYearCanonicalMap({
+ year: 1,
+ month: undefined,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ }),
+ "0001",
+ );
+ assertEquals(
+ gYearCanonicalMap({
+ year: 99999,
+ month: undefined,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 840,
+ }),
+ "99999+14:00",
+ );
+ assertEquals(
+ gYearCanonicalMap({
+ year: 1972,
+ month: undefined,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ }),
+ "1972",
+ );
+ });
+
+ it("throws an error if the value is not a date/timeSevenPropertyModel value", () => {
+ assertThrows(() => {
+ gYearCanonicalMap(0);
+ });
+ assertThrows(() => {
+ gYearCanonicalMap("0001Z");
+ });
+ assertThrows(() => {
+ gYearCanonicalMap({
+ year: null,
+ month: undefined,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ });
+ });
+ });
+
+ it("throws an error if the value is not a complete gYear value", () => {
+ assertThrows(() => {
+ gYearCanonicalMap({});
+ });
+ assertThrows(() => {
+ gYearCanonicalMap({
+ year: 1972,
+ month: 12,
+ day: 31,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: 0,
+ });
+ });
+ assertThrows(() => {
+ gYearCanonicalMap({ year: 1972, month: 12, day: 31 });
+ });
+ });
+});
+
+describe("gMonthDayCanonicalMap", () => {
+ it("maps the value", () => {
+ assertEquals(
+ gMonthDayCanonicalMap({
+ year: undefined,
+ month: 1,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ }),
+ "--01-01Z",
+ );
+ assertEquals(
+ gMonthDayCanonicalMap({
+ year: undefined,
+ month: 1,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ }),
+ "--01-01",
+ );
+ assertEquals(
+ gMonthDayCanonicalMap({
+ year: undefined,
+ month: 12,
+ day: 31,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 840,
+ }),
+ "--12-31+14:00",
+ );
+ assertEquals(
+ gMonthDayCanonicalMap({
+ year: undefined,
+ month: 2,
+ day: 29,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ }),
+ "--02-29",
+ );
+ });
+
+ it("throws an error if the value is not a date/timeSevenPropertyModel value", () => {
+ assertThrows(() => {
+ gMonthDayCanonicalMap(0);
+ });
+ assertThrows(() => {
+ gMonthDayCanonicalMap("--01-01Z");
+ });
+ assertThrows(() => {
+ gMonthDayCanonicalMap({
+ year: undefined,
+ month: null,
+ day: null,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ });
+ });
+ });
+
+ it("throws an error if the value is not a complete gMonthDay value", () => {
+ assertThrows(() => {
+ gMonthDayCanonicalMap({});
+ });
+ assertThrows(() => {
+ gMonthDayCanonicalMap({
+ year: 1972,
+ month: 12,
+ day: 31,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: 0,
+ });
+ });
+ assertThrows(() => {
+ gMonthDayCanonicalMap({ year: 1972, month: 12, day: 31 });
+ });
+ assertThrows(() => {
+ gMonthDayCanonicalMap({ day: 31 });
+ });
+ });
+});
+
+describe("gDayCanonicalMap", () => {
+ it("maps the value", () => {
+ assertEquals(
+ gDayCanonicalMap({
+ year: undefined,
+ month: undefined,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ }),
+ "---01Z",
+ );
+ assertEquals(
+ gDayCanonicalMap({
+ year: undefined,
+ month: undefined,
+ day: 1,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ }),
+ "---01",
+ );
+ assertEquals(
+ gDayCanonicalMap({
+ year: undefined,
+ month: undefined,
+ day: 31,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 840,
+ }),
+ "---31+14:00",
+ );
+ assertEquals(
+ gDayCanonicalMap({
+ year: undefined,
+ month: undefined,
+ day: 29,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ }),
+ "---29",
+ );
+ });
+
+ it("throws an error if the value is not a date/timeSevenPropertyModel value", () => {
+ assertThrows(() => {
+ gDayCanonicalMap(0);
+ });
+ assertThrows(() => {
+ gDayCanonicalMap("---01Z");
+ });
+ assertThrows(() => {
+ gDayCanonicalMap({
+ year: undefined,
+ month: undefined,
+ day: null,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ });
+ });
+ });
+
+ it("throws an error if the value is not a complete gDay value", () => {
+ assertThrows(() => {
+ gDayCanonicalMap({});
+ });
+ assertThrows(() => {
+ gDayCanonicalMap({
+ year: 1972,
+ month: 12,
+ day: 31,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: 0,
+ });
+ });
+ assertThrows(() => {
+ gDayCanonicalMap({ year: 1972, month: 12, day: 31 });
+ });
+ });
+});
+
+describe("gMonthCanonicalMap", () => {
+ it("maps the value", () => {
+ assertEquals(
+ gMonthCanonicalMap({
+ year: undefined,
+ month: 1,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 0,
+ }),
+ "--01Z",
+ );
+ assertEquals(
+ gMonthCanonicalMap({
+ year: undefined,
+ month: 1,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ }),
+ "--01",
+ );
+ assertEquals(
+ gMonthCanonicalMap({
+ year: undefined,
+ month: 12,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: 840,
+ }),
+ "--12+14:00",
+ );
+ assertEquals(
+ gMonthCanonicalMap({
+ year: undefined,
+ month: 2,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ timezoneOffset: undefined,
+ }),
+ "--02",
+ );
+ });
+
+ it("throws an error if the value is not a date/timeSevenPropertyModel value", () => {
+ assertThrows(() => {
+ gMonthCanonicalMap(0);
+ });
+ assertThrows(() => {
+ gMonthCanonicalMap("--01Z");
+ });
+ assertThrows(() => {
+ gMonthCanonicalMap({
+ year: undefined,
+ month: null,
+ day: undefined,
+ hour: undefined,
+ minute: undefined,
+ second: undefined,
+ });
+ });
+ });
+
+ it("throws an error if the value is not a complete gMonth value", () => {
+ assertThrows(() => {
+ gMonthCanonicalMap({});
+ });
+ assertThrows(() => {
+ gMonthCanonicalMap({
+ year: 1972,
+ month: 12,
+ day: 31,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ timezoneOffset: 0,
+ });
+ });
+ assertThrows(() => {
+ gMonthCanonicalMap({ year: 1972, month: 12, day: 31 });
+ });
+ });
+});
+
+describe("stringLexicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(stringLexicalMap(""), "");
+ assertStrictEquals(stringLexicalMap("etaoin"), "etaoin");
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ stringLexicalMap({});
+ });
+ assertThrows(() => {
+ stringLexicalMap(0);
+ });
+ assertThrows(() => {
+ stringLexicalMap(new String());
+ });
+ });
+});
+
+describe("booleanLexicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(booleanLexicalMap("0"), false);
+ assertStrictEquals(booleanLexicalMap("false"), false);
+ assertStrictEquals(booleanLexicalMap("1"), true);
+ assertStrictEquals(booleanLexicalMap("true"), true);
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ booleanLexicalMap({});
+ });
+ assertThrows(() => {
+ booleanLexicalMap(0);
+ });
+ assertThrows(() => {
+ booleanLexicalMap(false);
+ });
+ });
+});
+
+describe("stringCanonicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(stringCanonicalMap(""), "");
+ assertStrictEquals(stringCanonicalMap("etaoin"), "etaoin");
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ stringCanonicalMap({});
+ });
+ assertThrows(() => {
+ stringCanonicalMap(0);
+ });
+ assertThrows(() => {
+ stringCanonicalMap(new String());
+ });
+ });
+});
+
+describe("booleanCanonicalMap", () => {
+ it("maps the value", () => {
+ assertStrictEquals(booleanCanonicalMap(false), "false");
+ assertStrictEquals(booleanCanonicalMap(true), "true");
+ });
+
+ it("throws an error if the value is not a boolean", () => {
+ assertThrows(() => {
+ booleanCanonicalMap({});
+ });
+ assertThrows(() => {
+ booleanCanonicalMap(0);
+ });
+ assertThrows(() => {
+ booleanCanonicalMap("false");
+ });
+ assertThrows(() => {
+ booleanCanonicalMap(new Boolean());
+ });
+ });
+});
+
+describe("hexBinaryMap", () => {
+ it("maps the value", () => {
+ assertEquals(
+ new Uint8Array(hexBinaryMap("")),
+ new Uint8Array([]),
+ );
+ assertEquals(
+ new Uint8Array(hexBinaryMap("4b494249")),
+ new Uint8Array([75, 73, 66, 73]),
+ );
+ assertEquals(
+ new Uint8Array(hexBinaryMap("626173653634")),
+ new Uint8Array([98, 97, 115, 101, 54, 52]),
+ );
+ assertEquals(
+ new Uint8Array(hexBinaryMap("14FB9C03D97E")),
+ new Uint8Array([0x14, 0xFB, 0x9C, 0x03, 0xD9, 0x7E]),
+ );
+ });
+
+ it("throws an error if the value is not a string", () => {
+ assertThrows(() => {
+ hexBinaryMap({});
+ });
+ assertThrows(() => {
+ hexBinaryMap(0);
+ });
+ assertThrows(() => {
+ hexBinaryMap(new ArrayBuffer());
+ });
+ });
+
+ it("throws an error if the value is not a valid hexBinary", () => {
+ assertThrows(() => {
+ hexBinaryMap("A");
+ });
+ assertThrows(() => {
+ hexBinaryMap("EG");
+ });
+ });
+});
+
+describe("hexBinaryCanonical", () => {
+ it("maps the value", () => {
+ assertStrictEquals(
+ hexBinaryCanonical(new ArrayBuffer()),
+ "",
+ );
+ assertEquals(
+ hexBinaryCanonical(new Uint8Array([75, 73, 66, 73]).buffer),
+ "4B494249",
+ );
+ assertEquals(
+ hexBinaryCanonical(
+ new Uint8Array([98, 97, 115, 101, 54, 52]).buffer,
+ ),
+ "626173653634",
+ );
+ assertEquals(
+ hexBinaryCanonical(
+ new Uint8Array([0x14, 0xFB, 0x9C, 0x03, 0xD9, 0x7E]).buffer,
+ ),
+ "14FB9C03D97E",
+ );
+ });
+
+ it("throws an error if the value is not an array buffer", () => {
+ assertThrows(() => {
+ hexBinaryCanonical({});
+ });
+ assertThrows(() => {
+ hexBinaryCanonical(0);
+ });
+ assertThrows(() => {
+ hexBinaryCanonical("");
+ });
+ assertThrows(() => {
+ hexBinaryCanonical(new Uint8Array([]));
+ });
+ });
+});