puppetlabs/puppet · error · ArgumentError

'from' must be less or equal to 'to'. Got (#{@from}, #{@to}

Error message

'from' must be less or equal to 'to'. Got (#{@from}, #{@to}

What it means

PTimespanType and PTimestampType (both PAbstractTimeDataType) are ranged types: initialize converts both bounds via convert_arg (nil/:default become -/+Infinity, Strings are parsed, numbers are seconds) and then requires from <= to. A reversed or overlapping-wrong range raises ArgumentError "'from' must be less or equal to 'to'" (the message text is missing its closing parenthesis - cosmetic bug in the source).

Source

Thrown at lib/puppet/pops/types/p_timespan_type.rb:11

# frozen_string_literal: true

module Puppet::Pops
module Types
  class PAbstractTimeDataType < PScalarType
    # @param from [AbstractTime] lower bound for this type. Nil or :default means unbounded
    # @param to [AbstractTime] upper bound for this type. Nil or :default means unbounded
    def initialize(from, to = nil)
      @from = convert_arg(from, true)
      @to = convert_arg(to, false)
      raise ArgumentError, "'from' must be less or equal to 'to'. Got (#{@from}, #{@to}" unless @from <= @to
    end

    # Checks if this numeric range intersects with another
    #
    # @param o [PNumericType] the range to compare with
    # @return [Boolean] `true` if this range intersects with the other range
    # @api public
    def intersect?(o)
      instance_of?(o.class) && !(@to < o.numeric_from || o.numeric_to < @from)
    end

    # Returns the lower bound of the numeric range or `nil` if no lower bound is set.
    # @return [Float,Integer]
    def from
      @from == -Float::INFINITY ? nil : @from
    end

    # Returns the upper bound of the numeric range or `nil` if no upper bound is set.

View on GitHub (pinned to e227c27540)

Solutions

  1. Swap the arguments so the earlier/lower value comes first: Timespan[60, 120]
  2. When bounds come from variables, normalize with min/max (or [a, b].minmax) before constructing the type
  3. Leave a bound as default/unbounded rather than passing an inverted sentinel value

Example fix

# before
$window = Timespan[120, 60]                                   # from > to
$range  = Timestamp['2024-06-01T00:00:00 UTC', '2024-01-01T00:00:00 UTC']

# after
$window = Timespan[60, 120]
$from, $to = [$start, $end].minmax                            # normalize variable bounds
$range  = Timestamp[$from, $to]
Defensive patterns

Strategy: validation

Validate before calling

# normalize bounds before constructing the ranged type
from, to = [from_bound, to_bound].minmax  # after converting to comparable Timespan/Timestamp
raise 'inverted range' if from && to && from > to
Timespan.new(from, to)  # or Timespan[from, to] in DSL

Type guard

def valid_time_bounds?(from, to)
  f = from.nil? || from == :default ? -Float::INFINITY : from.to_f
  t = to.nil? || to == :default ? Float::INFINITY : to.to_f
  f <= t
end

Try / catch

begin
  Timespan[from_arg, to_arg]
rescue ArgumentError => e
  raise unless e.message =~ /'from' must be less or equal to 'to'/
  Timespan[to_arg, from_arg]  # deliberate swap only when mis-ordering is provably the cause
end

Prevention

When it happens

Trigger: Timespan[120, 60] (lower bound of 120 seconds above upper bound of 60), or Timestamp['2024-01-01T00:00:00 UTC', '2023-12-31T23:59:59 UTC']. Any String, Integer, or Float pair where the first bound parses larger than the second, in DSL type expressions or Ruby PTimespanType.new(from, to).

Common situations: Bounds supplied as user variables in the wrong order (e.g. expiry window computed as [end, start]); DST or timezone shifts making a previously valid timestamp range inverted; literals written largest-first out of habit.

Related errors


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