{"record":{"id":"73b99155e9dacda5","repo":"apache/cassandra","slug":"unable-to-convert-s-to-a-duration","errorCode":null,"errorMessage":"Unable to convert '%s' to a duration","messagePattern":"Unable to convert '(.+?)' to a duration","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/cql3/functions/types/Duration.java","lineNumber":160,"sourceCode":"        boolean isNegative = input.startsWith(\"-\");\n        String source = isNegative ? input.substring(1) : input;\n\n        if (source.startsWith(\"P\"))\n        {\n            if (source.endsWith(\"W\")) return parseIso8601WeekFormat(isNegative, source);\n\n            if (source.contains(\"-\")) return parseIso8601AlternativeFormat(isNegative, source);\n\n            return parseIso8601Format(isNegative, source);\n        }\n        return parseStandardFormat(isNegative, source);\n    }\n\n    private static Duration parseIso8601Format(boolean isNegative, String source)\n    {\n        Matcher matcher = ISO8601_PATTERN.matcher(source);\n        if (!matcher.matches())\n            throw new IllegalArgumentException(\n            String.format(\"Unable to convert '%s' to a duration\", source));\n\n        Builder builder = new Builder(isNegative);\n        if (matcher.group(1) != null) builder.addYears(groupAsLong(matcher, 2));\n\n        if (matcher.group(3) != null) builder.addMonths(groupAsLong(matcher, 4));\n\n        if (matcher.group(5) != null) builder.addDays(groupAsLong(matcher, 6));\n\n        // Checks if the String contains time information\n        if (matcher.group(7) != null)\n        {\n            if (matcher.group(8) != null) builder.addHours(groupAsLong(matcher, 9));\n\n            if (matcher.group(10) != null) builder.addMinutes(groupAsLong(matcher, 11));\n\n            if (matcher.group(12) != null) builder.addSeconds(groupAsLong(matcher, 13));\n        }","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/cql3/functions/types/Duration.java#L142-L178","documentation":"Duration.from(String) in Cassandra's CQL types layer routes any string starting with 'P' (after an optional leading '-') to parseIso8601Format, which requires the whole string to match the ISO 8601 designator pattern P[n]Y[n]M[n]DT[n]H[n]M[n]S. If the string does not fully match, an IllegalArgumentException with this message is thrown. It means the value looked like it intended to be ISO 8601 but is malformed for that grammar.","triggerScenarios":"Calling Duration.from() (directly or by binding a CQL duration literal via the driver) with a 'P'-prefixed string that is not a valid ISO 8601 duration, e.g. 'PT' (bare T with no time designators), 'P1H' (hours must come after T), 'P' alone, 'P1.5H' (fractional units unsupported), or lowercase designators like 'p1d'. Also any string starting with P and containing '-' that fails the alternative format falls through here.","commonSituations":"Copy-pasting durations from java.time.Duration#toString() ('PT24H' works but 'PT-1H' or '0.5S' do not), config files carrying Spring/Joda style durations ('P1D30M' out of order is fine here, but 'PT1m' lowercase fails), users writing 'P' + a Go-style duration, or mixing the alternative format 'P0001-02-03T04:05:06' with a typo that then lands in this parser.","solutions":["Rewrite the value in valid ISO 8601 duration form: order must be P [n]Y [n]M [n]D [T [n]H [n]M [n]S], uppercase designators, no fractions, e.g. 'P1Y2M3DT4H5M6S'.","Or use the Cassandra standard format instead ('1y2mo3d4h5m6s'), which does not require the P prefix.","Check unit semantics: months use uppercase M, minutes use the M after T; 'PM' or 'PT' with nothing after T is invalid.","If the string comes from another library (java.time.Duration, Joda), convert it explicitly before passing it in.","Wrap the call in try/catch for IllegalArgumentException and surface the offending string to the user."],"exampleFix":"// before\nDuration d = Duration.from(\"PT\"); // throws IllegalArgumentException\n// after\nDuration d = Duration.from(\"PT1H30M\"); // valid ISO 8601: 1.5 hours\n// or use the standard format\nDuration d2 = Duration.from(\"1h30m\");","handlingStrategy":"validation","validationCode":"private static final Pattern ISO8601 = Pattern.compile(\"P((\\\\d+)Y)?((\\\\d+)M)?((\\\\d+)D)?(T((\\\\d+)H)?((\\\\d+)M)?((\\\\d+)S)?)?\");\nboolean isParsableIso8601Duration(String s) {\n    String v = s.startsWith(\"-\") ? s.substring(1) : s;\n    return v.startsWith(\"P\") && ISO8601.matcher(v).matches() && !v.equals(\"P\") && !v.equals(\"PT\");\n}","typeGuard":"boolean isValidDurationString(String s) {\n    return s != null && !s.isEmpty() && (s.startsWith(\"-\") ? s.length() > 1 : true);\n}","tryCatchPattern":"try {\n    Duration d = Duration.from(input);\n} catch (IllegalArgumentException e) {\n    LOG.warn(\"Invalid duration '{}': {}\", input, e.getMessage());\n    throw new ConfigurationException(\"duration must match P[n]Y[n]M[n]DT[n]H[n]M[n]S, got: \" + input);\n}","preventionTips":["Always use uppercase ISO 8601 designators with no fractions when using the P-format.","Prefer the Cassandra standard format (e.g. '1h30m') for hand-written config; it is harder to misformat than ISO 8601.","Never pass java.time.Duration.toString() output with negative or fractional components directly; convert first.","Add a regex pre-check in config loading code that reports the offending key and value.","Unit-test every user-supplied duration string format your tooling accepts."],"tags":["duration","parsing","cql","iso8601","illegal-argument"],"backgroundTag":"invalid-duration-format","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}