{"record":{"id":"25d30be32f308de4","repo":"javan/whenever","slug":"must-be-between-at-given","errorCode":null,"errorMessage":"#{must_be_between}, #{at} given","messagePattern":"#(.+?), #(.+?) given","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/whenever/cron.rb","lineNumber":160,"sourceCode":"\n        return (timing << '1-5') * \" \" if string.downcase.index('weekday')\n        return (timing << '6,0') * \" \" if string.downcase.index('weekend')\n\n        DAYS.each_with_index do |day, i|\n          return (timing << i) * \" \" if string.downcase.index(day)\n        end\n\n        raise ArgumentError, \"Couldn't parse: #{@time.inspect}\"\n      end\n\n      def range_or_integer(at, valid_range, name)\n        must_be_between = \"#{name} must be between #{valid_range.min}-#{valid_range.max}\"\n        if at.is_a?(Range)\n          raise ArgumentError, \"#{must_be_between}, #{at.min} given\" unless valid_range.include?(at.min)\n          raise ArgumentError, \"#{must_be_between}, #{at.max} given\" unless valid_range.include?(at.max)\n          return \"#{at.min}-#{at.max}\"\n        end\n        raise ArgumentError, \"#{must_be_between}, #{at} given\" unless valid_range.include?(at)\n        at\n      end\n\n      def comma_separated_timing(frequency, max, start = 0)\n        return start     if frequency.nil? || frequency == \"\" || frequency.zero?\n        return '*'       if frequency == 1\n        return frequency if frequency > (max * 0.5).ceil\n\n        original_start = start\n\n        start += frequency unless (max + 1).modulo(frequency).zero? || start > 0\n        output = (start..max).step(frequency).to_a\n\n        max_occurances = (max.to_f  / (frequency.to_f)).round\n        max_occurances += 1 if original_start.zero?\n\n        output[0, max_occurances].join(',')\n      end","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/javan/whenever/blob/756163ed1aa928c8abd629aa2f4904bc5226538c/lib/whenever/cron.rb#L142-L178","documentation":"Whenever validates a numeric :at against the cron field it feeds: for hourly jobs the number is a minute (0-59), for daily jobs an hour (0-23), for monthly jobs a day (1-31), for yearly jobs a month (1-12). If the integer lies outside that window, range_or_integer raises ArgumentError with the field name, its bounds, and the value. Strings are not validated here — they go through Chronic first, so '4:30pm' works where 430 does not.","triggerScenarios":"every 1.hour, at: 75 → Minute must be between 0-59, 75 given. every 1.day, at: 430 (meaning 4:30) → Hour must be between 0-23, 430 given. every 1.year, at: 13 → Month must be between 1-12, 13 given. Any compacted military-time integer like 1430 passed as a number instead of a string.","commonSituations":"Passing clock times as integers (430, 1430) because they look numeric; scripts generating at values from timestamps or arithmetic that overshoot field bounds; mixing up which field the number applies to for each frequency (minute for hourly, hour for daily).","solutions":["Pass times as parseable strings: at: '14:30' or at: '4:30pm' — Whenever runs them through Chronic and extracts hour and minute.","If numeric, use only the field value: hour 0-23 for daily jobs, minute 0-59 for hourly jobs, day 1-31 for monthly, month 1-12 for yearly.","Validate generated schedules with a dry-run `whenever` in CI so out-of-range values fail early."],"exampleFix":"# before (Hour must be between 0-23, 1430 given)\nevery 1.day, at: 1430 do\n  rake 'daily:sync'\nend\n\n# after\nevery 1.day, at: '14:30' do\n  rake 'daily:sync'\nend","handlingStrategy":"validation","validationCode":"LIMITS = { minute: 0..59, hour: 0..23, day: 1..31, month: 1..12 }\nat = 1430; field = :hour # from: every 1.day, at: 1430\nunless LIMITS[field].cover?(at)\n  abort('numeric :at must be a ' + field.to_s + ' (0-23 for daily jobs); use a string like 14:30 for clock times')\nend","typeGuard":"def valid_at_integer?(at, field)\n  limits = { minute: 0..59, hour: 0..23, day: 1..31, month: 1..12 }\n  at.is_a?(Integer) && limits[field].cover?(at)\nend","tryCatchPattern":"begin\n  Whenever.cron(file: 'config/schedule.rb')\nrescue ArgumentError => e\n  abort('invalid numeric :at in schedule (pass clock times as strings): ' + e.message)\nend","preventionTips":["Pass clock times as strings ('14:30', '4:30pm') — Chronic parses them and extracts hour and minute.","For numeric at, remember which field it feeds: minute for hourly jobs, hour for daily, day for monthly, month for yearly.","Never pass compacted military-time integers like 1430; they are validated as raw field values."],"tags":["ruby","cron","scheduling","value-validation","argumenterror"],"backgroundTag":"cron-field-out-of-range","analyzedSha":"756163ed1aa928c8abd629aa2f4904bc5226538c","analyzedAt":"2026-08-21T18:07:39.974Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}