{"record":{"id":"ee2aa9c6454c23e9","repo":"puppetlabs/puppet","slug":"klass-cannot-be-added-to-a-timespan","errorCode":null,"errorMessage":"%{klass} cannot be added to a Timespan","messagePattern":"%(.+?) cannot be added to a Timespan","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/puppet/pops/time/timespan.rb","lineNumber":138,"sourceCode":"      format.parse(str)\n    end\n\n    # @return [true] if the stored value is negative\n    def negative?\n      @nsecs < 0\n    end\n\n    def +(o)\n      case o\n      when Timestamp\n        Timestamp.new(@nsecs + o.nsecs)\n      when Timespan\n        Timespan.new(@nsecs + o.nsecs)\n      when Integer, Float\n        # Add seconds\n        Timespan.new(@nsecs + (o * NSECS_PER_SEC).to_i)\n      else\n        raise ArgumentError, _(\"%{klass} cannot be added to a Timespan\") % { klass: a_an_uc(o) } unless o.is_a?(Timespan)\n      end\n    end\n\n    def -(o)\n      case o\n      when Timespan\n        Timespan.new(@nsecs - o.nsecs)\n      when Integer, Float\n        # Subtract seconds\n        Timespan.new(@nsecs - (o * NSECS_PER_SEC).to_i)\n      else\n        raise ArgumentError, _(\"%{klass} cannot be subtracted from a Timespan\") % { klass: a_an_uc(o) }\n      end\n    end\n\n    def -@\n      Timespan.new(-@nsecs)\n    end","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/pops/time/timespan.rb#L120-L156","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nduration = ts + lookup('max_age')   # lookup returns a String like \"3600\"\n\n// after\nraw = lookup('max_age')\nduration = ts + (raw.is_a?(String) ? raw.to_i : raw)","handlingStrategy":"type-guard","validationCode":"allowed = [Puppet::Pops::Time::Timespan, Puppet::Pops::Time::Timestamp, Integer, Float]\nraise ArgumentError, \"bad addend #{o.inspect}\" unless allowed.any? { |k| o.is_a?(k) }\nduration = ts + (o.is_a?(String) ? o.to_i : o)","typeGuard":"def timespan_addable?(o)\n  o.is_a?(Puppet::Pops::Time::Timespan) ||\n    o.is_a?(Puppet::Pops::Time::Timestamp) ||\n    (o.is_a?(Integer) || o.is_a?(Float))\nend","tryCatchPattern":"begin\n  ts + o\nrescue ArgumentError => e\n  raise DataError, \"cannot add #{o.class} to Timespan (value: #{o.inspect})\" \nend","preventionTips":["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"],"tags":["puppet","timespan","arithmetic","argument-error","type-mismatch"],"backgroundTag":"invalid-operand-type","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}