@toreda/time
Version:
Simple, small footprint library for common time operations and converting between units of time.
104 lines (103 loc) • 3.39 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.timeValueParse = timeValueParse;
const utils_1 = require("../utils");
const convert_1 = require("../convert");
const SUFFIX_RE = /(\d+(?:\.\d+)?)(μs|ms|mo|y|w|d|h|m|s)/g;
const PURE_NUMERIC_RE = /^[+-]?\d+(\.\d+)?$/;
const DATE_LIKE_RE = /[-/:T]/;
const UNIT_ORDER = ['y', 'mo', 'w', 'd', 'h', 'm', 's', 'ms', 'μs'];
const UNIT_RANK = UNIT_ORDER.reduce((acc, u, i) => {
acc[u] = i;
return acc;
}, {});
/**
* Parse a duration value into a number expressed in `units`. Accepts:
* - finite numbers (returned as-is when in safe range)
* - pure numeric strings ('5', '-1.5', '+30') — interpreted as a duration in `units`
* - simple suffix form ('5m', '-1.5h', '+30s') — converted to `units`
* - compound suffix form ('1h30m', '2d4h30m') — components must be in
* descending unit order with no repeats and no whitespace; sign applies
* to the whole expression, not per component
*
* Returns null for empty / whitespace-only strings, NaN/Infinity, date-like
* strings (those containing '-', '/', ':', or 'T' — use `timeValueParseDate`
* for those), unit aliases like '5min', and any result outside the safe
* integer range.
*
* @param value Duration value to parse.
* @param units Target unit; pure numeric strings are read as this unit
* and suffix components are converted into this unit.
*
* @category Time Parsing
*/
function timeValueParse(value, units) {
if (typeof value === 'number') {
if (!isFinite(value) || !utils_1.TimeUtils.withinSafeRange(value)) {
return null;
}
return value;
}
if (typeof value !== 'string') {
return null;
}
const trimmed = value.trim();
if (trimmed.length === 0) {
return null;
}
if (PURE_NUMERIC_RE.test(trimmed)) {
const n = Number(trimmed);
if (!isFinite(n) || !utils_1.TimeUtils.withinSafeRange(n)) {
return null;
}
return n;
}
let body = trimmed;
let sign = 1;
if (body[0] === '+' || body[0] === '-') {
sign = body[0] === '-' ? -1 : 1;
body = body.slice(1);
}
if (body.length === 0) {
return null;
}
if (DATE_LIKE_RE.test(body)) {
return null;
}
SUFFIX_RE.lastIndex = 0;
const seen = new Set();
let consumed = 0;
let lastRank = -1;
let total = 0;
let match;
while ((match = SUFFIX_RE.exec(body)) !== null) {
if (match.index !== consumed) {
return null;
}
const componentValue = Number(match[1]);
const componentUnit = match[2];
if (seen.has(componentUnit)) {
return null;
}
const rank = UNIT_RANK[componentUnit];
if (rank <= lastRank) {
return null;
}
const converted = (0, convert_1.timeConvert)(componentUnit, units, componentValue);
if (converted === null) {
return null;
}
total += converted;
seen.add(componentUnit);
lastRank = rank;
consumed = SUFFIX_RE.lastIndex;
}
if (consumed === 0 || consumed !== body.length) {
return null;
}
const result = sign * total;
if (!isFinite(result) || !utils_1.TimeUtils.withinSafeRange(result)) {
return null;
}
return result;
}