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 oView on GitHub (pinned to e227c27540)
Solutions
- If you meant 'duration before a point in time', swap the operands: timestamp - timespan is valid and yields a Timestamp
- Convert string seconds with .to_i / Float(o) before subtracting
- Default optional inputs explicitly: (opts['margin'] || 0)
- 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
- Remember Timespan - Timestamp is never valid; Timestamp - Timespan is - fix operand order first
- Wrap external data in a small coerce(v) helper that returns Numeric or raises with the variable name
- Unit-test arithmetic paths with String and nil inputs to catch coercion gaps early
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
- %{klass} cannot be added to a Timespan
- A Timestamp cannot be multiplied by %{klass}
- Can not do modulus on a Timespan using a %{klass}
- A Timespan cannot be divided by %{klass}
- Format must be a String
AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21).
Data as JSON: /api/errors/0f0f0de0751b8889.
Report an issue: GitHub.