puppetlabs/puppet · error · ArgumentError
%{klass} cannot be added to a Timespan
Error message
%{klass} cannot be added to a Timespan What it means
Puppet::Pops::Time::Timespan#+ only accepts a Timestamp, a Timespan, or a plain Integer/Float (interpreted as seconds). Any other operand class falls into the else branch and raises ArgumentError with the operand's class name interpolated. Integer/Float operands are converted with (o * NSECS_PER_SEC).to_i, i.e. whole seconds, not nanoseconds.
Source
Thrown at lib/puppet/pops/time/timespan.rb:138
format.parse(str)
end
# @return [true] if the stored value is negative
def negative?
@nsecs < 0
end
def +(o)
case o
when Timestamp
Timestamp.new(@nsecs + o.nsecs)
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)
endView on GitHub (pinned to e227c27540)
Solutions
- Convert numeric-looking strings before adding: ts + o.to_i or ts + Float(o) for fractional seconds
- If the operand is a duration string like '1h30m', parse it: Timespan.parse(o) or construct Timespan.from_hash({'hours' => 30})
- Guard the call site with a case/is_a? check on Timestamp, Timespan, Integer, Float and raise a domain-specific error otherwise
- nil almost always means an upstream lookup returned nothing - check for nil before the expression rather than rescuing after
Example fix
// before
duration = ts + lookup('max_age') # lookup returns a String like "3600"
// after
raw = lookup('max_age')
duration = ts + (raw.is_a?(String) ? raw.to_i : raw) Defensive patterns
Strategy: type-guard
Validate before calling
allowed = [Puppet::Pops::Time::Timespan, Puppet::Pops::Time::Timestamp, Integer, Float]
raise ArgumentError, "bad addend #{o.inspect}" unless allowed.any? { |k| o.is_a?(k) }
duration = ts + (o.is_a?(String) ? o.to_i : o) Type guard
def timespan_addable?(o)
o.is_a?(Puppet::Pops::Time::Timespan) ||
o.is_a?(Puppet::Pops::Time::Timestamp) ||
(o.is_a?(Integer) || o.is_a?(Float))
end Try / catch
begin
ts + o
rescue ArgumentError => e
raise DataError, "cannot add #{o.class} to Timespan (value: #{o.inspect})"
end Prevention
- Coerce Hiera/JSON values with Integer()/Float() at the boundary where they enter your code
- Default optional numbers explicitly with || 0 to keep nil away from arithmetic
- In Puppet DSL, validate with assert_type(Numeric, $x) before passing to Ruby functions
When it happens
Trigger: Calling ts + '5', ts + nil, ts + [1], ts + some_hash, or ts + a BigDecimal/Rational with a Puppet::Pops::Time::Timespan instance on the left. Also adding a value obtained from Puppet lookup/Hiera that arrived as a String.
Common situations: Puppet DSL or Ruby custom functions doing arithmetic on durations where one side came from external data (JSON/YAML gives Strings, not Integers); mixing Puppet's Timespan with Ruby Duration-like objects from other gems; passing a Puppet::Pops::Types::PScalarType value directly.
Related errors
- %{klass} cannot be subtracted from 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/ee2aa9c6454c23e9.
Report an issue: GitHub.