Skip to content

fix(format): support thousands grouping and format sections in numberFormat/TEXT (HF-287) - #1716

Open
marcin-kordas-hoc wants to merge 14 commits into
developfrom
feat/hf-287-numberformat-sections
Open

marcin-kordas-hoc wants to merge 14 commits into
developfrom
feat/hf-287-numberformat-sections

Conversation

@marcin-kordas-hoc

@marcin-kordas-hoc marcin-kordas-hoc commented Jul 24, 2026 •

Copy link
Copy Markdown
Collaborator

Context

TEXT and the built-in number formatter understood only a plain mask of digit placeholders with an optional decimal part. Other masks leaked their tail into the result: TEXT(1234.5,"#,##0.00") returned 1235,##0.00. Thousands grouping (#,##0), sections (positive;negative;zero), and the decimalSeparator and thousandSeparator options were ignored.

This PR extends the formatter in place rather than rewriting its grammar:

  • the number-format regular expression admits , inside its flat character class, with no nested quantifier, so it stays free of catastrophic backtracking;
  • the formatter splits the mask into sections (aware of quotes and backslash escapes), picks the section by the sign of the value, groups digits with thousandSeparator, and writes the decimal point with decimalSeparator. A section it cannot parse is rendered as literal text. Values of 1e21 and above are converted to digits with BigInt, which is why hyperformula/tsconfig.json adds the es2020.bigint library;
  • color tags such as [Red] are stripped after the stringifyCurrency callback and before date and time formatting. That fixes [Red]dd-mm-yyyy, whose d in Red was read as a day token: TEXT(1,"[Red]dd-mm-yyyy") returned [Re31]31-12-1899 and now returns 31-12-1899. As a consequence, a custom stringifyDateTime now receives the mask without color tags (dd-mm-yyyy), while stringifyCurrency still receives it unchanged ([Red]0.00).

The compatibility guide (docs/guide/compatibility-with-microsoft-excel.md) now lists what the built-in formatter handles and what still needs a callback.

How did you test your changes?

Paired tests: handsontable/hyperformula-tests#27, branch feat/hf-287-numberformat-sections.

  • unit/format/format-number-sections.spec.ts: 65 tests, all green on this head. Run against develop, with its one white-box import stubbed (the exported regular-expression source does not exist there), 43 of them fail.
  • unit/interpreter/function-text.spec.ts: one existing assertion changes, TEXT(12.45,"$###,##0.00") from $12,##0.00 to $12.45 on the default config.
  • Both specs together: 110 tests green on this head.

Results that changed for masks that used to work: on the default config, TEXT(5,"0,000") was 5,000 and is now 0005, because the default thousandSeparator is empty and the old output was the mask tail, not grouping.

Compatibility with Excel and Google Sheets

HyperFormula results measured on this head, with thousandSeparator: ',' and functionArgSeparator: ';' unless noted. The Excel column is the expected result of each mask and was not re-measured for this revision. Google Sheets was not checked.

Formula Excel Google Sheets HyperFormula
=TEXT(1234.5;"#,##0.00") 1,234.50 Not checked 1,234.50
=TEXT(-1234.5;"#,##0.00;-#,##0.00") -1,234.50 Not checked -1,234.50
=TEXT(-1234.5;"$#,##0.00;-$#,##0.00") -$1,234.50 Not checked -$1,234.50
=TEXT(1234.567;"$#,##0.00") $1,234.57 Not checked $1,234.57
=TEXT(-5,"000.00") (default config) -005.00 Not checked -005.00

The formatter still does not implement percent scaling (0.00% returns 0.50% for 0.5), scaler commas (0,,), the ? placeholder, scientific notation (0.00E+00) or [condition] comparators; the compatibility guide lists them.

Public API changes

None in signatures. Behaviour: a custom stringifyDateTime callback receives the format without color tags, as described in Context.

Out of scope and follow-ups

  • The formats listed at the end of the compatibility section above. No follow-up ticket yet.

Types of changes

  • Breaking change (existing functionality no longer works as before) – agreed with the maintainers
  • New feature or improvement (a non-breaking change that adds functionality)
  • Bug fix (a non-breaking change that fixes an issue)
  • Additional language file, or a change to an existing language file (translations)
  • Change to the documentation

Related issues

  1. HF-287. Public issue: TEXT() function should support popular currency formats #1145, which asked for $#,##0.00 in TEXT.

Checklist


Note

Medium Risk
Changes core TEXT/formatting behavior and what custom stringifyDateTime receives; some previously wrong outputs (e.g. masks that looked like grouping with default config) will differ intentionally.

Overview
Fixes TEXT and the shared format() number path so Excel-style masks like #,##0 and multi-section patterns (0.00;(0.00)) render correctly instead of leaking mask tail text (e.g. 1235,##0.00). The formatter now splits sections on unescaped ; (respecting quotes/escapes), picks positive/negative/zero by value sign, applies decimalSeparator / thousandSeparator from config, handles literal-only sections, signed-zero rounding, and uses BigInt for magnitudes ≥ 1e21 (es2020.bigint in tsconfig).

Color tags ([Red], etc.) are stripped after stringifyCurrency and before date/duration dispatch, so colored date masks format as dates and custom stringifyDateTime receives the mask without the color segment (documented as a behavior change). The number-format tokenizer admits grouping commas with a flat regex and only treats runs containing #/0 as number tokens.

Docs and CHANGELOG cover supported vs callback-only format features and the stringifyDateTime callback input change.

Reviewed by Cursor Bugbot for commit eb272ea. Bugbot is set up for automated code reviews on this repo. Configure here.

@netlify

netlify Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for hyperformula-dev-docs ready!

Name Link
🔨 Latest commit 51ed68a
🔍 Latest deploy log https://app.netlify.com/projects/hyperformula-dev-docs/deploys/6a7610dc68d7270007fc85e6
😎 Deploy Preview https://deploy-preview-1716--hyperformula-dev-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@qunabu

qunabu commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

@marcin-kordas-hoc

Copy link
Copy Markdown
Collaborator Author

bugbot run

@marcin-kordas-hoc
marcin-kordas-hoc force-pushed the feat/hf-287-numberformat-sections branch from 98bb963 to 8a64987 Compare July 24, 2026 12:32
Comment thread hyperformula/src/format/parser.ts
Comment thread src/format/format.ts Outdated
@github-actions

github-actions Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

Performance comparison of head (eb272ea) vs base (d143456)

                                     testName |    base |    head | change
--------------------------------------------------------------------------
                                      Sheet A |  414.06 |  399.53 | -3.51%
                                      Sheet B |  140.32 |   135.3 | -3.58%
                                      Sheet T |  117.82 |  114.23 | -3.05%
                                Column ranges |  433.45 |  421.15 | -2.84%
                                Sorted lookup | 12822.3 | 12328.8 | -3.85%
Sheet A:  change value, add/remove row/column |   14.81 |   14.85 | +0.27%
 Sheet B: change value, add/remove row/column |   122.3 |  132.34 | +8.21%
                   Column ranges - add column |  140.49 |  131.96 | -6.07%
                Column ranges - without batch |  428.28 |  423.17 | -1.19%
                        Column ranges - batch |  106.48 |  109.45 | +2.79%

@marcin-kordas-hoc
marcin-kordas-hoc force-pushed the feat/hf-287-numberformat-sections branch from 8a64987 to b92d6c0 Compare July 24, 2026 12:54
…Format/TEXT (HF-287)

The TEXT number formatter understood only a single simple mask
(`[#0]+(\.[#0]*)?`), so complex masks leaked their unparsed tail into the
output (e.g. `TEXT(1234.5,"#,##0.00")` -> `1235,##0.00`) and it ignored
the instance's configured separators.

Extend the existing formatter in place (Option A):
- parser.ts: widen the number-format regex to a FLAT class `[#0,]+(\.[#0]*)?`
  that admits the grouping comma (no nested quantifier — DEV-2120 ReDoS
  discipline). Export its source for a white-box shape test.
- format.ts: strip presentational color tags (`[Red]`, ...) after the currency
  callback and before date/time dispatch; split the mask into sign-selected
  sections (positive;negative;zero) honoring quotes/escapes; thread Config so
  the decimal glyph uses `decimalSeparator` and grouping uses `thousandSeparator`
  (empty on default config -> no visible glyph). Sign is extracted on `abs`,
  fixing the pre-existing `padLeft('-5',3)` bug (`TEXT(-5,"000.00")` -> `-005.00`).
  Trailing scaler commas degrade to a visible literal rather than silently
  mis-scaling. Parse failures fall back to the cleaned format string.

No public API change, no i18n, no grammar rewrite. Percent scaling, scaler
arithmetic, `?` placeholders, scientific notation and `[condition]` comparators
remain out of scope.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@marcin-kordas-hoc
marcin-kordas-hoc force-pushed the feat/hf-287-numberformat-sections branch from b92d6c0 to fa890f6 Compare July 24, 2026 18:11
@marcin-kordas-hoc

Copy link
Copy Markdown
Collaborator Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit fa890f6. Configure here.

@marcin-kordas-hoc
marcin-kordas-hoc marked this pull request as ready for review July 25, 2026 10:54
marcin-kordas-hoc and others added 4 commits July 28, 2026 14:37
The public test/ directory holds smoke tests only; internal test suites live
in the hyperformula-tests repository. The spec moves there unchanged in
coverage, split into single-assertion cases:
handsontable/hyperformula-tests#27.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…cks (HF-287)

Color tags are removed from the format string before it reaches the
stringifyDateTime and stringifyDuration callbacks, so a custom callback no
longer sees them. That is client-visible and belongs in the entry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread src/format/format.ts Outdated
The file cites the reporting issue in 93 entries and a PR in 12, and HF-24's
stringifyCurrency entry in this same release already points at #1145. That issue
asked for two things — support for the $#,##0.00 format, or a stringifyCurrency
config option. HF-24 shipped the alternative; this change delivers the primary
ask, so the entry references the issue rather than the pull request.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@qunabu

qunabu commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Comment thread hyperformula/src/format/format.ts
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 7, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
hyperformula-docs eb272ea Commit Preview URL

Branch Preview URL
Oct 25 2026, 01:50 AM

Comment thread CHANGELOG.md Outdated
marcin-kordas-hoc and others added 3 commits August 28, 2026 12:36
This morning's develop merge landed the TEXT/numberFormat entry inside
the already-published 3.4.0 Fixed list, putting CHANGELOG.md out of sync
with docs/guide/release-notes.md for that released version. The fix is
unreleased, so the entry belongs under [Unreleased]; the 3.4.0 lists in
the two files match again. Same correction as 63e2137 on #1714.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…HF-287)

Two divergences measured against real Excel via the MS Graph workbook
API (2026-08-28):

- TEXT(-0.4,"0") rendered "-0" — the implicitly-prepended minus is now
  dropped when every rendered digit rounds to zero; explicit `-` mask
  literals and raw-sign section selection are unchanged.
- TEXT(1E+21,"#,##0") rendered JS scientific notation ("1e+21") — doubles
  >= 1e21 are always integer-valued, so they are expanded to their exact
  digits via BigInt before padding and grouping.

tsconfig lib gains es2020.bigint for the BigInt global — a types-only
change with no runtime or output impact (all supported browsers and Node
versions provide BigInt).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 975b72c. Configure here.

Comment thread CHANGELOG.md Outdated

- Fixed the MAXPOOL and MEDIANPOOL functions throwing an uncaught `TypeError` instead of returning the `#VALUE!` error when the range dimensions are not a whole multiple of the window size and the stride. [#1718](https://github.com/handsontable/hyperformula/pull/1718)
- Fixed the `MOD` function returning a remainder with the sign of the dividend instead of the sign of the divisor, which made the results differ from Excel and Google Sheets for arguments with opposite signs (e.g. `=MOD(-3, 12)` now returns `9` instead of `-3`). [#1747](https://github.com/handsontable/hyperformula/issues/1747)
- Fixed the `TEXT` function so that number-format masks with thousands grouping (`#,##0`) and positive/negative/zero sections (`0.00;(0.00)`) are formatted correctly instead of leaking the unparsed mask into the output. The built-in number formatter now also honors the configured `decimalSeparator` and `thousandSeparator` and ignores color tags such as `[Red]`, which are now removed from the format string before it reaches the `stringifyDateTime` and `stringifyDuration` callbacks. Matching Excel, a negative value whose rendered digits all round to zero is now displayed without the minus sign (`TEXT(-0.4, "0")` returns `0`), and magnitudes of `1e21` and above are expanded to their full digits instead of scientific notation. [#1145](https://github.com/handsontable/hyperformula/issues/1145)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CHANGELOG uses issue not PR link

Low Severity

The new Unreleased TEXT/numberFormat bullet ends with an issue URL for #1145 rather than a GitHub pull-request link. Unreleased changelog items need a pull/NNNN link; an issue-only reference does not count.

Fix in Cursor Fix in Web

Triggered by learned rule: CHANGELOG bullets need a PR link

Reviewed by Cursor Bugbot for commit 975b72c. Configure here.

@Tobiadefami Tobiadefami left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Grouping respects the configured separators, and positive, negative, and zero values select the expected format sections.

marcin-kordas-hoc and others added 3 commits October 22, 2026 15:22
… changelog entry

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…k without its color section

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.29%. Comparing base (d143456) to head (eb272ea).

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #1716      +/-   ##
===========================================
+ Coverage    97.28%   97.29%   +0.01%     
===========================================
  Files          207      207              
  Lines        16218    16289      +71     
  Branches      3556     3600      +44     
===========================================
+ Hits         15777    15848      +71     
  Misses         433      433              
  Partials         8        8              
Files with missing lines Coverage Δ
hyperformula/src/format/format.ts 99.55% <100.00%> (+0.20%) ⬆️
hyperformula/src/format/parser.ts 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants