Skip to content

Fix pluralization when maxDecimalPoints truncates the count to 1 - #239

Closed
dualfroz wants to merge 1 commit into
EvanHahn:mainfrom
dualfroz:dualfroz/fix-plural-with-maxdecimalpoints
Closed

Fix pluralization when maxDecimalPoints truncates the count to 1#239
dualfroz wants to merge 1 commit into
EvanHahn:mainfrom
dualfroz:dualfroz/fix-plural-with-maxdecimalpoints

Conversation

@dualfroz

@dualfroz dualfroz commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

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'

@EvanHahn

EvanHahn commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Thanks. I'll review in the next few weeks.

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.
@dualfroz
dualfroz force-pushed the dualfroz/fix-plural-with-maxdecimalpoints branch from e4768de to f21a996 Compare September 5, 2026 23:13
EvanHahn added a commit that referenced this pull request Sep 11, 2026
See [#239].

[#239]: #239

Co-Authored-By: Evan Hahn <me@evanhahn.com>
@EvanHahn

Copy link
Copy Markdown
Owner

I made some changes to this and merged it in 924c2a1. This has been released in humanize-duration@3.35.0.

Thank you!

@EvanHahn EvanHahn closed this Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants