UNPKG

hyperformula

Version:

HyperFormula is a JavaScript engine for efficient processing of spreadsheet-like data and formulas

214 lines (213 loc) 7.6 kB
"use strict"; exports.__esModule = true; exports.TIME_FORMAT_SECONDS_ITEM_REGEXP = void 0; exports.defaultParseToDateTime = defaultParseToDateTime; /** * @license * Copyright (c) 2025 Handsoncode. All rights reserved. */ const TIME_FORMAT_SECONDS_ITEM_REGEXP = exports.TIME_FORMAT_SECONDS_ITEM_REGEXP = new RegExp('^ss(\\.(s+|0+))?$'); const QUICK_CHECK_REGEXP = new RegExp('^[0-9/.\\-: ]+[ap]?m?$'); const WHITESPACE_REGEXP = new RegExp('\\s+'); const DATE_SEPARATOR_REGEXP = new RegExp('[ /.-]'); const TIME_SEPARATOR = ':'; const SECONDS_PRECISION = 1000; const memoizedParseTimeFormat = memoize(parseTimeFormat); const memoizedParseDateFormat = memoize(parseDateFormat); /** * Parses a DateTime value from a string if the string matches the given date format and time format. * * Idea for more readable implementation: * - divide string into parts by a regexp [date_regexp]? [time_regexp]? [ampm_regexp]? * - start by finding the time part, because it is unambiguous '([0-9]+:[0-9:.]+ ?[ap]?m?)$', before it is the date part * - OR split by spaces - last segment is ampm token, second to last is time (with or without ampm), rest is date * If applied: * - date parsing might work differently after these changes but still according to the docs * - make sure to test edge cases like timeFormats: ['hh', 'ss.ss'] etc, string: '01-01-2019 AM', 'PM' */ function defaultParseToDateTime(text, dateFormat, timeFormat) { if (dateFormat === undefined && timeFormat === undefined) { return undefined; } let dateTimeString = text.replace(WHITESPACE_REGEXP, ' ').trim().toLowerCase(); if (!doesItLookLikeADateTimeQuickCheck(dateTimeString)) { return undefined; } let ampmToken = dateTimeString.substring(dateTimeString.length - 2); if (ampmToken === 'am' || ampmToken === 'pm') { dateTimeString = dateTimeString.substring(0, dateTimeString.length - 2).trim(); } else { ampmToken = dateTimeString.substring(dateTimeString.length - 1); if (ampmToken === 'a' || ampmToken === 'p') { dateTimeString = dateTimeString.substring(0, dateTimeString.length - 1).trim(); } else { ampmToken = undefined; } } const dateItems = dateTimeString.split(DATE_SEPARATOR_REGEXP); if (dateItems.length >= 2 && dateItems[dateItems.length - 2].includes(TIME_SEPARATOR)) { dateItems[dateItems.length - 2] = dateItems[dateItems.length - 2] + '.' + dateItems[dateItems.length - 1]; dateItems.pop(); } const timeItems = dateItems[dateItems.length - 1].split(TIME_SEPARATOR); if (ampmToken !== undefined) { timeItems.push(ampmToken); } if (dateItems.length === 1) { return defaultParseToTime(timeItems, timeFormat); } if (timeItems.length === 1) { return defaultParseToDate(dateItems, dateFormat); } const parsedDate = defaultParseToDate(dateItems.slice(0, dateItems.length - 1), dateFormat); const parsedTime = defaultParseToTime(timeItems, timeFormat); if (parsedDate === undefined) { return undefined; } else if (parsedTime === undefined) { return undefined; } else { return Object.assign(Object.assign({}, parsedDate), parsedTime); } } /** * Parses a time value from a string if the string matches the given time format. */ function defaultParseToTime(timeItems, timeFormat) { var _a, _b, _c; if (timeFormat === undefined) { return undefined; } const { itemsCount, hourItem, minuteItem, secondItem } = memoizedParseTimeFormat(timeFormat); let ampm = undefined; if (timeItems[timeItems.length - 1] === 'am' || timeItems[timeItems.length - 1] === 'a') { ampm = false; timeItems.pop(); } else if (timeItems[timeItems.length - 1] === 'pm' || timeItems[timeItems.length - 1] === 'p') { ampm = true; timeItems.pop(); } if (timeItems.length !== itemsCount) { return undefined; } const secondsParsed = Number((_a = timeItems[secondItem]) !== null && _a !== void 0 ? _a : '0'); if (!Number.isFinite(secondsParsed)) { return undefined; } const seconds = Math.round(secondsParsed * SECONDS_PRECISION) / SECONDS_PRECISION; const minutes = Number((_b = timeItems[minuteItem]) !== null && _b !== void 0 ? _b : '0'); if (!(Number.isFinite(minutes) && Number.isInteger(minutes))) { return undefined; } const hoursParsed = Number((_c = timeItems[hourItem]) !== null && _c !== void 0 ? _c : '0'); if (!(Number.isFinite(hoursParsed) && Number.isInteger(hoursParsed))) { return undefined; } if (ampm !== undefined && (hoursParsed < 0 || hoursParsed > 12)) { return undefined; } const hours = ampm !== undefined ? hoursParsed % 12 + (ampm ? 12 : 0) : hoursParsed; return { hours, minutes, seconds }; } /** * Parses a date value from a string if the string matches the given date format. */ function defaultParseToDate(dateItems, dateFormat) { var _a; if (dateFormat === undefined) { return undefined; } const { itemsCount, dayItem, monthItem, shortYearItem, longYearItem } = memoizedParseDateFormat(dateFormat); if (dateItems.length !== itemsCount) { return undefined; } const day = Number(dateItems[dayItem]); if (!(Number.isFinite(day) && Number.isInteger(day))) { return undefined; } const month = Number(dateItems[monthItem]); if (!(Number.isFinite(month) && Number.isInteger(month))) { return undefined; } if (dateItems[longYearItem] && dateItems[shortYearItem]) { return undefined; } const year = Number((_a = dateItems[longYearItem]) !== null && _a !== void 0 ? _a : dateItems[shortYearItem]); if (!(Number.isFinite(year) && Number.isInteger(year))) { return undefined; } if (dateItems[longYearItem] && (year < 1000 || year > 9999)) { return undefined; } if (dateItems[shortYearItem] && (year < 0 || year > 99)) { return undefined; } return { year, month, day }; } /** * Parses a time format string into a format object. */ function parseTimeFormat(timeFormat) { const formatLowercase = timeFormat.toLowerCase().trim(); const formatWithoutAmPmItem = formatLowercase.endsWith('am/pm') ? formatLowercase.substring(0, formatLowercase.length - 5) : formatLowercase.endsWith('a/p') ? formatLowercase.substring(0, timeFormat.length - 3) : formatLowercase; const items = formatWithoutAmPmItem.trim().split(TIME_SEPARATOR); return { itemsCount: items.length, hourItem: items.indexOf('hh'), minuteItem: items.indexOf('mm'), secondItem: items.findIndex(item => TIME_FORMAT_SECONDS_ITEM_REGEXP.test(item)) }; } /** * Parses a date format string into a format object. */ function parseDateFormat(dateFormat) { const items = dateFormat.toLowerCase().trim().split(DATE_SEPARATOR_REGEXP); return { itemsCount: items.length, dayItem: items.indexOf('dd'), monthItem: items.indexOf('mm'), shortYearItem: items.indexOf('yy'), longYearItem: items.indexOf('yyyy') }; } /** * If this function returns false, the string is not parsable as a date time. Otherwise, it might be. * This is a quick check that is used to avoid running the more expensive parsing operations. */ function doesItLookLikeADateTimeQuickCheck(text) { return QUICK_CHECK_REGEXP.test(text); } /** * Function memoization for improved performance. */ function memoize(fn) { const memoizedResults = {}; return arg => { const memoizedResult = memoizedResults[arg]; if (memoizedResult !== undefined) { return memoizedResult; } const result = fn(arg); memoizedResults[arg] = result; return result; }; }