{"record":{"id":"f8827c047686ccb4","repo":"vitessio/vitess","slug":"stray-at-the-end-of-pattern","errorCode":null,"errorMessage":"stray %% at the end of pattern","messagePattern":"stray %% at the end of pattern","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/mysql/datetime/strftime.go","lineNumber":34,"sourceCode":"*/\n\npackage datetime\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n)\n\nfunc compile(ds map[byte]Spec, p string, exec func(Spec)) error {\n\tfor l := len(p); l > 0; l = len(p) {\n\t\ti := strings.IndexByte(p, '%')\n\t\tif i < 0 {\n\t\t\texec(&fmtVerbatim{s: p})\n\t\t\tbreak\n\t\t}\n\t\tif i == l-1 {\n\t\t\treturn errors.New(`stray %% at the end of pattern`)\n\t\t}\n\n\t\t// we found a '%'. we need the next byte to decide what to do next\n\t\t// we already know that i < l - 1\n\t\t// everything up to the i is verbatim\n\t\tif i > 0 {\n\t\t\texec(&fmtVerbatim{s: p[:i]})\n\t\t\tp = p[i:]\n\t\t}\n\n\t\tif spec, ok := ds[p[1]]; ok {\n\t\t\tif spec == nil {\n\t\t\t\treturn fmt.Errorf(`unsupported format specifier: %%%c`, p[1])\n\t\t\t}\n\t\t\texec(spec)\n\t\t} else {\n\t\t\texec(&fmtVerbatim{s: p[1:2]})\n\t\t}","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/mysql/datetime/strftime.go#L16-L52","documentation":"The strftime-style pattern compiler in go/mysql/datetime rejects format strings that end with a bare '%' — the trailing '%' has no following conversion specifier byte, so the compiled program would be incomplete. Returned by compile (invoked via New and Format) when strings.IndexByte finds '%' at the last position of the pattern.","triggerScenarios":"Format(pattern, t) or New(pattern) with a pattern whose last character is '%', e.g. \"%Y-%m-%d %\".","commonSituations":"Format strings assembled by string concatenation where a literal percent got orphaned; translating a format from another language and forgetting '%' must be escaped by doubling; copy-pasted templates with trailing whitespace stripped.","solutions":["Remove the trailing '%' or add the intended specifier after it (e.g. '%%' for a literal percent sign)","Double literal percent signs ('%%') in the pattern, since % introduces a specifier","Trim or sanitize user-supplied format templates before compiling"],"exampleFix":"// before\nout, err := datetime.Format(\"%Y-%m-%d %\", ts)\n// after\nout, err := datetime.Format(\"%Y-%m-%d %%\", ts)","handlingStrategy":"validation","validationCode":"func validStrftimePattern(p string) bool { return p == \"\" || !strings.HasSuffix(p, \"%\") }","typeGuard":"func safeFormatPattern(p string) (string, bool) {\n    if strings.HasSuffix(p, \"%\") { return strings.TrimSuffix(p, \"%\"), false }\n    return p, true\n}","tryCatchPattern":"out, err := datetime.Format(pattern, ts)\nif err != nil && strings.Contains(err.Error(), \"stray %\") {\n    out, err = datetime.Format(strings.TrimRight(pattern, \"%\"), ts)\n}","preventionTips":["Never end a format string with a bare '%' — use '%%' for a literal percent","Escape user-supplied templates before compiling","Add a unit test asserting your production format strings compile"],"tags":["datetime","format-string","parsing"],"backgroundTag":"invalid-format-string","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}