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
- Convert the modulus to a plain Ruby number first: ts % divisor.to_i
- If you meant Timespan/Timespan division (ratio), use the div operator instead: span1 / span2 returns a Float
- Validate config values with assert_type(Numeric, ...) in the Puppet DSL before they reach Ruby
- 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
- Convert interval sizes from config once at startup: @bucket = Integer(config['bucket'])
- For Timespan/Timespan ratios use div (/), not % - different operator, different rules
- Assert Numeric in the Puppet DSL with assert_type before values reach Ruby
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
- %{klass} cannot be added to a Timespan
- %{klass} cannot be subtracted from a Timespan
- A Timespan cannot be divided by %{klass}
- Format must be a String
- A Timestamp cannot be multiplied by %{klass}
AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21).
Data as JSON: /api/errors/a9e4f866032c9008.
Report an issue: GitHub.