Repository navigation
M12: regional formats (typed and displayed numbers, dates and times) - #447
quantmind-br wants to merge 1 commit into
Conversation
|
This looks like exactly the right shape for the problem — the Serbian ( static SR_RS: Region = Region {
tag: "sr-Latn-RS",
name: "Srpski (Srbija)",
lcid: 0x081A, // Serbian (Latin, Serbia)
decimal: ',',
group: '.',
grouping: Grouping::Thousands,
date_order: DateOrder::Dmy,
date_sep: '.',
time_sep: ':',
short_date: "dd.mm.yyyy",
long_date: "dddd, dd. mmmm yyyy",
short_time: "hh:mm",
long_time: "hh:mm:ss",
datetime: "dd.mm.yyyy hh:mm",
months: ["Januar", "Februar", "Mart", "April", "Maj", "Jun", "Jul", "Avgust", "Septembar", "Oktobar", "Novembar", "Decembar"],
months_abbr: ["Jan", "Feb", "Mar", "Apr", "Maj", "Jun", "Jul", "Avg", "Sep", "Okt", "Nov", "Dec"],
months_genitive: None, // Serbian doesn't inflect month names like ru/cs
weekdays: ["Nedelja", "Ponedeljak", "Utorak", "Sreda", "Četvrtak", "Petak", "Subota"],
weekdays_abbr: ["Ned", "Pon", "Uto", "Sre", "Čet", "Pet", "Sub"],
am_pm: ["AM", "PM"], // no native AM/PM convention; 24-hour clock is standard
currency: "RSD",
currency_format: "#,##0.00\" RSD\"",
currency_format_int: "#,##0\" RSD\"",
};A few judgment calls I made (correct me if CLDR/Windows disagree): Serbian uses a 24-hour clock almost exclusively, so Let me know if you'd rather I open this as a PR once #447 lands (adding |
|
@Dejan-Port thanks, Serbian would be a good addition. A separate PR after #447 lands would be less friction: #447 is already large, so I'd rather keep its scope fixed for review. You can branch from I checked the entry against CLDR 48 (ICU 78) and glibc's
|
1. Target branch unchanged (regional-formats) — staying here for review as asked; will rebase onto storytold/gridcraft main and reopen there once storytold#447 merges. 2. The trailing-separator fix was wrong in scope: trim_end_matches(seps) stripped any run of '/', '-' and '.' in every region, not one separator in regions whose own short date ends with it. That turned "3/4/" into a date in en-US, "25.09.2026.." into one in sr-Latn-RS, and similar for every other region, none of which have a trailing- separator convention. Replaced with the suggested, narrower check: strip exactly one instance of the region's own date_sep, and only when r.short_date.ends_with(r.date_sep). Added regression tests pinning en-US "3/4/", "1-2-", "2026-09-25-", "12.2026." and fr-FR "25.09.2026/" as text, and sr-Latn-RS "25.09.2026.." (a second trailing period) as text too. 3. long_date now ends with the period: "dddd, d. mmmm yyyy." — CLDR 48 (ICU 78) gives it for both the full and the long pattern, and glibc's sr_RS@latin uses %A, %d. %B %Y.; missed that the quoted "15. mart 2026." already had it. 4. Docs: ROADMAP.md (Languages paragraph, 2026-10-11 regional-formats row) and docs/localization-parity.md (count, region list, a new revision-history row for this change) now say 16 regions. Minor: short_date "dd.mm.yyyy." is cited from glibc's %d.%m.%Y. (sr_RS@latin), not Windows — I don't have a source for the Windows sr-Latn-RS short date pattern and shouldn't have claimed one. Tests: cargo test -p gridcraft-core 33/33 (including the 5 new regression cases above). cargo fmt --all -- --check and cargo clippy --workspace --all-targets -- -D warnings both clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
3653e5b to
dd5d5fc
Compare
Adds Region::sr-Latn-RS (crates/core/src/region.rs) to the Regional format system from storytold#447, plus a general parsing fix it exposed. Corrections from review on storytold#447 (thanks @quantmind-br): - lcid 0x241A (sr-Latn-RS), not 0x081A (the legacy sr-Latn-CS, Serbia and Montenegro). - Month and weekday names lowercase (januar, mart, nedelja, ponedeljak; jan, ned), matching cs-CZ's convention and CLDR/glibc. - am_pm: AM_PM (the shared constant) — Serbian uses the 24-hour clock, so this is unused in practice but keeps the struct uniform. - short_date "dd.mm.yyyy." keeps its trailing period, matching glibc sr_RS@latin's %d.%m.%Y. and Windows' sr-Latn-RS. long_date drops it ("dddd, d. mmmm yyyy"), matching the CLDR long (non-full) pattern quoted in review, with the weekday prepended the way every other region's long_date already does (per the module doc: "CLDR's *full* date, with the weekday"). - currency "1.234,56 RSD" (symbol after the number, period grouping) confirmed against CLDR. - Registered "sr" => "sr-Latn-RS" in lookup()'s language fallback — without it, sr/sr-RS/sr_RS.UTF-8/sr_RS@latin/sr-Cyrl-RS would all still resolve to en-US regardless of the new Region entry, since lookup() only reaches a Region through this explicit match. sr-Cyrl variants fold into the same Latin region for now, as the review comment flagged (lookup drops the @latin/script modifier before matching; no separate Cyrillic region exists yet). The trailing period that was specifically being verified exposed a real gap: parse_date's `t.split(seps)` left a trailing empty part whenever input ended with a separator, so a Serbian short date typed back exactly as shown ("25.09.2026.") fell through every match arm and failed to parse as a date at all. Fixed by trimming one trailing separator before splitting — a one-line, region-agnostic fix; no currently-shipped region's short_date ends with a separator, so this changes no existing behaviour (covered by the existing hostile_input_never_panics_in_any_region fuzz test, which still passes). Tests: extended dates_in_the_regions_order (plain and trailing-period Serbian dates, including the round-trip through `.format`), month_names_in_local_words, currency_follows_the_region, lookup_accepts_system_and_browser_tags (sr, sr-RS, sr_RS.UTF-8, sr_RS@latin, sr-Cyrl-RS), and month_names_match_loosely. Full cargo test -p gridcraft-core: 33/33. cargo fmt --all -- --check and cargo clippy --workspace --all-targets -- -D warnings both clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1. Target branch unchanged (regional-formats) — staying here for review as asked; will rebase onto storytold/gridcraft main and reopen there once storytold#447 merges. 2. The trailing-separator fix was wrong in scope: trim_end_matches(seps) stripped any run of '/', '-' and '.' in every region, not one separator in regions whose own short date ends with it. That turned "3/4/" into a date in en-US, "25.09.2026.." into one in sr-Latn-RS, and similar for every other region, none of which have a trailing- separator convention. Replaced with the suggested, narrower check: strip exactly one instance of the region's own date_sep, and only when r.short_date.ends_with(r.date_sep). Added regression tests pinning en-US "3/4/", "1-2-", "2026-09-25-", "12.2026." and fr-FR "25.09.2026/" as text, and sr-Latn-RS "25.09.2026.." (a second trailing period) as text too. 3. long_date now ends with the period: "dddd, d. mmmm yyyy." — CLDR 48 (ICU 78) gives it for both the full and the long pattern, and glibc's sr_RS@latin uses %A, %d. %B %Y.; missed that the quoted "15. mart 2026." already had it. 4. Docs: ROADMAP.md (Languages paragraph, 2026-10-11 regional-formats row) and docs/localization-parity.md (count, region list, a new revision-history row for this change) now say 16 regions. Minor: short_date "dd.mm.yyyy." is cited from glibc's %d.%m.%Y. (sr_RS@latin), not Windows — I don't have a source for the Windows sr-Latn-RS short date pattern and shouldn't have claimed one. Tests: cargo test -p gridcraft-core 33/33 (including the 5 new regression cases above). cargo fmt --all -- --check and cargo clippy --workspace --all-targets -- -D warnings both clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Brings in ka-catalog (upstream main + storytold#447 + Georgian table). Keeps the fork-only Windows preview workflow; the ported ka-fixes follow in the next merge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dd5d5fc to
04ced7b
Compare
|
Update: Rebased onto
s.execute("selection.set", json!({"range":"E8:F13"})).ok();
s.execute("home.hideRows", json!({"rows":"9:11"})).ok();
s.execute("home.rowHeight", json!({"height":60.0})).ok();
// Sales!C11 =SUBTOTAL(109,Sales[Q1]) stays 203670; a full recalc gives 304820The command-sequence fuzz on Also in this rebase:
|
04ced7b to
2eeecc9
Compare
|
Rebased onto Also in this rebase:
|
2eeecc9 to
a7288a3
Compare
a7288a3 to
67f02cd
Compare
Adds an Excel-style Region setting that is independent of the interface
language and of the formula language (FormulaLocale is untouched).
- core: region.rs with 15 regions (en-US, en-GB, en-IN, es-ES, de-DE,
fr-FR, it-IT, pt-BR, pt-PT, ru-RU, cs-CZ, ja-JP, ko-KR, zh-CN, zh-TW):
separators, grouping, date order, short/long date and time codes, month
and day names, AM/PM words, currency. Data from CLDR.
- core: parse_input_in(s, sys, &Region) reads regional numbers, currency,
percent, dates (D/M/Y, M/D/Y, Y/M/D, local month names) and times;
parse_input stays the en-US wrapper.
- numfmt: format_value_in renders separators, month/day names and AM/PM
per region; built-in formats 14 and 22 show the region's short date,
[$-F800]/[$-F400] its long date and time. format_value stays en-US.
- model: Workbook.region, a #[serde(skip)] runtime field (en-US default).
- engine: Prefs.region is the session preference; it is applied to every
open and new document and re-applied on undo/redo. New noundo command
app.region {region?} sets it (strict tags, error lists the valid ones)
or reports it. Cell input, the editor text, data validation, what-if
and forecast inputs and display use the workbook region.
- ui-egui: Backstage Options "Regional format" combo, i18n labels in all
shipped languages, Ctrl+; and Ctrl+Shift+; follow the region, constants
are no longer re-read by the formula language.
- apps: desktop starts from the system region on first run, then the
saved pref; web from navigator.language; headless sessions use en-US.
- docs: control-protocol.md, localization-parity.md, ROADMAP.md.
Command text is canonical (en-US) unless the call carries region: "<tag>"
(cell.set input, range.fill, range.setValues, edit.paste text, data.validate,
data.forecastSheet end). The UI passes the workbook's region on every edit it
commits, so a script, a journal or a canonical producer (JSON values, Replace,
scenarios, Circle Invalid Data) never depends on the preference. CSV/TSV files
and Get Data from a range are canonical en-US; only the system clipboard and
HTML copy carry the displayed text.
Stored values, formulas and format codes are never rewritten, so changing
region only changes how they are typed and shown. Worksheet-function text
coercion (VALUE, DATEVALUE, TEXT, criteria) and chart axes stay en-US.
67f02cd to
fe1b10f
Compare
|
Rebased onto On this branch, |
|
@quantmind-br I tried this PR together with #443, merged on What works. With a Croatian region added (see below), typed Croatian falls back to en-US.
The short date ends with a period, so Question: should formula separators follow the region? In Excel with an English interface and Croatian regional settings:
So Excel takes function names from its interface language, but the decimal and argument separators in formulas from the regional settings. Here the region is kept out of formulas, and #443 ties the separators to the interface language, so this combination can't type a decimal comma in a formula. Would you consider taking the formula decimal and list separators from the region, with #443's dialects supplying the names? I only measured this combination (English interface, Croatian region), so whether a German interface with a US region gives A suggestion: a "System" region. Excel doesn't use a fixed table; it reads the Windows regional settings, including what the user changed there. A customised short date such as |
|
@vedran-dobos thank you, this is exactly the measurement that was missing. Separators. Your English UI + hr-HR result agrees with an independent one on #191: a French Excel 2024 used How I'd stage it, without reshaping either PR under review:
Your open question stays open in that plan: whether a German UI with a US region gives hr-HR. Yes please, as a separate PR once #447 lands, as with the Serbian region. Taking Windows' data (via "System" region. I like it as a desktop default, as a follow-up after #447. The fixed regions stay for the web app and for anyone who picks one explicitly. It needs a per-OS reader: on Windows the Also thanks for confirming the fuzz result (seed 0 fails without #984, passes with it). |
|
Closing in favour of a stack of smaller PRs, same final tree (rebased onto current main):
I'll open each one as the previous lands. |
Adds Region::sr-Latn-RS (crates/core/src/region.rs) to the Regional format system from storytold#447, plus a general parsing fix it exposed. Corrections from review on storytold#447 (thanks @quantmind-br): - lcid 0x241A (sr-Latn-RS), not 0x081A (the legacy sr-Latn-CS, Serbia and Montenegro). - Month and weekday names lowercase (januar, mart, nedelja, ponedeljak; jan, ned), matching cs-CZ's convention and CLDR/glibc. - am_pm: AM_PM (the shared constant) — Serbian uses the 24-hour clock, so this is unused in practice but keeps the struct uniform. - short_date "dd.mm.yyyy." keeps its trailing period, matching glibc sr_RS@latin's %d.%m.%Y. and Windows' sr-Latn-RS. long_date drops it ("dddd, d. mmmm yyyy"), matching the CLDR long (non-full) pattern quoted in review, with the weekday prepended the way every other region's long_date already does (per the module doc: "CLDR's *full* date, with the weekday"). - currency "1.234,56 RSD" (symbol after the number, period grouping) confirmed against CLDR. - Registered "sr" => "sr-Latn-RS" in lookup()'s language fallback — without it, sr/sr-RS/sr_RS.UTF-8/sr_RS@latin/sr-Cyrl-RS would all still resolve to en-US regardless of the new Region entry, since lookup() only reaches a Region through this explicit match. sr-Cyrl variants fold into the same Latin region for now, as the review comment flagged (lookup drops the @latin/script modifier before matching; no separate Cyrillic region exists yet). The trailing period that was specifically being verified exposed a real gap: parse_date's `t.split(seps)` left a trailing empty part whenever input ended with a separator, so a Serbian short date typed back exactly as shown ("25.09.2026.") fell through every match arm and failed to parse as a date at all. Fixed by trimming one trailing separator before splitting — a one-line, region-agnostic fix; no currently-shipped region's short_date ends with a separator, so this changes no existing behaviour (covered by the existing hostile_input_never_panics_in_any_region fuzz test, which still passes). Tests: extended dates_in_the_regions_order (plain and trailing-period Serbian dates, including the round-trip through `.format`), month_names_in_local_words, currency_follows_the_region, lookup_accepts_system_and_browser_tags (sr, sr-RS, sr_RS.UTF-8, sr_RS@latin, sr-Cyrl-RS), and month_names_match_loosely. Full cargo test -p gridcraft-core: 33/33. cargo fmt --all -- --check and cargo clippy --workspace --all-targets -- -D warnings both clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1. Target branch unchanged (regional-formats) — staying here for review as asked; will rebase onto storytold/gridcraft main and reopen there once storytold#447 merges. 2. The trailing-separator fix was wrong in scope: trim_end_matches(seps) stripped any run of '/', '-' and '.' in every region, not one separator in regions whose own short date ends with it. That turned "3/4/" into a date in en-US, "25.09.2026.." into one in sr-Latn-RS, and similar for every other region, none of which have a trailing- separator convention. Replaced with the suggested, narrower check: strip exactly one instance of the region's own date_sep, and only when r.short_date.ends_with(r.date_sep). Added regression tests pinning en-US "3/4/", "1-2-", "2026-09-25-", "12.2026." and fr-FR "25.09.2026/" as text, and sr-Latn-RS "25.09.2026.." (a second trailing period) as text too. 3. long_date now ends with the period: "dddd, d. mmmm yyyy." — CLDR 48 (ICU 78) gives it for both the full and the long pattern, and glibc's sr_RS@latin uses %A, %d. %B %Y.; missed that the quoted "15. mart 2026." already had it. 4. Docs: ROADMAP.md (Languages paragraph, 2026-10-11 regional-formats row) and docs/localization-parity.md (count, region list, a new revision-history row for this change) now say 16 regions. Minor: short_date "dd.mm.yyyy." is cited from glibc's %d.%m.%Y. (sr_RS@latin), not Windows — I don't have a source for the Windows sr-Latn-RS short date pattern and shouldn't have claimed one. Tests: cargo test -p gridcraft-core 33/33 (including the 5 new regression cases above). cargo fmt --all -- --check and cargo clippy --workspace --all-targets -- -D warnings both clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Closes #361. Second of the focused PRs announced in #191: a Regional format setting next to the interface language, the way Excel follows the Windows/macOS region. Typed numbers, dates and times, and how they are displayed, follow it (
1.234,56,25/09/2026,15.03.2026). Stored values, formulas, number format codes and files stay en-US.What changes
crates/core/src/region.rs: en-US, en-GB, en-IN, es-ES, de-DE, fr-FR, it-IT, pt-BR, pt-PT, ru-RU, cs-CZ, ja-JP, ko-KR, zh-CN and zh-TW. Each has decimal and group separators (with Indian 3;2 grouping), date order, short and long date and time codes, month and day names (genitive months for ru and cs), AM/PM words and currency. The data is from CLDR; the module doc lists the deliberate deviations. Other system tags fall back to the language's main region (en-AU → en-GB, pt-AO → pt-PT).parse_input_in(s, sys, &Region)reads regional numbers, currency, percent, day/month order, two-digit years, local month names, ISO dates and local AM/PM. The returned format codes stay canonical (m/d/yyyy), so a region change never rewrites a cell.format_value_inapplies regional separators, names and AM/PM. Built-in formats 14 and 22 show the region's short date, and[$-F800]/[$-F400]its long date and time, as Excel does.format_valueis unchanged (en-US).app.region {region?}: strict tag; an unknown tag is an error that lists the valid ones; without a param it reports the current region. The command is journaled but not undoable, and a region change neither dirties a workbook nor recalculates.prefs.json. The desktop app starts from the system region on first run, the web app fromnavigator.language(CJK regions keep en-US there, like the language), and CLI, MCP and tests use en-US.The engine boundary stays canonical
Command text is en-US unless the call says otherwise.
cell.set(input),range.fill,range.setValues(string cells),edit.paste(text),data.validate(input) anddata.forecastSheet(end) acceptregion: "<tag>", meaning "this text was typed in this region". The UI passes the workbook's region on every edit it commits. So:cell.set {value: 1.234}stores 1.234 in de-DE), Replace, scenarios and Circle Invalid Data never reparse their text in the region.docs/control-protocol.mddocumentsapp.regionand theregionparam.Behaviour change to note
The Spanish interface used to read decimal-comma constants (
-1,5) in the formula bar on its own. Now the region decides: Spanish interface with the es-ES region (the first-run default on a Spanish system) works as before, and with en-US-1,5is text.crates/ui-egui/tests/formula_locale.rsis updated to that.Not in this PR (documented in
localization-parity.md)The following stay en-US:
VALUE,DATEVALUE,TEXT, criteria);With #443
The code merges cleanly with #443 (formula languages). Only
ROADMAP.mdanddocs/localization-parity.mdconflict, and I'll rebase whichever lands second. With both merged, the formula, calc, core, numfmt, ui-egui and engine tests pass. The only failures are the CSV tests below, which also fail on main.Tests
cargo xtask cilocally:cargo test --workspace: 1360 passed, except three tests that fail on current main without this PR:io::tests::csv_save_keeps_the_separator_it_was_opened_withandtests::saving_an_opened_semicolon_csv_keeps_semicolons: CSV: saving keeps the separator the file was opened with (#426) #427's expectations meet File formats: CSV/TSV export writes displayed values; templates and .xlsm saved with their own content types #422's CSV display (124412140710is saved as1.24412E+11).i18n::tests::translations_are_unique_and_preserve_unknown_text: the French table from i18n: add French (fr) interface #413 lacksDifferent First Pagefrom Headers and footers: first-page and even-page variants, Insert › Header & Footer edits in place (#344) #404.New tests cover:
format_value_inmatchingformat_value;regionparam on every command that takes it;-R$ 5committing as a number with a pt-BR region and the Spanish interface.Smoke on the release build through the control channel:
1.234,51.234,501234,515.3.202615.03.202615.03.202612,5%12,50%0,1251.234,50 €1.234,50 €1234,525/09/202625/09/202615/03/2026after switching to en-GB.cell.set {input: "1.5"}stores 1.5 and shows1,5, and"1,234"withoutregionstores 1234 (en-US grouping).#,##0, 1.5 and a date are shown as1.000,1,5,25.09.2026and saved to CSV as"1,000",1.5,9/25/2026; reopening the file gives the same values.Follow-up, #1398 (draft, stacked on this and #443): formulas typed in the editor use this region's list and decimal separators, as Excel does, whatever the interface language. This PR is unchanged.