{"record":{"id":"3553321d838f4675","repo":"locustio/locust","slug":"invalid-time-span-format-valid-formats-20-20s","errorCode":null,"errorMessage":"Invalid time span format. Valid formats: 20, 20s, 3m, 2h, 1h20m, 3h30m10s, etc.","messagePattern":"Invalid time span format\\. Valid formats: 20, 20s, 3m, 2h, 1h20m, 3h30m10s, etc\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"locust/util/timespan.py","lineNumber":20,"sourceCode":"from datetime import timedelta\n\n\ndef parse_timespan(time_str) -> int:\n    \"\"\"\n    Parse a string representing a time span and return the number of seconds.\n    Valid formats are: 20, 20s, 3m, 2h, 1h20m, 3h30m10s, etc.\n    \"\"\"\n    if not time_str:\n        raise ValueError(\"Invalid time span format\")\n\n    if re.match(r\"^\\d+$\", time_str):\n        # if an int is specified we assume they want seconds\n        return int(time_str)\n\n    timespan_regex = re.compile(r\"((?P<hours>\\d+?)h)?((?P<minutes>\\d+?)m)?((?P<seconds>\\d+?)s)?\")\n    parts = timespan_regex.match(time_str)\n    if not parts or parts.group(0) != time_str:\n        raise ValueError(\"Invalid time span format. Valid formats: 20, 20s, 3m, 2h, 1h20m, 3h30m10s, etc.\")\n    time_params = {name: int(value) for name, value in parts.groupdict().items() if value}\n    if not time_params:\n        raise ValueError(\"Invalid time span format. Valid formats: 20, 20s, 3m, 2h, 1h20m, 3h30m10s, etc.\")\n    return int(timedelta(**time_params).total_seconds())\n","sourceCodeStart":2,"sourceCodeEnd":25,"githubUrl":"https://github.com/locustio/locust/blob/f391a716e12c2c712e80b5835e877b7933397453/locust/util/timespan.py#L2-L25","documentation":"After regex matching, parse_timespan verifies the whole string matched the pattern (group(0) == time_str). Strings containing unsupported characters, units (d/w), signs, or partial matches raise this ValueError with the list of valid formats. Note an all-optional regex can also produce an empty match, which is caught later at line 23.","triggerScenarios":"parse_timespan('1d'), parse_timespan('2H') (uppercase), parse_timespan('3m30') (trailing unitless digits), parse_timespan('-5m'), or any string with whitespace/typos like ' 3m'.","commonSituations":"Users assuming day/week units are supported (they are not — only h/m/s); uppercase unit letters; stray spaces from config parsing; fractional durations like '1.5h'.","solutions":["Rewrite the duration using only supported units, e.g. '1d' → '24h', '1.5h' → '1h30m'","Strip whitespace and lowercase the input before passing it","Add your own pre-validation/normalization if you need days or weeks (convert to hours/minutes first)","Update the CLI/config value to one of: 20, 20s, 3m, 2h, 1h20m, 3h30m10s"],"exampleFix":"# before\nlocust --run-time=1d\n\n# after\nlocust --run-time=24h","handlingStrategy":"validation","validationCode":"import re\nRUNTIME_RE = re.compile(r'^\\d+(?:\\d*h)?(?:\\d*m)?(?:\\d*s)?$')\ndef is_valid_timespan(s):\n    return bool(RUNTIME_RE.fullmatch(s))  # then strip/lower before parse_timespan","typeGuard":null,"tryCatchPattern":"try:\n    seconds = parse_timespan(run_time.strip().lower())\nexcept ValueError as e:\n    log.error('%s — use formats like 20, 20s, 3m, 2h, 1h20m, 3h30m10s', e)\n    sys.exit(2)","preventionTips":["Only use digits plus h/m/s units; convert days to hours yourself","Normalize input with .strip().lower() before parsing","Avoid fractional durations; write '1h30m' instead of '1.5h'","Unit-test your config parsing with the same formats you ship in config files"],"tags":["timespan","cli","validation"],"backgroundTag":"invalid-timespan-format","analyzedSha":"f391a716e12c2c712e80b5835e877b7933397453","analyzedAt":"2026-08-29T00:36:13.872Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}