puppetlabs/puppet · error · ArgumentError

%{klass} cannot be subtracted from a Timespan

Error message

%{klass} cannot be subtracted from a Timespan

What it means

Timespan#- accepts only a Timespan or an Integer/Float (seconds) as the subtrahend. A Timestamp operand is deliberately absent because subtracting a point-in-time from a duration is meaningless; any other class hits the else branch and raises ArgumentError naming the offending class.

Source

Thrown at lib/puppet/pops/time/timespan.rb:150

      when Timespan
        Timespan.new(@nsecs + o.nsecs)
      when Integer, Float
        # Add seconds
        Timespan.new(@nsecs + (o * NSECS_PER_SEC).to_i)
      else
        raise ArgumentError, _("%{klass} cannot be added to a Timespan") % { klass: a_an_uc(o) } unless o.is_a?(Timespan)
      end
    end

    def -(o)
      case o
      when Timespan
        Timespan.new(@nsecs - o.nsecs)
      when Integer, Float
        # Subtract seconds
        Timespan.new(@nsecs - (o * NSECS_PER_SEC).to_i)
      else
        raise ArgumentError, _("%{klass} cannot be subtracted from a Timespan") % { klass: a_an_uc(o) }
      end
    end

    def -@
      Timespan.new(-@nsecs)
    end

    def *(o)
      case o
      when Integer, Float
        Timespan.new((@nsecs * o).to_i)
      else
        raise ArgumentError, _("A Timestamp cannot be multiplied by %{klass}") % { klass: a_an(o) }
      end
    end

    def divmod(o)
      case o

View on GitHub (pinned to e227c27540)

Solutions

  1. If you meant 'duration before a point in time', swap the operands: timestamp - timespan is valid and yields a Timestamp
  2. Convert string seconds with .to_i / Float(o) before subtracting
  3. Default optional inputs explicitly: (opts['margin'] || 0)
  4. Add an is_a?(Timespan) || is_a?(Integer) || is_a?(Float) check and fail with your own message naming the variable

Example fix

// before
remaining = timeout_span - node_value   # node_value is a String

// after
remaining = timeout_span - (node_value.is_a?(Numeric) ? node_value : node_value.to_i)
# if you actually wanted 'when does this expire':
# expiry = last_run_timestamp - timeout_span
Defensive patterns

Strategy: type-guard

Validate before calling

raise ArgumentError, "bad subtrahend #{o.inspect}" unless o.is_a?(Puppet::Pops::Time::Timespan) || o.is_a?(Integer) || o.is_a?(Float)
result = ts - o

Type guard

def timespan_subtractable?(o)
  o.is_a?(Puppet::Pops::Time::Timespan) || o.is_a?(Integer) || o.is_a?(Float)
end

Try / catch

begin
  ts - o
rescue ArgumentError => e
  raise DataError, "cannot subtract #{o.class} from Timespan (value: #{o.inspect})"
end

Prevention

When it happens

Trigger: ts - '10', ts - nil, ts - some_timestamp (Timespan minus Timestamp), or ts - a Rational/BigDecimal. Passing a Puppet Undef value or a Sensitive wrapper also lands here.

Common situations: Computing remaining TTL as Timespan - value from catalog data where the value is a String; accidentally swapping operands (should be Timestamp - Timespan, which is legal, rather than Timespan - Timestamp); nil from an optional variable that was never defaulted.

Related errors


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/0f0f0de0751b8889. Report an issue: GitHub.