@visulima/humanizer
Version:
Humanizer is a library for humanizing data in a human-readable form.
217 lines (214 loc) • 7.13 kB
JavaScript
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 };