Humanizr/Humanizer · error · ArgumentOutOfRangeException

Specified argument was out of the range of valid values. (Pa

Error message

Specified argument was out of the range of valid values. (Parameter 'seconds')

What it means

Thrown by DefaultFormatter.TimeSpanHumanizeWithFractionalSeconds when the seconds value is negative. The method formats a non-negative fractional-seconds count, so a negative value is invalid; zero is allowed. On .NET 8+ it uses ArgumentOutOfRangeException.ThrowIfNegative; otherwise a manual throw. The parameter is named 'seconds'.

Source

Thrown at src/Humanizer/Localisation/Formatters/DefaultFormatter.cs:78

    /// <inheritdoc/>
    public virtual string TimeSpanHumanize(TimeUnit timeUnit, int unit, bool toWords = false) =>
        TryFormatTimeSpanFromPhraseTable(timeUnit, unit, toWords, out var result)
            ? result
            : throw new InvalidOperationException($"Missing generated time-span phrase for '{Culture.Name}' and unit '{timeUnit}'.");

    /// <summary>
    /// Returns the localized representation of a non-negative seconds value.
    /// </summary>
    /// <param name="seconds">The non-negative seconds value to format.</param>
    /// <param name="toSymbols">Whether the seconds unit is rendered as a symbol.</param>
    /// <returns>The localized seconds value.</returns>
    public virtual string TimeSpanHumanizeWithFractionalSeconds(decimal seconds, bool toSymbols)
    {
#if NET8_0_OR_GREATER
        ArgumentOutOfRangeException.ThrowIfNegative(seconds);
#else
        if (seconds < 0)
            throw new ArgumentOutOfRangeException(nameof(seconds));
#endif

        var visibleSeconds = decimal.Parse(
            seconds.ToString("0.#######", CultureInfo.InvariantCulture),
            CultureInfo.InvariantCulture);
        var countValue = visibleSeconds.ToString(
            "0.#######",
            LocaleNumberFormattingOverrides.GetFormattingNumberFormat(Culture));

        var category = ValidateFractionalSecondGrammar(visibleSeconds, toSymbols);

        if (toSymbols)
        {
            return string.Concat(countValue, TimeUnitHumanize(TimeUnit.Second));
        }

        if (!phraseTable.TryGetTimeSpanPhrase(TimeUnit.Second, out var phrase) ||
            phrase.Multiple is not { } multiple)

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Pass Math.Abs(seconds) when you only care about the magnitude of the fractional seconds.
  2. Validate the sign at the source: if a negative value means 'in the past', branch and format the absolute value with a contextual prefix.
  3. Guard with a precondition: if (seconds < 0) throw or default before calling.

Example fix

// before
var text = formatter.TimeSpanHumanizeWithFractionalSeconds((decimal)ts.TotalSeconds, true);

// after
var secs = Math.Abs((decimal)ts.TotalSeconds);
var text = formatter.TimeSpanHumanizeWithFractionalSeconds(secs, true);
Defensive patterns

Strategy: validation

Validate before calling

if (seconds < 0)
    throw new ArgumentOutOfRangeException(nameof(seconds), "Must be non-negative.");
var text = formatter.TimeSpanHumanizeWithFractionalSeconds(seconds, toSymbols);

Type guard

static bool IsValidSeconds(decimal s) => s >= 0;

Try / catch

try { return formatter.TimeSpanHumanizeWithFractionalSeconds(seconds, toSymbols); }
catch (ArgumentOutOfRangeException ex) when (ex.ParamName == "seconds")
{ return formatter.TimeSpanHumanizeWithFractionalSeconds(Math.Abs(seconds), toSymbols); }

Prevention

When it happens

Trigger: Calling formatter.TimeSpanHumanizeWithFractionalSeconds(-0.5m, false); passing a computed seconds value from a negative TimeSpan without taking the absolute value; feeding a sensor/delta value that can be negative into the formatter directly.

Common situations: Neglecting to take TimeSpan.TotalSeconds absolute value before formatting a duration; passing a signed difference (e.g. now - other) where the sign was not resolved; reusing a 'seconds remaining' counter that can go negative on overshoot.

Related errors


AI-assisted analysis of Humanizr/Humanizer@ffc2b77c0f (2026-08-13). Data as JSON: /api/errors/aad8490372d44c10. Report an issue: GitHub.