UNPKG

@visulima/humanizer

Version:

Humanizer is a library for humanizing data in a human-readable form.

217 lines (214 loc) 7.13 kB
import { durationLanguage } from '../language/en.mjs'; import validateDurationLanguage from '../language/util/validate-duration-language.mjs'; var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); const toFixed = /* @__PURE__ */ __name((number_, fixed) => { fixed = fixed || -1; const matches = new RegExp(`^-?\\d+(?:.\\d{0,${fixed}})?`).exec(number_.toString()); if (matches === null) { return number_; } return Number.parseFloat(matches[0]); }, "toFixed"); const renderPiece = /* @__PURE__ */ __name(({ unitCount, unitName }, language, options) => { let { spacer } = options; const { maxDecimalPoints } = options; let decimal = "."; if (options.decimal !== void 0) { decimal = options.decimal; } else if (language.decimal !== void 0) { decimal = language.decimal; } let digitReplacements; if ("digitReplacements" in options) { digitReplacements = options.digitReplacements; } else if ("_digitReplacements" in language) { digitReplacements = language._digitReplacements; } let formattedCount; let normalizedUnitCount = unitCount; if (maxDecimalPoints !== void 0) { normalizedUnitCount = toFixed(unitCount, maxDecimalPoints); } const countString = normalizedUnitCount.toString(); if (!language._hideCountIf2 || unitCount !== 2) { if (digitReplacements) { formattedCount = ""; for (const char of countString) { formattedCount += char === "." ? decimal : digitReplacements[char]; } } else { formattedCount = countString.replace(".", decimal); } } else { formattedCount = ""; } const languageWord = language[unitName]; let word = languageWord; if (typeof languageWord === "function") { word = languageWord(unitCount); } if (language._hideCountIf2 && unitCount === 2) { spacer = ""; } if (language._numberFirst) { return word + spacer + formattedCount; } return formattedCount + spacer + word; }, "renderPiece"); const getPieces = /* @__PURE__ */ __name((ms, options) => { const { units } = options; if (units.length === 0) { return []; } const { unitMeasures } = options; const largest = options.largest ?? Number.POSITIVE_INFINITY; const unitCounts = {}; let unitName; let index; let unitCount; let msRemaining = ms; for (index = 0; index < units.length; index++) { unitName = units[index]; const unitMs = unitMeasures[unitName]; const isLast = index === units.length - 1; unitCount = isLast ? msRemaining / unitMs : Math.floor(msRemaining / unitMs); unitCounts[unitName] = unitCount; msRemaining -= unitCount * unitMs; } if (options.round) { let unitsRemainingBeforeRound = largest; for (index = 0; index < units.length; index++) { unitName = units[index]; unitCount = unitCounts[unitName]; if (unitCount === 0) { continue; } unitsRemainingBeforeRound--; if (unitsRemainingBeforeRound === 0) { for (let index_ = index + 1; index_ < units.length; index_++) { const smallerUnitName = units[index_]; const smallerUnitCount = unitCounts[smallerUnitName]; unitCounts[unitName] += smallerUnitCount * unitMeasures[smallerUnitName] / unitMeasures[unitName]; unitCounts[smallerUnitName] = 0; } break; } } for (index = units.length - 1; index >= 0; index--) { unitName = units[index]; unitCount = unitCounts[unitName]; if (unitCount === 0) { continue; } const rounded = Math.round(unitCount); unitCounts[unitName] = rounded; if (index === 0) { break; } const previousUnitName = units[index - 1]; const previousUnitMs = unitMeasures[previousUnitName]; const amountOfPreviousUnit = Math.floor(rounded * unitMeasures[unitName] / previousUnitMs); if (amountOfPreviousUnit) { unitCounts[previousUnitName] += amountOfPreviousUnit; unitCounts[unitName] = 0; } else { break; } } } const result = []; for (index = 0; index < units.length && result.length < largest; index++) { unitName = units[index]; unitCount = unitCounts[unitName]; if (unitCount && !options.round && result.length === largest - 1) { let index_; let remainder = 0; for (index_ = index + 1, units.length; index_ < units.length; index_++) { const remainderUnitName = units[index_]; remainder += unitCounts[remainderUnitName] * (options.unitMeasures[remainderUnitName] / options.unitMeasures[unitName]); } unitCount += remainder; if (options.maxDecimalPoints !== void 0) { unitCount = toFixed(unitCount, options.maxDecimalPoints); } } if (unitCount) { result.push({ unitCount, unitName }); } } return result; }, "getPieces"); const formatPieces = /* @__PURE__ */ __name((pieces, options, ms) => { const { language, units } = options; if (pieces.length === 0) { const smallestUnitName = units.at(-1); return renderPiece({ unitCount: 0, unitName: smallestUnitName }, language, options); } const { conjunction, serialComma } = options; let delimiter = ", "; if (options.delimiter !== void 0) { delimiter = options.delimiter; } else if (language.delimiter !== void 0) { delimiter = language.delimiter; } let adverb = ""; if (options.timeAdverb && ms !== 0) { adverb = language.future ?? ""; if (ms < 0) { adverb = language.past ?? ""; } } const renderedPieces = []; for (const piece_ of pieces) { const piece = piece_; renderedPieces.push(renderPiece(piece, language, options)); } let result; if (!conjunction || pieces.length === 1) { result = renderedPieces.join(delimiter); } else if (pieces.length === 2) { result = renderedPieces.join(conjunction); } else { result = renderedPieces.slice(0, -1).join(delimiter) + (serialComma ? "," : "") + conjunction + renderedPieces.at(-1); } if (adverb) { result = adverb.replace("%s", result); } return result; }, "formatPieces"); const duration = /* @__PURE__ */ __name((milliseconds, options) => { if (Number.isNaN(milliseconds)) { throw new TypeError("Expected a valid number"); } if (typeof milliseconds !== "number") { throw new TypeError("Expected a number"); } const config = { conjunction: "", language: durationLanguage, round: false, serialComma: true, spacer: " ", timeAdverb: false, unitMeasures: { d: 864e5, h: 36e5, m: 6e4, mo: 2629746e3, // 365.2425 / 12 = 30.436875 days ms: 1, s: 1e3, w: 6048e5, y: 31556952e3 // 365 + 1/4 - 1/100 + 1/400 (actual leap day rules) = 365.2425 days }, units: ["w", "d", "h", "m", "s"], ...options }; validateDurationLanguage(config.language); const absTime = Math.abs(milliseconds); const pieces = getPieces(absTime, config); return formatPieces(pieces, config, milliseconds); }, "duration"); export { duration as default };