Files
EvanHahn-HumanizeDuration.js/PR.md
T
dualfroz 93dd2b2f06 fix: pluralize unit word using the maxDecimalPoints-truncated count
When maxDecimalPoints truncates a unit count to exactly 1, the rendered
number showed 1 while the word was pluralized from the raw count (for
example humanizeDuration(1500, { maxDecimalPoints: 0 }) returned
"1 seconds"). Pluralize from the normalized count that is actually
displayed so the number and word agree.
2026-09-04 09:04:26 +02:00

2.4 KiB

Fix pluralization when maxDecimalPoints truncates the count to 1

Problem

When the maxDecimalPoints option truncates a unit count down to exactly 1, the rendered number and the unit word disagree. The number is shown as 1 but the word is pluralized:

const humanizeDuration = require("humanize-duration");

humanizeDuration(1500, { maxDecimalPoints: 0 }); // "1 seconds"  (expected "1 second")
humanizeDuration(1005, { maxDecimalPoints: 2 }); // "1 seconds"  (expected "1 second")

The bug is language-independent; any language whose unit word is a function of the count is affected (for example German returns "1 Sekunden" instead of "1 Sekunde").

Root cause

humanize-duration.js, in renderPiece.

The displayed count is normalizedUnitCount, computed from maxDecimalPoints:

var normalizedUnitCount =
  maxDecimalPoints === void 0
    ? unitCount
    : Math.floor(unitCount * Math.pow(10, maxDecimalPoints)) /
      Math.pow(10, maxDecimalPoints);
var countStr = normalizedUnitCount.toString();

but the unit word was pluralized from the raw, un-truncated unitCount:

if (typeof languageWord === "function") {
  word = languageWord(unitCount);
}

So 1.5 seconds with maxDecimalPoints: 0 renders the count as "1" (from normalizedUnitCount) while languageWord(1.5) returns the plural form, giving "1 seconds".

Fix

Pluralize based on the count that is actually rendered, normalizedUnitCount:

if (typeof languageWord === "function") {
  word = languageWord(normalizedUnitCount);
}

When maxDecimalPoints is unset, normalizedUnitCount === unitCount, so existing behavior is unchanged. The fix only affects cases where the option truncates the count.

Test

Added a test to test/humanizer.js covering the truncated-to-one case, a truncated count that stays plural, and the same mechanism in German:

assert.strictEqual(humanizer({ maxDecimalPoints: 0 })(1500), "1 second");
assert.strictEqual(humanizer({ maxDecimalPoints: 2 })(1005), "1 second");
assert.strictEqual(humanizer({ maxDecimalPoints: 0 })(2500), "2 seconds");
assert.strictEqual(
  humanizer({ language: "de", maxDecimalPoints: 0 })(1500),
  "1 Sekunde"
);

Counterfactual (revert the one-line fix in renderPiece):

Expected values to be strictly equal:
expected: '1 second'
actual:   '1 seconds'

Gate

  • npm test (runs prettier --check + eslint + tsc --noEmit, then node --test): exit 0, all tests pass.