puppetlabs/puppet · error · ArgumentError

Illegal timezone '%{timezone}'

Error message

Illegal timezone '%{timezone}'

What it means

Timestamp.utc_offset resolves a timezone argument either as the keyword 'current' (uses the Ruby process's local zone) or via DateTime._strptime(timezone, '%z'), which understands numeric offsets like '+02:00', '-0600', and 'Z'. Named zones such as 'PST', 'UTC' (only case-insensitive 'current' is special-cased) or 'America/New_York' do not parse, and ArgumentError 'Illegal timezone' is raised. There is no zoneinfo database lookup here.

Source

Thrown at lib/puppet/pops/time/timestamp.rb:42

        sprintf('-%2.2d:%2.2d', offset / 60, offset % 60)
      else
        sprintf('+%2.2d:%2.2d', offset / 60, offset % 60)
      end
    end
  end

  # Returns the zone offset from utc for the given `timezone`
  # @param [String] timezone the timezone to get the offset for
  # @return [Integer] the timezone offset, in seconds
  #
  # @api private
  def self.utc_offset(timezone)
    if CURRENT_TIMEZONE.casecmp(timezone) == 0
      ::Time.now.utc_offset
    else
      hash = DateTime._strptime(timezone, '%z')
      offset = hash.nil? ? nil : hash[:offset]
      raise ArgumentError, _("Illegal timezone '%{timezone}'") % { timezone: timezone } if offset.nil?

      offset
    end
  end

  # Formats a ruby Time object using the given timezone
  def self.format_time(format, time, timezone)
    unless timezone.nil? || timezone.empty?
      time = time.localtime(convert_timezone(timezone))
    end
    time.strftime(format)
  end

  def self.now
    from_time(::Time.now)
  end

  def self.from_time(t)

View on GitHub (pinned to e227c27540)

Solutions

  1. Use a numeric UTC offset string instead of a zone name: '+01:00', '-0500', or the literal 'current'
  2. For 'UTC' pass '+00:00'
  3. Resolve named zones yourself before calling: require 'time'; offset = TZInfo::Timezone.get(name).period_for_utc(Time.now).utc_offset then format with '+HH:MM' built from it
  4. In the Puppet DSL, Timestamp(str, fmt, timezone) has the same constraint - fix the data, not the call

Example fix

// before
stamp.format('%F %T %z', 'America/New_York')   # raise

 after
stamp.format('%F %T %z', '-05:00')   # fixed offset; adjust for DST yourself
# or resolve dynamically:
# require 'tzinfo'; o = TZInfo::Timezone.get('America/New_York').utc_now_and_offset
Defensive patterns

Strategy: validation

Validate before calling

def legal_puppet_timezone?(tz)
  return true if tz.nil? || tz.empty? || tz.casecmp('current') == 0
  h = DateTime._strptime(tz, '%z')
  !h.nil? && !h[:offset].nil?
end
raise ConfigError, "zone #{tz} not supported" unless legal_puppet_timezone?(tz)

Type guard

def numeric_zone_or_current?(tz)
  tz.casecmp('current') == 0 || tz.match?(/\A[+-]\d\d:?\d\d\z/) || tz == 'Z'
end

Try / catch

begin
  stamp.format(fmt, tz)
rescue ArgumentError => e
  raise ConfigError, "timezone #{tz.inspect} rejected - use 'current' or '+HH:MM'"
end

Prevention

When it happens

Trigger: Timestamp#format('%F %T %z', 'PST'), Timestamp#format(fmt, 'America/New_York'), Timestamp.parse(str, fmt, 'UTC') - all raise; only 'current', '+05:00', '-0800', 'Z'-style strings work. Reached via convert_timezone, which format_time calls whenever timezone is non-empty.

Common situations: Passing IANA zone names from module data into Timestamp#format; assuming 'UTC' is accepted because 'current' is; migrating from Time#in_time_zone('...') APIs; converting reports for humans in named zones.

Related errors


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