puppetlabs/puppet · error · ArgumentError

A Timestamp cannot be multiplied by %{klass}

Error message

A Timestamp cannot be multiplied by %{klass}

What it means

Timespan#* only multiplies by Integer or Float and raises ArgumentError otherwise. Note the message text is misleading in Puppet itself: it says 'A Timestamp cannot be multiplied by ...' even though this is the Timespan#* operator - the string was copied from timestamp.rb. Trust the operand semantics, not the message wording.

Source

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

        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
      when Integer
        to_i.divmod(o)
      when Float
        to_f.divmod(o)
      else
        raise ArgumentError, _("Can not do modulus on a Timespan using a %{klass}") % { klass: a_an(o) }
      end
    end

    def modulo(o)
      divmod(o)[1]
    end

View on GitHub (pinned to e227c27540)

Solutions

  1. Recognize the message wording is a known copy-paste artifact - your receiver is a Timespan and only the right operand is at fault
  2. Convert the factor: ts * factor.to_i (or .to_f for fractional scaling)
  3. If multiplying two durations was intentional, redesign - scale by a dimensionless number instead
  4. Guard with factor.is_a?(Integer) || factor.is_a?(Float) before the expression

Example fix

// before
scaled = span * lookup('scale_factor')   # String "2"

// after
f = lookup('scale_factor')
scaled = span * (f.is_a?(String) ? f.to_f : f)
Defensive patterns

Strategy: type-guard

Validate before calling

raise ArgumentError, "factor must be Numeric, got #{o.class}" unless o.is_a?(Integer) || o.is_a?(Float)
scaled = ts * o

Type guard

def numeric_factor?(o)
  o.is_a?(Integer) || o.is_a?(Float)
end

Try / catch

begin
  ts * factor
rescue ArgumentError => e
  # note: message says 'Timestamp' but receiver is a Timespan (known wording quirk)
  factor = factor.to_f rescue raise DataError, e.message
  retry
end

Prevention

When it happens

Trigger: ts * '2', ts * nil, ts * another_timespan, or ts * a Decimal/BigDecimal type from Puppet's type system. Only Integer and Float pass the case statement.

Common situations: Scaling a duration by a factor pulled from Hiera that arrived as a String; developers confused by the error message into thinking their Timestamp is at fault when the receiver is actually a Timespan; multiplying two durations together by mistake.

Related errors


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