Skip to content

Fix CLDR pattern letters left in locale formats - #3370

Merged
kylekatarnls merged 17 commits into
briannesbitt:masterfrom
kamthorn:fix/locale-yy-tokens
Oct 2, 2026
Merged

kylekatarnls merged 17 commits into
briannesbitt:masterfrom
kamthorn:fix/locale-yy-tokens

Conversation

@kamthorn

@kamthorn kamthorn commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #3371

Problem

Some locale formats were copied from CLDR patterns without converting them to isoFormat() tokens. In CLDR, yy is a 2-digit year and d is the day of the month. In isoFormat(), yy is not a token (so it's printed literally), and d is the day of the week:

Locale Format Before After
es_PH L 30/9/yy 30/9/26
ne_IN L Ly/9/3 (the leading L is the macro bug fixed in #3369) 26/9/30
ps, ps_AF L 2026/9/3 2026/9/30
seh LL 3 de Set de 2026 30 de Set de 2026
fur, fur_IT LL 30 3i setembar 3pm30. 9. 26 2026 30 di setembar dal 2026

(Examples use 2026-09-30, a Wednesday.)

In fur, the words "di" and "dal" weren't escaped, so d, a and l were read as tokens.

Changes

One commit per file. Each commit message cites the CLDR 48.2.2 pattern it was checked against (cldr-json, cldr-dates-full/main//ca-gregorian.json).

File Key Before After CLDR 48.2.2
es_PH L D/M/yy D/M/YY d/M/yy
fo_DK L DD.MM.yy DD.MM.YY dd.MM.yy
hr_BA L D. M. yy. D. M. YY. d. M. yy.
ms_BN L D/MM/yy D/MM/YY d/MM/yy
ms_SG L D/MM/yy D/MM/YY d/MM/yy
ne_IN L yy/M/d YY/M/D yy/M/d
nnh L DD/MM/yy DD/MM/YY dd/MM/yy
nnh LLL, LLLL [lyɛ]̌ʼ d [na] MMMM… [lyɛ]̌ʼ D [na] MMMM… 'lyɛ'̌ʼ d 'na' MMMM, y
pa_Guru L D/M/yy D/M/YY d/M/yy
sr_Cyrl_BA L D.M.yy. D.M.YY. d. M. y.
sr_Latn_BA L D.M.yy. D.M.YY. d. M. y.
ta_MY L D/M/yy D/M/YY d/M/yy
ta_SG L D/M/yy D/M/YY d/M/yy
uz_Cyrl L DD/MM/yy DD/MM/YY dd/MM/yy
fur_IT LL, LLL, LLLL DD di MMMM dal YYYY… DD [di] MMMM [dal] YYYY… d 'di' MMMM 'dal' y
ps L YYYY/M/d YYYY/M/D y/M/d
seh LL, LLL, LLLL d [de] MMM… D [de] MMM… d 'de' MMM 'de' y

sr_Cyrl_XK, sr_Latn_XK (inheriting from _BA), ps_AF and fur get the fix through their parent file.

Only the token letters changed. Everything else in each format was left as it was, including zero-padding and separators. There's one exception: CLDR 48 now uses d. M. y. (4-digit year, with spaces) for sr_*_BA. I kept the 2-digit year that the current format intends, so the only change is yy → YY. I can switch them to D. M. YYYY. if you'd prefer following current CLDR.

Tests

New tests/Language/LocaleFormatsTest.php:

  • testFormatsHaveNoCldrOnlyLetters goes through every locale and fails if a format (LT…llll) or calendar format has an unescaped y or N. Those are CLDR year/era letters and are printed literally today. Before this PR, it lists 15 locales.
  • testCldrFormatsUseIsoTokens checks the output of each fixed format.

No existing expected value had to change. The full phpunit suite, phpstan and phpmd pass locally on PHP 8.4, and php-cs-fixer reports nothing for the changed files.

Notes

This change was written with the help of Claude Code (AI). I reviewed the diff myself.

🤖 Generated with Claude Code

kamthorn and others added 17 commits September 30, 2026 12:42
CLDR 48.2.2 es-PH: short = d/M/yy

Refs briannesbitt#3368

Co-Authored-By: Claude Opus 5.5 
CLDR 48.2.2 fo-DK: short = dd.MM.yy

Refs briannesbitt#3368

Co-Authored-By: Claude Opus 5.5 
CLDR 48.2.2 hr-BA: short = d. M. yy.

Refs briannesbitt#3368

Co-Authored-By: Claude Opus 5.5 
CLDR 48.2.2 ms-BN: short = d/MM/yy

Refs briannesbitt#3368

Co-Authored-By: Claude Opus 5.5 
CLDR 48.2.2 ms-SG: short = d/MM/yy

Refs briannesbitt#3368

Co-Authored-By: Claude Opus 5.5 
CLDR 48.2.2 ne-IN: short = yy/M/d

Refs briannesbitt#3368

Co-Authored-By: Claude Opus 5.5 
CLDR 48.2.2 nnh: short = dd/MM/yy, long = 'lyɛ'̌ʼ d 'na' MMMM, y

Refs briannesbitt#3368

Co-Authored-By: Claude Opus 5.5 
CLDR 48.2.2 pa-Guru: short = d/M/yy

Refs briannesbitt#3368

Co-Authored-By: Claude Opus 5.5 
CLDR 48.2.2 sr-Cyrl-BA: short = d. M. y. (2-digit year kept, see PR)

Refs briannesbitt#3368

Co-Authored-By: Claude Opus 5.5 
CLDR 48.2.2 sr-Latn-BA: short = d. M. y. (2-digit year kept, see PR)

Refs briannesbitt#3368

Co-Authored-By: Claude Opus 5.5 
CLDR 48.2.2 ta-MY: short = d/M/yy

Refs briannesbitt#3368

Co-Authored-By: Claude Opus 5.5 
CLDR 48.2.2 ta-SG: short = d/M/yy

Refs briannesbitt#3368

Co-Authored-By: Claude Opus 5.5 
CLDR 48.2.2 uz-Cyrl: short = dd/MM/yy

Refs briannesbitt#3368

Co-Authored-By: Claude Opus 5.5 
CLDR 48.2.2 fur: long = d 'di' MMMM 'dal' y

Refs briannesbitt#3368

Co-Authored-By: Claude Opus 5.5 
CLDR 48.2.2 ps: short = y/M/d

Refs briannesbitt#3368

Co-Authored-By: Claude Opus 5.5 
CLDR 48.2.2 seh: long = d 'de' MMMM 'de' y

Refs briannesbitt#3368

Co-Authored-By: Claude Opus 5.5 
Checks that no locale format or calendar format contains unescaped y or N
(CLDR year/era letters, printed literally by isoFormat()), and checks the
output of every format fixed in this branch.

Refs briannesbitt#3368

Co-Authored-By: Claude Opus 5.5 
@kylekatarnls kylekatarnls added this to the 3.14.1 milestone Oct 2, 2026
@kylekatarnls
kylekatarnls merged commit 6b929fe into briannesbitt:master Oct 2, 2026
24 checks passed
@kylekatarnls kylekatarnls modified the milestones: 3.14.1, 3.14.2 Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Locale formats contain CLDR pattern letters that isoFormat() reads differently

2 participants