{"record":{"id":"8f1cd5e916646720","repo":"chinabugotech/hutool","slug":"unterminated-quote","errorCode":null,"errorMessage":"Unterminated quote","messagePattern":"Unterminated quote","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"hutool-core/src/main/java/cn/hutool/core/date/format/FastDateParser.java","lineNumber":183,"sourceCode":"\n\t\tprivate StrategyAndWidth literal() {\n\t\t\tboolean activeQuote = false;\n\n\t\t\tfinal StringBuilder sb = new StringBuilder();\n\t\t\twhile (currentIdx < pattern.length()) {\n\t\t\t\tfinal char c = pattern.charAt(currentIdx);\n\t\t\t\tif (!activeQuote && isFormatLetter(c)) {\n\t\t\t\t\tbreak;\n\t\t\t\t} else if (c == '\\'' && (++currentIdx == pattern.length() || pattern.charAt(currentIdx) != '\\'')) {\n\t\t\t\t\tactiveQuote = !activeQuote;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t++currentIdx;\n\t\t\t\tsb.append(c);\n\t\t\t}\n\n\t\t\tif (activeQuote) {\n\t\t\t\tthrow new IllegalArgumentException(\"Unterminated quote\");\n\t\t\t}\n\n\t\t\tfinal String formatField = sb.toString();\n\t\t\treturn new StrategyAndWidth(new CopyQuotedStrategy(formatField), formatField.length());\n\t\t}\n\t}\n\n\tprivate static boolean isFormatLetter(final char c) {\n\t\treturn c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z';\n\t}\n\n\t// Serializing\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Create the object after serialization. This implementation reinitializes the transient properties.\n\t *\n\t * @param in ObjectInputStream from which the object is being deserialized.","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/chinabugotech/hutool/blob/8870454b2a0c29cc6ffd31dcf5667c8ceb2fc442/hutool-core/src/main/java/cn/hutool/core/date/format/FastDateParser.java#L165-L201","documentation":"Thrown when constructing a FastDateParser from a pattern that contains an opening single quote (') with no matching closing quote. In SimpleDateFormat-style patterns, a single quote begins a literal/quoted section that must be terminated by another single quote. The parser reaches end-of-pattern while the quote is still 'active', so the format object can never be built.","triggerScenarios":"Calling DateUtil.parse / new FastDateFormat (or DatePattern-based parsing) with a pattern like \"dd 'MM\" (only one quote), \"HH''mm'ss\" (unbalanced), or any pattern where a ' opens a literal that runs to the end of the string. Also triggered when a quote is meant to escape a format letter but is never closed.","commonSituations":"User-supplied or config-driven date patterns built by string concatenation; patterns localized from resource bundles that lose a quote during editing; patterns that attempt to embed an apostrophe (e.g. \"o'clock\") but only add one quote instead of two ('' for a literal apostrophe).","solutions":["Balance the quotes: every opening ' must have a closing '. For a literal apostrophe inside the pattern use two consecutive quotes, e.g. \"HH''mm\".","Validate the pattern before constructing the parser (unit test, or FastDateFormat.getInstance(pattern) in a try/catch at startup).","If the quote is intentional literal text, ensure the literal segment is terminated, e.g. \"yyyy'year'\"."],"exampleFix":"// before\nFastDateFormat fdf = FastDateFormat.getInstance(\"dd 'MM\");\n// after\nFastDateFormat fdf = FastDateFormat.getInstance(\"dd 'MM'\");\n// or, for a literal apostrophe:\nFastDateFormat fdf = FastDateFormat.getInstance(\"HH''mm\");","handlingStrategy":"validation","validationCode":"private static final Pattern BALANCED = Pattern.compile(\"^(?:[^']|'(?:[^']|'')*')*$\");\nvoid checkPattern(String p){\n  // simple heuristic: count of unescaped quotes must be even\n  int q=0; for(int i=0;i<p.length();i++){ if(p.charAt(i)=='\\''){ if(i+1<p.length() && p.charAt(i+1)=='\\''){ i++; continue; } q++; } }\n  if((q&1)!=0) throw new IllegalArgumentException(\"unbalanced quote in pattern: \"+p);\n}","typeGuard":null,"tryCatchPattern":"try { FastDateFormat fdf = FastDateFormat.getInstance(pattern); }\ncatch (IllegalArgumentException e) { if (e.getMessage().contains(\"Unterminated quote\")) { /* fix pattern */ } else throw e; }","preventionTips":["Unit-test every externally-supplied date pattern at startup.","Use doubled single quotes ('') for literal apostrophes.","Never build patterns by blind concatenation of user input."],"tags":["date","format-pattern","parsing","fast-date","argument"],"backgroundTag":null,"analyzedSha":"8870454b2a0c29cc6ffd31dcf5667c8ceb2fc442","analyzedAt":"2026-08-14T04:01:12.892Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}