// 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([]));
+    });
+  });
+});