Humanizr/Humanizer · error · ArgumentOutOfRangeException

Unsupported time unit.

Error message

Unsupported time unit.

What it means

Thrown in IGrammaticalCaseTimeSpanFormatter.TimeSpanHumanize when the timeUnit argument is not a defined member of the TimeUnit enum. The guard casts to uint and compares against (uint)TimeUnit.Year (the highest defined unit), so any value above Year or a negative-cast-to-large-uint fails. The exception is ArgumentOutOfRangeException named 'timeUnit' with the offending value.

Source

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

    string IGrammaticalCaseTimeSpanFormatter.TimeSpanHumanize(
        TimeUnit timeUnit,
        int unit,
        GrammaticalCase grammaticalCase)
    {
        if (GetType().Assembly != typeof(DefaultFormatter).Assembly)
        {
            throw new NotSupportedException(
                $"Custom formatter type '{GetType().FullName}' must explicitly implement {nameof(IGrammaticalCaseTimeSpanFormatter)} to support grammatical-case-aware durations.");
        }

        if ((uint)grammaticalCase > (uint)GrammaticalCase.Causal)
        {
            throw new ArgumentOutOfRangeException(nameof(grammaticalCase), grammaticalCase, "Unsupported grammatical case.");
        }

        if ((uint)timeUnit > (uint)TimeUnit.Year)
        {
            throw new ArgumentOutOfRangeException(nameof(timeUnit), timeUnit, "Unsupported time unit.");
        }

        var table = LocaleDurationCaseTableCatalog.Resolve(Culture)
            ?? throw new NotSupportedException(
                $"Culture '{Culture.Name}' has no grammatical-case classification for duration phrases.");

        if (table.Classification == LocaleDurationCaseClassification.Unsupported)
        {
            throw new NotSupportedException(
                $"Culture '{Culture.Name}' has an applicable grammatical case system, but verified duration forms are unavailable.");
        }

        if (table.Classification == LocaleDurationCaseClassification.NotApplicable)
        {
            throw new NotSupportedException(
                $"Culture '{Culture.Name}' does not support grammatical-case duration phrases.");
        }

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Validate with Enum.IsDefined(typeof(TimeUnit), timeUnit) before calling.
  2. Bind configuration via strongly-typed enum parsing rather than raw int casts.
  3. Use the standard TimeSpan.Humanize() entry points, which only ever pass defined units internally.

Example fix

// before
var text = formatter.TimeSpanHumanize((TimeUnit)unitCode, count, grammaticalCase);

// after
if (!Enum.IsDefined(typeof(TimeUnit), unitCode))
    throw new ArgumentOutOfRangeException(nameof(unitCode));
var text = formatter.TimeSpanHumanize((TimeUnit)unitCode, count, grammaticalCase);
Defensive patterns

Strategy: validation

Validate before calling

if (!Enum.IsDefined(typeof(TimeUnit), timeUnit))
    throw new ArgumentOutOfRangeException(nameof(timeUnit));
var text = caseFormatter.TimeSpanHumanize(timeUnit, unit, grammaticalCase);

Type guard

static bool IsValidTimeUnit(TimeUnit u) => Enum.IsDefined(typeof(TimeUnit), u);

Try / catch

try { return caseFormatter.TimeSpanHumanize(timeUnit, unit, grammaticalCase); }
catch (ArgumentOutOfRangeException ex) when (ex.ParamName == "timeUnit")
{ /* invalid unit; handle upstream */ throw; }

Prevention

When it happens

Trigger: Calling the case-aware formatter with (TimeUnit)50; passing a TimeUnit deserialized from an out-of-range integer; default(TimeUnit) is the first member and is valid, so an uninitialized field will not throw (though it may produce the wrong unit).

Common situations: Storing TimeUnit as a raw int in config/database with a value outside the defined range; casting a foreign enum to TimeUnit; reflection-based dispatch that builds the unit dynamically.

Related errors


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