diff --git a/src/__tests__/format.spec.ts b/src/__tests__/format.spec.ts index 89c972e..85f5c7c 100644 --- a/src/__tests__/format.spec.ts +++ b/src/__tests__/format.spec.ts @@ -271,4 +271,13 @@ describe("format with a timezone", () => { }) ).toBe("12/19/89, 1:30:10 AM -0600") }) + it("does not throw when locale already contains a Unicode extension subtag (#68)", () => { + // ar-u-nu-arab requests Arabic-Indic numerals via BCP 47 Unicode extension. + // Previously, format() appended "-u-hc-h23" producing the invalid tag + // "ar-u-nu-arab-u-hc-h23" which throws "Invalid language tag". + expect(() => format("2024-01-15", "YYYY/MM/DD", "ar-u-nu-arab")).not.toThrow() + // The formatted output should use Arabic-Indic digits (٢٠٢٤, etc.) + const result = format("2024-01-15", "YYYY/MM/DD", "ar-u-nu-arab") + expect(result).toBe("٢٠٢٤/٠١/١٥") + }) }) diff --git a/src/common.ts b/src/common.ts index 92d911d..f97f3f9 100644 --- a/src/common.ts +++ b/src/common.ts @@ -248,7 +248,14 @@ function createPartMap( const genitiveParts: Part[] = [] function addValues(requestedParts: Part[], hour12 = false) { - const preciseLocale = `${locale}-u-hc-${hour12 ? "h12" : "h23"}` + // Use Intl.Locale to merge the hourCycle extension into the locale tag. + // Plain string concatenation (e.g. `${locale}-u-hc-h23`) produces an + // invalid BCP 47 tag when the locale already contains a Unicode extension + // block (e.g. "ar-u-nu-arab"), because a tag may only have one "-u-" block. + // Intl.Locale merges extensions correctly into a single "-u-" subtag. + const preciseLocale = new Intl.Locale(locale, { + hourCycle: hour12 ? "h12" : "h23", + }).toString() valueParts.push( ...new Intl.DateTimeFormat( preciseLocale,