Skip to content

Consider long-safe TimeSpan.Humanize counts for very large durations #1728

@clairernovotny

Description

@clairernovotny

Problem

TimeSpan.Humanize() currently reduces duration counts to int before passing them into the formatter pipeline. That works for normal spans, but very large spans can exceed int.MaxValue when the selected output unit is seconds, milliseconds, minutes, or hours.

The current path is roughly:

  • TimeSpanHumanizeExtensions.GetTimeUnitNumericalValue(...) returns int
  • GetNormalCaseTimeAsInteger(...) casts TotalSeconds / TotalMilliseconds / etc. to int when the requested unit is the maximum unit
  • BuildFormatTimePart(...) then calls Math.Abs(int) before IFormatter.TimeSpanHumanize(TimeUnit, int, ...)

This can make extreme values runtime/TFM dependent. While adding coverage in PR #1727, TimeSpan.MaxValue.Humanize(maxUnit: TimeUnit.Second, minUnit: TimeUnit.Second) overflowed on some targets because the cast produced int.MinValue, and Math.Abs(int.MinValue) throws. Other targets produced a different saturated value, so the behavior was not a stable cross-target invariant.

Why this matters

Even if TimeSpan.MaxValue is an edge case, the library should avoid undefined or runtime-dependent behavior when humanizing large but valid TimeSpan values. At minimum, it should not throw because an internal cast overflowed before formatting.

Possible approaches

  • Make the TimeSpan.Humanize internal count calculation long-safe and avoid Math.Abs(int.MinValue).
  • Consider whether the formatter contract should support long counts for time spans. Today IFormatter.TimeSpanHumanize(TimeUnit, int, bool) and related formatter/profile methods are int-based, so a public API change needs careful compatibility review.
  • If full long support is too broad, define and test explicit saturating/clamping behavior for counts above int.MaxValue.

Acceptance criteria

  • TimeSpan.Humanize() has deterministic behavior for very large durations across supported TFMs.
  • Very large valid TimeSpan values do not fail due to an internal numeric overflow in count calculation or Math.Abs.
  • Tests cover at least one count above int.MaxValue for a small unit such as seconds or milliseconds.
  • If formatter public APIs change, API approval snapshots and custom formatter compatibility are handled intentionally.

Related context: discovered while raising coverage in PR #1727.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions