UNPKG

vremel

Version:

JavaScript date utility library for Temporal API

278 lines 12.1 kB
import { createRecord } from "./_createRecord.js"; import { escapeRegExp } from "./_escapeRegExp.js"; import { tokenize } from "./_ldmrDatePattern.js"; import { unionRegExp } from "./_unionRegExp.js"; const fieldToRegExpMap = createRecord({ y: "(?<year>[1-9]\\d*)", yyy: "(?<year>\\d{3,})", yyyy: "(?<year>\\d{4,})", M: "(?<monthNum>[1-9]\\d?)", MM: "(?<monthNum>\\d{2})", L: "(?<monthNum>[1-9]\\d?)", LL: "(?<monthNum>\\d{2})", d: "(?<day>[1-9]\\d?)", dd: "(?<day>\\d{2})", h: "(?<hour12>[1-9]|1[012])", hh: "(?<hour12>0[1-9]|1[012])", H: "(?<hour>1\\d?|2[0-3]?|\\d)", HH: "(?<hour>[01]\\d|2[0-3])", K: "(?<hour12>[0-9]|1[01])", KK: "(?<hour12>0\\d|1[01])", m: "(?<minute>0|[1-9]\\d?)", mm: "(?<minute>\\d{2})", s: "(?<second>0|[1-9]\\d?)", ss: "(?<second>\\d{2})", }); function getEraNamesAndLookupTable(style, localeData) { const eraNamesData = localeData.era?.[style]; if (!eraNamesData) { throw new Error(`locale data for ${style} style of the eras is not provided`); } const table = createRecord(); const monthNames = []; for (const [monthCode, name] of Object.entries(eraNamesData)) { monthNames.push(name); table[name] = monthCode; } return [monthNames, table]; } function getMonthNamesAndLookupTable(form, style, localeData) { const monthNamesData = localeData.month?.[form]?.[style]; if (!monthNamesData) { throw new Error(`locale data for ${form} form + ${style} style of the months is not provided`); } const table = createRecord(); const monthNames = []; if (Array.isArray(monthNamesData)) { monthNamesData.forEach((name, index) => { monthNames.push(name); table[name] = index + 1; }); } else { for (const [monthCode, name] of Object.entries(monthNamesData)) { monthNames.push(name); table[name] = monthCode; } } return [monthNames, table]; } function getDayPeriodNamesAndLookupTable(style, localeData) { const dayPeriodData = localeData.dayPeriod?.[style]; if (!dayPeriodData) { throw new Error(`locale data for ${style} style of the day periods is not provided`); } const table = createRecord(); const dayPeriodNames = []; for (const [dayPeriodId, name] of Object.entries(dayPeriodData)) { dayPeriodNames.push(name); table[name] = dayPeriodId; } return [dayPeriodNames, table]; } function fieldToRegExp(field, localeData, lookupTable) { if (fieldToRegExpMap[field] !== undefined) { return fieldToRegExpMap[field]; } if (field.startsWith("y")) { return `(?<year>\\d{${field.length},})`; } if (field.startsWith("S")) { return `(?<fracSec>\\d{${field.length}})`; } if (field.length <= 5) { const type = field.length <= 3 ? "abbreviated" : field.length === 4 ? "wide" : "narrow"; if (field.startsWith("G")) { const [eraNames, table] = getEraNamesAndLookupTable(type, localeData); lookupTable.era = table; return `(?<era>${unionRegExp(eraNames)})`; } if (field.startsWith("M")) { const [monthNames, table] = getMonthNamesAndLookupTable("format", type, localeData); lookupTable.month = table; return `(?<monthName>${unionRegExp(monthNames)})`; } if (field.startsWith("L")) { const [monthNames, table] = getMonthNamesAndLookupTable("standalone", type, localeData); lookupTable.month = table; return `(?<monthName>${unionRegExp(monthNames)})`; } if (field.startsWith("a")) { const [eraNames, table] = getDayPeriodNamesAndLookupTable(type, localeData); lookupTable.dayPeriod = table; return `(?<dayPeriod>${unionRegExp(eraNames)})`; } } throw new Error(`Unknown field: ${field}`); } function compilePattern(pattern, localeData) { const lookupTable = { era: {}, month: {}, dayPeriod: {}, }; let regexpString = ""; for (const token of tokenize(pattern)) { if (token.type === "literal") { regexpString += escapeRegExp(token.value); } else { regexpString += fieldToRegExp(token.value, localeData, lookupTable); } } return [new RegExp(`^${regexpString}$`), lookupTable]; } /** * Return the object parsed from string using the given format string. * * ```typescript * Temporal.PlainDate.from(parse('2/9/2025', 'M/d/yyyy')) * // 2025-02-09 * ``` * * Characters between two single quotes (`'`) in the format are escaped. * Two consecutive single quotes (`''`) in the format always represents one single quote (`'`). * Letters `A` to `Z` and `a` to `z` are reserved for use as pattern characters, unless they are escaped. * * Available field patterns are subset of date field symbols in Unicode CLDR, * see https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table for details. * * Basically 3 characters' field (e.g. `MMM`) means 'abbreviated' style, * 4 characters' field (e.g. `MMMM`) means 'wide' style, * 5 characters's field (e.g. `MMMMM`) means 'narrow' style. * * Available patterns: * | unit | field | result examples | * | ------------------ | ---------------- | --------------------------------------- | * | era | G, GG, GGG | AD, BC | * | | GGGG | Anno Domini, Before Christ | * | | GGGGG | A, B | * | calendar year | y | 2, 20, 201, 2000, 20020 | * | | yyy | 002, 020, 201, 2000, 20020 | * | | yyyy | 0002, 0020, 0201, 2000, 20020 | * | | yyyyy, yyyyyy... | (at least `n` digits with zero-padding) | * | month (format) | M | 1, 12 | * | | MM | 01, 12 | * | | MMM | Jan, Feb, ... | * | | MMMM | January, February, ... | * | | MMMMM | J, F, ... | * | month (standalone) | L | 1, 12 | * | | LL | 01, 12 | * | | LLL | Jan, Feb, ... | * | | LLLL | January, February, ... | * | | LLLLL | J, F, ... | * | day | d | 1, 31 | * | | dd | 01, 31 | * | AM, PM | a, aa, aaa | AM, PM | * | | aaaa | a.m., p.m. | * | | aaaaa | a, p | * | hour (1-12) | h | 12, 1, 11 | * | | hh | 12, 01, 11 | * | hour (0-23) | H | 0, 1, 23 | * | | HH | 00, 01, 23 | * | hour (0-11) | K | 0, 1, 11 | * | | KK | 00, 01, 11 | * | minute | m | 0, 59 | * | | mm | 00, 59 | * | second | s | 0, 59 | * | | ss | 00, 59 | * | fraction of second | S | 1, 8 | * | | SS | 10, 83 | * | | SSS, SSSS... | (`n` digits) | * * @param dateTimeString string representing a date * @param pattern date format pattern string * @param localeData optional locale data (required when using non-numeric pattern fields) * @returns parse result which can be passed to `Temporal.XX.from` static method. */ export function parse(dateTimeString, pattern, localeData = {}) { const [regexp, table] = compilePattern(pattern, localeData); const matchResult = dateTimeString.match(regexp); if (matchResult === null) { throw new Error(`Doesn't match the pattern \`${pattern}\` for the string \`${dateTimeString}\``); } const matchGroups = matchResult.groups; if (matchGroups === undefined) { return {}; } const result = {}; if (matchGroups["era"] !== undefined) { const eraId = table.era[matchGroups["era"]]; if (eraId === undefined) { throw new Error("Unknown error"); } result.era = eraId; } if (matchGroups["year"] !== undefined) { if (matchGroups["era"] !== undefined) { result.eraYear = parseInt(matchGroups["year"]); } else { result.year = parseInt(matchGroups["year"]); } } if (matchGroups["monthNum"] !== undefined) { result.month = parseInt(matchGroups["monthNum"]); } if (matchGroups["monthName"] !== undefined) { const month = table.month[matchGroups["monthName"]]; if (month === undefined) { throw new Error("Unknown error"); } if (typeof month === "number") { result.month = month; } else { result.monthCode = month; } } if (matchGroups["day"] !== undefined) { result.day = parseInt(matchGroups["day"]); } if (matchGroups["hour"] !== undefined) { result.hour = parseInt(matchGroups["hour"]); } if (matchGroups["hour12"] !== undefined) { if (matchGroups["dayPeriod"] === undefined) { throw new Error("no day period specified"); } const hour = parseInt(matchGroups["hour12"]); const dayPeriodId = table.dayPeriod[matchGroups["dayPeriod"]]; if (dayPeriodId === undefined) { throw new Error("Unknown error"); } if (dayPeriodId === "am") { result.hour = hour % 12; } else if (dayPeriodId === "pm") { result.hour = (hour % 12) + 12; } else { throw new Error(`day period '${dayPeriodId}' is not supported`); } } if (matchGroups["minute"] !== undefined) { const minute = parseInt(matchGroups["minute"]); if (minute < 0 || minute >= 60) { throw new Error(`minute value is out of range: ${minute}`); } result.minute = minute; } if (matchGroups["second"] !== undefined) { const second = parseInt(matchGroups["second"]); if (second < 0 || second >= 60) { throw new Error(`second value is out of range: ${second}`); } result.second = second; } if (matchGroups["fracSec"] !== undefined) { const fractionalSecondsWithTrailingZero = matchGroups["fracSec"].padEnd(9, "0"); result.millisecond = parseInt(fractionalSecondsWithTrailingZero.slice(0, 3)); result.microsecond = parseInt(fractionalSecondsWithTrailingZero.slice(3, 6)); result.nanosecond = parseInt(fractionalSecondsWithTrailingZero.slice(6, 9)); } return result; } //# sourceMappingURL=parse.js.map