Skip to content

Intl: the locale and timeZone options are ignored (output is always en-US / UTC) #55

Closed

Description

@hassidan

Test I ran

node intl.ts
slice run intl.ts
intl.ts
const show = async (k: string, f: () => unknown) => { let v; try { v = await Promise.race([Promise.resolve().then(f), new Promise((r) => setTimeout(() => r("NEVER SETTLED (2000 ms)"), 2000))]); } catch (e: any) { v = "THROWS " + e.name + ": " + String(e.message).slice(0, 100); } console.log(k.padEnd(52) + JSON.stringify(v)); };
(async () => {
  await show("Intl.NumberFormat en-US 1234567.891", () => new Intl.NumberFormat("en-US").format(1234567.891));
  await show("Intl.NumberFormat de-DE EUR", () => new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR" }).format(1234.5));
  await show("Intl.NumberFormat percent", () => new Intl.NumberFormat("en-US", { style: "percent", maximumFractionDigits: 1 }).format(0.1234));
  await show("Intl.NumberFormat compact", () => new Intl.NumberFormat("en-US", { notation: "compact" }).format(1234567));
  await show("Intl.DateTimeFormat en-US UTC medium/short", () => new Intl.DateTimeFormat("en-US", { timeZone: "UTC", dateStyle: "medium", timeStyle: "short" }).format(new Date(0)));
  await show("Intl.DateTimeFormat Asia/Tokyo", () => new Intl.DateTimeFormat("en-US", { timeZone: "Asia/Tokyo", hour: "numeric", minute: "2-digit", hour12: false }).format(new Date(0)));
  await show("Date#toLocaleDateString en-US UTC", () => new Date(0).toLocaleDateString("en-US", { timeZone: "UTC" }));
  await show("Date#toLocaleString de-DE UTC", () => new Date(0).toLocaleString("de-DE", { timeZone: "UTC" }));
  await show("Intl.PluralRules en cardinal/ordinal", () => [new Intl.PluralRules("en-US").select(1), new Intl.PluralRules("en-US", { type: "ordinal" }).select(2), new Intl.PluralRules("ar").select(3)]);
  await show("Intl.Collator('de') sort", () => ["b", "a", "ä", "z"].sort(new Intl.Collator("de").compare));
  await show("Intl.Collator numeric", () => ["a10", "a2", "a1"].sort(new Intl.Collator("en", { numeric: true }).compare));
  await show("Intl.Segmenter grapheme", () => [...new Intl.Segmenter("en", { granularity: "grapheme" }).segment("a👍🏽é")].map((s) => s.segment));
  await show("Intl.Segmenter word", () => [...new Intl.Segmenter("en", { granularity: "word" }).segment("Hi there, you")].filter((s) => s.isWordLike).map((s) => s.segment));
  await show("(1234.5).toLocaleString('en-US')", () => (1234.5).toLocaleString("en-US"));
  await show("(1e21).toLocaleString('en-US')", () => (1e21).toLocaleString("en-US"));
  await show("(1234567.891).toLocaleString('de-DE')", () => (1234567.891).toLocaleString("de-DE"));
  await show("'a'.localeCompare('B','en',{sensitivity:'base'})", () => ["a".localeCompare("B", "en", { sensitivity: "base" }), "a".localeCompare("A", "en", { sensitivity: "base" })]);
})();

Result

Node v24.22.2:

Intl.NumberFormat en-US 1234567.891                 "1,234,567.891"
Intl.NumberFormat de-DE EUR                         "1.234,50 €"
Intl.NumberFormat percent                           "12.3%"
Intl.NumberFormat compact                           "1.2M"
Intl.DateTimeFormat en-US UTC medium/short          "Jan 1, 1970, 12:00 AM"
Intl.DateTimeFormat Asia/Tokyo                      "09:00"
Date#toLocaleDateString en-US UTC                   "1/1/1970"
Date#toLocaleString de-DE UTC                       "1.1.1970, 00:00:00"
Intl.PluralRules en cardinal/ordinal                ["one","two","few"]
Intl.Collator('de') sort                            ["a","ä","b","z"]
Intl.Collator numeric                               ["a1","a2","a10"]
Intl.Segmenter grapheme                             ["a","👍🏽","é"]
Intl.Segmenter word                                 ["Hi","there","you"]
(1234.5).toLocaleString('en-US')                    "1,234.5"
(1e21).toLocaleString('en-US')                      "1,000,000,000,000,000,000,000"
(1234567.891).toLocaleString('de-DE')               "1.234.567,891"
'a'.localeCompare('B','en',{sensitivity:'base'})    [-1,0]

Slice v0.10.7:

Intl.NumberFormat en-US 1234567.891                 "1,234,567.891"
Intl.NumberFormat de-DE EUR                         "€1,234.50"
Intl.NumberFormat percent                           "12.3%"
Intl.NumberFormat compact                           "1.2M"
Intl.DateTimeFormat en-US UTC medium/short          "Jan 1, 1970, 0:00"
Intl.DateTimeFormat Asia/Tokyo                      "0:00"
Date#toLocaleDateString en-US UTC                   "1/1/1970"
Date#toLocaleString de-DE UTC                       "1/1/1970, 0:00:00"
Intl.PluralRules en cardinal/ordinal                ["one","two","other"]
Intl.Collator('de') sort                            ["a","b","z","ä"]
Intl.Collator numeric                               ["a1","a2","a10"]
Intl.Segmenter grapheme                             ["a","👍","🏽","é"]
Intl.Segmenter word                                 ["Hi","there","you"]
(1234.5).toLocaleString('en-US')                    "1,234.5"
(1e21).toLocaleString('en-US')                      "1e,+21"
(1234567.891).toLocaleString('de-DE')               "1,234,567.891"
'a'.localeCompare('B','en',{sensitivity:'base'})    [-1,0]

What is wrong

Call Slice Expected
NumberFormat("de-DE", {style:"currency",currency:"EUR"}).format(1234.5) €1,234.50 1.234,50 €
(1234567.891).toLocaleString("de-DE") 1,234,567.891 1.234.567,891
DateTimeFormat("en-US", {timeZone:"Asia/Tokyo", hour, minute}) of epoch 0 0:00 09:00
DateTimeFormat("en-US", {timeZone:"UTC", dateStyle:"medium", timeStyle:"short"}) Jan 1, 1970, 0:00 Jan 1, 1970, 12:00 AM
new Date(0).toLocaleString("de-DE", {timeZone:"UTC"}) 1/1/1970, 0:00:00 1.1.1970, 00:00:00
new Intl.PluralRules("ar").select(3) other few
["b","a","ä","z"].sort(new Intl.Collator("de").compare) a, b, z, ä a, ä, b, z
Segmenter grapheme on "a👍🏽é" splits the emoji from its skin-tone modifier one grapheme
(1e21).toLocaleString("en-US") 1e,+21 1,000,000,000,000,000,000,000

The locale argument and the timeZone option are accepted and then have no effect. The time-zone case is the most serious because the result looks plausible: a timestamp formatted for Asia/Tokyo is shown in UTC with no error.

COMPATIBILITY.md lists Intl (DateTimeFormat, NumberFormat, Collator, PluralRules, Segmenter) as "100% Supported" via a "Zero-ICU-bloat deterministic Intl fast-path shard". If shipping without locale and tz data is intentional, the matrix should say that only en-US / UTC are implemented, and unsupported locales or zones should throw RangeError rather than silently format as en-US / UTC.

Environment

  • Slice v0.10.7, commit ea7c70c, built with cargo +1.96.0 build --release --locked --bin slice
  • Reference: Node.js v24.22.2 (same script, same machine)
  • Ubuntu 26.04, Linux 7.0, x86_64
  • The same output is produced by the v0.10.5 tag, so this is not a regression in 0.10.6/0.10.7.

Activity

  1. siktec-lab commented on Nov 11, 2026

    @siktec-lab
    Collaborator

    Addressed in #67 (�0.10.11): upgraded Intl in 18_intl.js with scientific-notation expansion (>= 1e21) and locale separators/currency placement in NumberFormat, IANA timezone offsets/DST and RangeError validation in DateTimeFormat, CLDR cardinal/ordinal rules (�r,
    u, pl, r, en, etc.) in PluralRules, Unicode Latin diacritic/umlaut sorting in Collator, and UAX #29 grapheme cluster segmentation (emoji skin-tone modifiers, ZWJ, flags, CRLF) in Segmenter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions