{"record":{"id":"4c2675deb8d02f20","repo":"puppetlabs/puppet","slug":"format-must-be-a-string","errorCode":null,"errorMessage":"Format must be a String","messagePattern":"Format must be a String","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/puppet/pops/time/timespan.rb","lineNumber":635,"sourceCode":"        _(\"Bad format specifier '%{expression}' in '%{format}', at position %{position}\") % { expression: format[start, position - start], format: format, position: position }\n      end\n\n      def append_literal(bld, codepoint)\n        if bld.empty? || !bld.last.is_a?(Format::LiteralSegment)\n          bld << Format::LiteralSegment.new(''.dup.concat(codepoint))\n        else\n          bld.last.concat(codepoint)\n        end\n      end\n\n      # States used by the #internal_parser function\n      STATE_LITERAL = 0 # expects literal or '%'\n      STATE_PAD = 1 # expects pad, width, or format character\n      STATE_WIDTH = 2 # expects width, or format character\n\n      def internal_parse(str)\n        bld = []\n        raise ArgumentError, _('Format must be a String') unless str.is_a?(String)\n\n        highest = -1\n        state = STATE_LITERAL\n        padchar = '0'\n        width = nil\n        position = -1\n        fstart = 0\n\n        str.each_codepoint do |codepoint|\n          position += 1\n          if state == STATE_LITERAL\n            if codepoint == 0x25 # '%'\n              state = STATE_PAD\n              fstart = position\n              padchar = '0'\n              width = nil\n            else\n              append_literal(bld, codepoint)","sourceCodeStart":617,"sourceCodeEnd":653,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/pops/time/timespan.rb#L617-L653","documentation":"FormatParser#internal_parse is the state machine that compiles a Timespan format string; its first act is to require the argument to be a String, otherwise ArgumentError 'Format must be a String'. Puppet's Format/TimeData APIs accept either a precompiled Format object or a String - a Symbol, Array, or nil format string is rejected here.","triggerScenarios":"Timespan#format(:default) (Symbol), Timespan.new(5).format(nil), or FormatParser.singleton.parse_format(42). Passing an Array of formats (legal for Timestamp) to the Timespan format path also fails here because Array is not a String. Puppet DSL: Timespan('5', ['%H','%M']) with array second arg.","commonSituations":"Porting code from Timestamp.parse(str, array_of_formats) to Timespan and reusing the array; passing a symbol constant or an options hash where the format string belongs; format value coming from a function that returns nil on lookup miss.","solutions":["For Timespan, pass a single format String or a compiled Format object - arrays are not supported on this class","If you got nil, default it explicitly: (fmt || '%H:%M:%S')","Convert symbols/other scalars with to_s only when you know they hold a format string","Check the API you targeted: Timestamp.parse supports :default and arrays, Timespan.parse supports a single Format::DEFAULTS fallback"],"exampleFix":"// before\nspan = Timespan.parse('12:30', formats)   # formats = ['%H:%M'] Array\n\n// after\nspan = Timespan.parse('12:30', formats.first)","handlingStrategy":"validation","validationCode":"raise ArgumentError, \"format must be a String or Format, got #{fmt.class}\" unless fmt.is_a?(String) || fmt.is_a?(Puppet::Pops::Time::Timespan::Format)\nspan.format(fmt)","typeGuard":"def valid_timespan_format?(f)\n  f.is_a?(String) || f.is_a?(Puppet::Pops::Time::Timespan::Format)\nend","tryCatchPattern":"begin\n  Timespan.parse(str, fmt)\nrescue ArgumentError => e\n  raise DataError, \"format arg was #{fmt.class}, expected String\"\nend","preventionTips":["Timespan formats: one String or compiled Format - never an Array (that is the Timestamp API)","Default nil formats explicitly: (fmt || '%H:%M:%S')","Type-check at module parameter boundaries before values reach Puppet internals"],"tags":["puppet","timespan","format-string","argument-error","type-mismatch"],"backgroundTag":"invalid-format-string","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}