puppetlabs/puppet · error · ArgumentError

Can not do modulus on a Timespan using a %{klass}

Error message

Can not do modulus on a Timespan using a %{klass}

What it means

Timespan#divmod (and therefore #modulo and #%) only works when the divisor is an Integer (delegated to to_i.divmod) or a Float (delegated to to_f.divmod). Any other class raises ArgumentError with 'Can not do modulus on a Timespan using a %{klass}'.

Source

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

    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

    def %(o)
      modulo(o)
    end

    def div(o)
      case o
      when Timespan
        # Timespan/Timespan yields a Float
        @nsecs.fdiv(o.nsecs)
      when Integer, Float
        Timespan.new(@nsecs.div(o))

View on GitHub (pinned to e227c27540)

Solutions

  1. Convert the modulus to a plain Ruby number first: ts % divisor.to_i
  2. If you meant Timespan/Timespan division (ratio), use the div operator instead: span1 / span2 returns a Float
  3. Validate config values with assert_type(Numeric, ...) in the Puppet DSL before they reach Ruby
  4. Check for nil before the expression - optional config that was never set is the most common source

Example fix

// before
bucket = span % lookup('bucket_seconds')   # "900" from Hiera

// after
bucket = span % Integer(lookup('bucket_seconds'))
Defensive patterns

Strategy: validation

Validate before calling

raise ArgumentError, "modulus must be Integer/Float" unless divisor.is_a?(Integer) || divisor.is_a?(Float)
bucket = ts % divisor

Type guard

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

Try / catch

begin
  ts % m
rescue ArgumentError => e
  raise DataError, "bad modulus #{m.inspect} for Timespan"
end

Prevention

When it happens

Trigger: ts % '60', ts % nil, ts % another_timespan, or ts % a Rational. Also Puppet DSL like $span % $var where $var is a String from a fact or Hiera.

Common situations: Bucketing durations into intervals (span % 3600) with the bucket size loaded from config as a String; reusing Ruby's Numeric %-style idims with Puppet data; passing Puppet's Decimal type which is not a Ruby Float.

Related errors


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