date-fns/date-fns · error · RangeError
Format string contains an unescaped latin alphabet character
Error message
Format string contains an unescaped latin alphabet character `${firstCharacter}` What it means
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.
Source
Thrown at pkgs/core/src/format/index.ts:402
.join("")
.match(formattingTokensRegExp)!
.map((substring) => {
// Replace two single quote characters with one single quote character
if (substring === "''") {
return { isToken: false, value: "'" };
}
const firstCharacter = substring[0];
if (firstCharacter === "'") {
return { isToken: false, value: cleanEscapedString(substring) };
}
if (formatters[firstCharacter]) {
return { isToken: true, value: substring };
}
if (firstCharacter.match(unescapedLatinCharacterRegExp)) {
throw new RangeError(
"Format string contains an unescaped latin alphabet character `" +
firstCharacter +
"`",
);
}
return { isToken: false, value: substring };
});
// invoke localize preprocessor (only for french locales at the moment)
if (locale.localize.preprocessor) {
parts = locale.localize.preprocessor(originalDate, parts);
}
const formatterOptions = {
firstWeekContainsDate,
weekStartsOn,
locale,View on GitHub (pinned to 4098115cf7)
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).
Example fix
// before format(date, 'yyyy-MM-dd HH:mm UTC') // after format(date, "yyyy-MM-dd HH:mm 'UTC'")
Defensive patterns
Strategy: validation
Validate before calling
function assertNoUnescapedLatin(fmt: string) {
const re = /(\w)\1*|''|'(''|[^'])+('|$)|./g
for (const tok of fmt.match(re) ?? []) {
if (tok[0] === "'" || !/[a-zA-Z]/.test(tok[0])) continue
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])) {
throw new Error(`Unescaped latin character: ${tok[0]} in ${fmt}`)
}
}
} Try / catch
try {
return format(date, fmt)
} catch (e) {
if (e instanceof RangeError && /unescaped latin alphabet/.test(e.message)) {
return format(date, fmt.replace(/[A-Za-z]+/g, (m) => `'${m}'`))
}
throw e
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Use `${token.toLowerCase()}` instead of `${token}` (in `${fo
- Format string contains an unescaped latin alphabet character
- Format string contains an unescaped latin alphabet character
- Invalid time value
- The format string mustn't contain `${incompatibleToken.fullT
AI-assisted analysis of date-fns/date-fns@4098115cf7 (2026-08-03).
Data as JSON: /data/errors/5402aa9b911f908b.json.
Report an issue: GitHub.