{"id":"5402aa9b911f908b","repo":"date-fns/date-fns","slug":"format-string-contains-an-unescaped-latin-alphabet","errorCode":null,"errorMessage":"Format string contains an unescaped latin alphabet character `${firstCharacter}`","messagePattern":"Format string contains an unescaped latin alphabet character `(.+?)`","errorType":"exception","errorClass":"RangeError","httpStatus":null,"severity":"error","filePath":"pkgs/core/src/format/index.ts","lineNumber":402,"sourceCode":"    .join(\"\")\n    .match(formattingTokensRegExp)!\n    .map((substring) => {\n      // Replace two single quote characters with one single quote character\n      if (substring === \"''\") {\n        return { isToken: false, value: \"'\" };\n      }\n\n      const firstCharacter = substring[0];\n      if (firstCharacter === \"'\") {\n        return { isToken: false, value: cleanEscapedString(substring) };\n      }\n\n      if (formatters[firstCharacter]) {\n        return { isToken: true, value: substring };\n      }\n\n      if (firstCharacter.match(unescapedLatinCharacterRegExp)) {\n        throw new RangeError(\n          \"Format string contains an unescaped latin alphabet character `\" +\n            firstCharacter +\n            \"`\",\n        );\n      }\n\n      return { isToken: false, value: substring };\n    });\n\n  // invoke localize preprocessor (only for french locales at the moment)\n  if (locale.localize.preprocessor) {\n    parts = locale.localize.preprocessor(originalDate, parts);\n  }\n\n  const formatterOptions = {\n    firstWeekContainsDate,\n    weekStartsOn,\n    locale,","sourceCodeStart":384,"sourceCodeEnd":420,"githubUrl":"https://github.com/date-fns/date-fns/blob/4098115cf705e3af7f663d8e5b0686e39a9f478a/pkgs/core/src/format/index.ts#L384-L420","documentation":"In format/index.ts:401-407, after the regex splits the format string into runs, any remaining single letter that matches [a-zA-Z] and has no registered formatter throws — date-fns refuses to silently emit an unknown token. Literal letters must be wrapped in single quotes so they are treated as escaped text rather than tokens.","triggerScenarios":"format(date, 'yyyy-MM-dd HH:mm:ss UTC') — the literal 'U','T','C' aren't known tokens. Also format(date, 'Year yyyy') where 'Y','e','a','r' are misread as potential tokens. Anything like 'h:mm a ET' triggers it.","commonSituations":"Adding timezone abbreviations, timezone offsets, weekday words, or unit labels (e.g. 'min','hrs') as literal text without escaping; refactor that concatenated a format string with variable words.","solutions":["Wrap literal text in single quotes: 'UTC' becomes 'UTC' -> format(date, \"yyyy-MM-dd'T'HH:mm 'UTC'\").","Use two single quotes '' to represent one literal single quote inside escaped text.","If the letter is meant to be a token, check the Unicode token table and use the correct case (e.g. 'S' for fractions of a second, 'X'/'x' for unix offsets)."],"exampleFix":"// before\nformat(date, 'yyyy-MM-dd HH:mm UTC')\n// after\nformat(date, \"yyyy-MM-dd HH:mm 'UTC'\")","handlingStrategy":"validation","validationCode":"function assertNoUnescapedLatin(fmt: string) {\n  const re = /(\\w)\\1*|''|'(''|[^'])+('|$)|./g\n  for (const tok of fmt.match(re) ?? []) {\n    if (tok[0] === \"'\" || !/[a-zA-Z]/.test(tok[0])) continue\n    if (!['y','M','d','H','h','m','s','S','a','p','P','E','G','u','w','W','k','K','D','Y','x','X','T','R','Q','q','i','I','L','l','c','N','n','o','A','b','B','v','V','V','Z','z'].includes(tok[0])) {\n      throw new Error(`Unescaped latin character: ${tok[0]} in ${fmt}`)\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  return format(date, fmt)\n} catch (e) {\n  if (e instanceof RangeError && /unescaped latin alphabet/.test(e.message)) {\n    return format(date, fmt.replace(/[A-Za-z]+/g, (m) => `'${m}'`))\n  }\n  throw e\n}","preventionTips":["Wrap any literal letters in single quotes from the moment you write the format.","Keep format strings in constants near the input source so they're easy to audit.","Avoid concatenating dynamic text into format strings."],"tags":["format","unicode-tokens","escaping","range-error"],"analyzedSha":"4098115cf705e3af7f663d8e5b0686e39a9f478a","analyzedAt":"2026-08-03T20:20:32.267Z","schemaVersion":2}