Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for compact decimal formats #909

Merged
merged 13 commits into from Oct 31, 2022

Conversation

DenverCoder1
Copy link
Contributor

@DenverCoder1 DenverCoder1 commented Oct 17, 2022

Adds support for compact decimal formats.

Implementation based on details found at https://www.unicode.org/reports/tr35/tr35-45/tr35-numbers.html#Compact_Number_Formats

    >>> format_compact_decimal(12345, format_type="short", locale='en_US')
    u'12K'
    >>> format_compact_decimal(12345, format_type="long", locale='en_US')
    u'12 thousand'
    >>> format_compact_decimal(12345, format_type="short", locale='en_US', fraction_digits=2)
    u'12.35K'
    >>> format_compact_decimal(1234567, format_type="short", locale="ja_JP")
    u'123万'
    >>> format_compact_decimal(2345678, format_type="long", locale="mk")
    u'2 милиони'
    >>> format_compact_decimal(21098765, format_type="long", locale="mk")
    u'21 милион'

Closes #908

@jun66j5
Copy link
Contributor

jun66j5 commented Oct 17, 2022

If the given number is negative, the formatted text is not compact.

>>> numbers.format_decimal(123456789, compact='short', locale='en_US')
'123M'
>>> numbers.format_decimal(123456789, compact='short', locale='ja')
'1億'
>>> numbers.format_decimal(-123456789, compact='short', locale='en_US')
'-123,456,789'
>>> numbers.format_decimal(-123456789, compact='short', locale='ja')
'-123,456,789'

@jun66j5
Copy link
Contributor

jun66j5 commented Oct 17, 2022

Several locales have different formats between count="one" and count="other" (e.g. mk, ps, ...). I think the count="one" pattern should be handled.

>>> from babel.core import Locale
>>> from babel.localedata import locale_identifiers
>>> def show_different_formats():
...   for identifier in sorted(locale_identifiers()):
...     locale = Locale.parse(identifier)
...     fmts = locale.compact_decimal_formats['short']
...     if 'one' in fmts and 'other' in fmts:
...       for s in sorted(set(fmts['one']) & set(fmts['other'])):
...         fmt_one = fmts['one'][s]
...         fmt_other = fmts['other'][s]
...         if fmt_one.pattern != fmt_other.pattern:
...           print(identifier, repr(s), repr(fmt_one.pattern), repr(fmt_other.pattern))
...
>>> show_different_formats()
bn '10000000000' '00\xa0শত\xa0কো' '00শত\xa0কো'
bn_BD '10000000000' '00\xa0শত\xa0কো' '00শত\xa0কো'
bn_IN '10000000000' '00\xa0শত\xa0কো' '00শত\xa0কো'
mk '100000000000' "000\xa0мј'.'" "000\xa0ми'.'"
mk_MK '100000000000' "000\xa0мј'.'" "000\xa0ми'.'"
ps '100000000000' '000G' '000B'
ps_AF '100000000000' '000G' '000B'
ps_PK '100000000000' '000G' '000B'
sw '1000000' '0M;-0M' '0M'
sw '10000000' '00M;-00M' '00M'
sw '100000000' '000M;-000M' '000M'
sw '1000000000000' '0T;-0T' '0T'
sw '10000000000000' '00T;-00T' '00T'
sw '100000000000000' '000T;-000T' '000T'
sw_CD '1000000' '0M;-0M' '0M'
sw_CD '10000000' '00M;-00M' '00M'
sw_CD '100000000' '000M;-000M' '000M'
sw_CD '1000000000000' '0T;-0T' '0T'
sw_CD '10000000000000' '00T;-00T' '00T'
sw_CD '100000000000000' '000T;-000T' '000T'
sw_TZ '1000000' '0M;-0M' '0M'
sw_TZ '10000000' '00M;-00M' '00M'
sw_TZ '100000000' '000M;-000M' '000M'
sw_TZ '1000000000000' '0T;-0T' '0T'
sw_TZ '10000000000000' '00T;-00T' '00T'
sw_TZ '100000000000000' '000T;-000T' '000T'
sw_UG '1000000' '0M;-0M' '0M'
sw_UG '10000000' '00M;-00M' '00M'
sw_UG '100000000' '000M;-000M' '000M'
sw_UG '1000000000000' '0T;-0T' '0T'
sw_UG '10000000000000' '00T;-00T' '00T'
sw_UG '100000000000000' '000T;-000T' '000T'

@DenverCoder1
Copy link
Contributor Author

Several locales have different formats between count="one" and count="other" (e.g. mk, ps, ...). I think the count="one" pattern should be handled.

Unless I understood incorrectly, I believe it does already when number is 1 after dividing.

@DenverCoder1
Copy link
Contributor Author

In the latest commit, the parameter compact was renamed to format_type to make it more similar to the parameters to format_currency.

format_type can be None to use the format locale.decimal_formats[None], or "short" / "long" to use the respective compact decimal format.

(Note: compact format currencies are not implemented by this PR, they appear to partially implemented already and broken and can be corrected in a later PR).

@jun66j5
Copy link
Contributor

jun66j5 commented Oct 19, 2022

Unless I understood incorrectly, I believe it does already when number is 1 after dividing.

No. We should determine to use which "count" attribute using plural form of the locale.

>>> from babel.core import Locale
>>> mk = Locale.parse('mk')
>>> mk.plural_form
<PluralRule 'one: v in 0 and i mod 10 in 1 and i mod 100 not in 11 or f mod 10 in 1 and f mod 100 not in 11'>
>>> for n in (0, 1, 2, 11, 21, 100, 101):
...   n, mk.plural_form(n)
...
(0, 'other')
(1, 'one')
(2, 'other')
(11, 'other')
(21, 'one')
(100, 'other')
(101, 'one')

@DenverCoder1
Copy link
Contributor Author

DenverCoder1 commented Oct 19, 2022

Thanks, that actually makes a lot more sense with the examples. I believe it still should check after dividing, though, since the plural rule seems to only apply to the first few digits.

@jun66j5
Copy link
Contributor

jun66j5 commented Oct 22, 2022

I think we could add doc tests for the plural form like:

index 7e0ddd63a..fc2164183 100644
--- a/babel/numbers.py
+++ b/babel/numbers.py
@@ -417,6 +417,10 @@ def format_decimal(
     u'12.35K'
     >>> format_decimal(1234567, locale='ja_JP', format_type="short")
     u'123万'
+    >>> format_decimal(2345678, locale='mk', format_type='long')
+    u'2 милиони'
+    >>> format_decimal(21098765, locale='mk', format_type='long')
+    u'21 милион'

     :param number: the number to format
     :param format:

@DenverCoder1
Copy link
Contributor Author

I think we could add doc tests for the plural form

Added 👍

@codecov
Copy link

codecov bot commented Oct 31, 2022

Codecov Report

Merging #909 (6e768be) into master (cdc77b4) will decrease coverage by 0.31%.
The diff coverage is 100.00%.

❗ Current head 6e768be differs from pull request most recent head 6a29607. Consider uploading reports for the commit 6a29607 to get more accurate results

@@            Coverage Diff             @@
##           master     #909      +/-   ##
==========================================
- Coverage   91.33%   91.01%   -0.32%     
==========================================
  Files          21       21              
  Lines        4176     4198      +22     
==========================================
+ Hits         3814     3821       +7     
- Misses        362      377      +15     
Impacted Files Coverage Δ
babel/core.py 96.37% <100.00%> (+0.03%) ⬆️
babel/numbers.py 98.12% <100.00%> (+0.12%) ⬆️
babel/localtime/_win32.py 21.56% <0.00%> (-39.22%) ⬇️
babel/localtime/__init__.py 61.53% <0.00%> (-2.57%) ⬇️
babel/messages/frontend.py 87.17% <0.00%> (-0.75%) ⬇️
babel/localedata.py 95.86% <0.00%> (ø)
babel/messages/jslexer.py 87.00% <0.00%> (+0.33%) ⬆️
babel/localtime/_unix.py 24.13% <0.00%> (+0.52%) ⬆️

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

Copy link
Member

@akx akx left a comment

Choose a reason for hiding this comment

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

Thank you for getting to work on this! The code looks mainly great, just a couple thoughts here and there, and maybe we should discuss whether to overload the format_decimal API further.

(Also thanks, @jun66j5, for thoroughly reviewing this earlier!)

babel/numbers.py Outdated Show resolved Hide resolved
babel/numbers.py Outdated Show resolved Hide resolved
tests/test_numbers.py Show resolved Hide resolved
babel/numbers.py Outdated Show resolved Hide resolved
DenverCoder1 and others added 3 commits October 31, 2022 07:58
@DenverCoder1 DenverCoder1 requested a review from akx October 31, 2022 15:00
Copy link
Member

@akx akx left a comment

Choose a reason for hiding this comment

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

Looks quite excellent! Just a couple of tiny comments to go.

babel/numbers.py Outdated Show resolved Hide resolved
tests/test_numbers.py Show resolved Hide resolved
babel/numbers.py Outdated Show resolved Hide resolved
Copy link
Member

@akx akx left a comment

Choose a reason for hiding this comment

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

Great, thank you!

@akx akx merged commit 05df10f into python-babel:master Oct 31, 2022
@DenverCoder1 DenverCoder1 deleted the compact-decimal branch October 31, 2022 15:29
bors bot added a commit to microsoft/Qcodes that referenced this pull request Nov 2, 2022
4784: Update babel requirement from ~=2.10.3 to ~=2.11.0 r=jenshnielsen a=dependabot[bot]

Updates the requirements on [babel](https://github.com/python-babel/babel) to permit the latest version.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/python-babel/babel/releases">babel's releases</a>.</em></p>
<blockquote>
<h2>Version 2.11.0</h2>
<p>The below release notes are auto-generated; please see <code>CHANGES.rst</code> for a human-crafted one.</p>
<p>Thank you to all contributors!</p>
<h2>What's Changed</h2>
<ul>
<li>tests: Use bare asserts and pytest.raises by <a href="https://github.com/akx"><code>`@​akx</code></a>` in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/875">python-babel/babel#875</a></li>
<li>Use email.Message for pofile header parsing by <a href="https://github.com/akx"><code>`@​akx</code></a>` in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/876">python-babel/babel#876</a></li>
<li>Become 2.10.2 by <a href="https://github.com/akx"><code>`@​akx</code></a>` in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/884">python-babel/babel#884</a></li>
<li>Build packages in CI by <a href="https://github.com/akx"><code>`@​akx</code></a>` in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/888">python-babel/babel#888</a></li>
<li>Become 2.10.3 by <a href="https://github.com/akx"><code>`@​akx</code></a>` in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/889">python-babel/babel#889</a></li>
<li>Small downloader improvements by <a href="https://github.com/akx"><code>`@​akx</code></a>` in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/894">python-babel/babel#894</a></li>
<li>Update Python versions in dev docs by <a href="https://github.com/rnestler"><code>`@​rnestler</code></a>` in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/898">python-babel/babel#898</a></li>
<li>Remove <code>__nonzero__</code> methods by <a href="https://github.com/sobolevn"><code>`@​sobolevn</code></a>` in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/896">python-babel/babel#896</a></li>
<li>Remove superfluous <code>__unicode__</code> declarations by <a href="https://github.com/lukasjuhrich"><code>`@​lukasjuhrich</code></a>` in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/905">python-babel/babel#905</a></li>
<li>align license with OSI template by <a href="https://github.com/lsmith77"><code>`@​lsmith77</code></a>` in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/912">python-babel/babel#912</a></li>
<li>Support for hex escapes in JavaScript strings by <a href="https://github.com/codepainters"><code>`@​codepainters</code></a>` in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/877">python-babel/babel#877</a></li>
<li>Remove determining time zone via systemsetup on macOS by <a href="https://github.com/akx"><code>`@​akx</code></a>` in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/914">python-babel/babel#914</a></li>
<li>Add support for compact decimal formats by <a href="https://github.com/DenverCoder1"><code>`@​DenverCoder1</code></a>` in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/909">python-babel/babel#909</a></li>
<li>Adapt parse_date to handle ISO dates by <a href="https://github.com/ericzolf"><code>`@​ericzolf</code></a>` in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/842">python-babel/babel#842</a></li>
<li>Use <code>ast</code> instead of <code>eval</code> for string extraction by <a href="https://github.com/akx"><code>`@​akx</code></a>` in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/915">python-babel/babel#915</a></li>
<li>Python 3.11 compatibility by <a href="https://github.com/akx"><code>`@​akx</code></a>` in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/913">python-babel/babel#913</a></li>
<li>Quiesce pytest warnings by <a href="https://github.com/akx"><code>`@​akx</code></a>` in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/916">python-babel/babel#916</a></li>
<li>Become 2.11.0 by <a href="https://github.com/akx"><code>`@​akx</code></a>` in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/917">python-babel/babel#917</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/rnestler"><code>`@​rnestler</code></a>` made their first contribution in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/898">python-babel/babel#898</a></li>
<li><a href="https://github.com/sobolevn"><code>`@​sobolevn</code></a>` made their first contribution in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/896">python-babel/babel#896</a></li>
<li><a href="https://github.com/lukasjuhrich"><code>`@​lukasjuhrich</code></a>` made their first contribution in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/905">python-babel/babel#905</a></li>
<li><a href="https://github.com/lsmith77"><code>`@​lsmith77</code></a>` made their first contribution in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/912">python-babel/babel#912</a></li>
<li><a href="https://github.com/codepainters"><code>`@​codepainters</code></a>` made their first contribution in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/877">python-babel/babel#877</a></li>
<li><a href="https://github.com/DenverCoder1"><code>`@​DenverCoder1</code></a>` made their first contribution in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/909">python-babel/babel#909</a></li>
<li><a href="https://github.com/ericzolf"><code>`@​ericzolf</code></a>` made their first contribution in <a href="https://github-redirect.dependabot.com/python-babel/babel/pull/842">python-babel/babel#842</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/python-babel/babel/compare/v2.10.3...v2.11.0">https://github.com/python-babel/babel/compare/v2.10.3...v2.11.0</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/python-babel/babel/blob/master/CHANGES.rst">babel's changelog</a>.</em></p>
<blockquote>
<h2>Version 2.11.0</h2>
<p>Upcoming deprecation</p>
<pre><code>
* This version, Babel 2.11, is the last version of Babel to support Python 3.6.
  Babel 2.12 will require Python 3.7 or newer.
<p>Improvements</p>
<pre><code>
* Support for hex escapes in JavaScript string literals :gh:`[#877](python-babel/babel#877 - Przemyslaw Wegrzyn
* Add support for formatting decimals in compact form :gh:`[#909](python-babel/babel#909 - Jonah Lawrence
* Adapt parse_date to handle ISO dates in ASCII format :gh:`[#842](python-babel/babel#842 - Eric L.
* Use `ast` instead of `eval` for Python string extraction :gh:`[#915](python-babel/babel#915 - Aarni Koskela
    * This also enables extraction from static f-strings.
      F-strings with expressions are silently ignored (but won't raise an error as they used to).

Infrastructure
</code></pre>
<ul>
<li>Tests: Use regular asserts and <code>pytest.raises()</code> :gh:<code>[#875](https://github.com/python-babel/babel/issues/875)</code> – Aarni Koskela</li>
<li>Wheels are now built in GitHub Actions :gh:<code>[#888](https://github.com/python-babel/babel/issues/888)</code> – Aarni Koskela</li>
<li>Small improvements to the CLDR downloader script :gh:<code>[#894](https://github.com/python-babel/babel/issues/894)</code> – Aarni Koskela</li>
<li>Remove antiquated <code>__nonzero__</code> methods :gh:<code>[#896](https://github.com/python-babel/babel/issues/896)</code> - Nikita Sobolev</li>
<li>Remove superfluous <code>__unicode__</code> declarations :gh:<code>[#905](https://github.com/python-babel/babel/issues/905)</code> - Lukas Juhrich</li>
<li>Mark package compatible with Python 3.11 :gh:<code>[#913](https://github.com/python-babel/babel/issues/913)</code> - Aarni Koskela</li>
<li>Quiesce pytest warnings :gh:<code>[#916](https://github.com/python-babel/babel/issues/916)</code> - Aarni Koskela</li>
</ul>
<p>Bugfixes</p>
<pre><code>
* Use email.Message for pofile header parsing instead of the deprecated ``cgi.parse_header`` function. :gh:`[#876](python-babel/babel#876 – Aarni Koskela
* Remove determining time zone via systemsetup on macOS :gh:`[#914](python-babel/babel#914 - Aarni Koskela

Documentation
</code></pre>
<ul>
<li>Update Python versions in documentation :gh:<code>[#898](https://github.com/python-babel/babel/issues/898)</code> - Raphael Nestler</li>
<li>Align BSD-3 license with OSI template :gh:<code>[#912](https://github.com/python-babel/babel/issues/912)</code> - Lukas Kahwe Smith</li>
</ul>
<h2>Version 2.10.3</h2>
<p>This is a bugfix release for Babel 2.10.2, which was mistakenly packaged with outdated locale data.</p>
<p>Thanks to Michał Górny for pointing this out and Jun Omae for verifying.</p>
<p>This and future Babel PyPI packages will be built by a more automated process,
&lt;/tr&gt;&lt;/table&gt;
</code></pre></p>
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/python-babel/babel/commit/a30d7cff7ed2df689e9539ef07104e8ea20445a9"><code>a30d7cf</code></a> Become 2.11.0</li>
<li><a href="https://github.com/python-babel/babel/commit/bc9dc84876bed120c67c73bb6fcd2d8045198090"><code>bc9dc84</code></a> Quiesce pytest warnings (<a href="https://github-redirect.dependabot.com/python-babel/babel/issues/916">#916</a>)</li>
<li><a href="https://github.com/python-babel/babel/commit/52b83d3630c50944e84d066808b7ec2b0a259e4d"><code>52b83d3</code></a> Setup: mark as compatible with Python 3.11</li>
<li><a href="https://github.com/python-babel/babel/commit/f117ead7d512ef3c32b68db953a193c21245ad14"><code>f117ead</code></a> CI: test on Python 3.11</li>
<li><a href="https://github.com/python-babel/babel/commit/2e5708f8f231696afcbea3f5803ca43b1e33bf7f"><code>2e5708f</code></a> Use <code>ast</code> instead of <code>eval</code> for string extraction</li>
<li><a href="https://github.com/python-babel/babel/commit/a946ae6bad2701a021969d82b550ba6be1f5c7d7"><code>a946ae6</code></a> Adapt parse_date to handle ISO dates in ASCII format</li>
<li><a href="https://github.com/python-babel/babel/commit/05df10fd1474e929793183c3b0ffa28251df79eb"><code>05df10f</code></a> Add support for compact decimal formats (<a href="https://github-redirect.dependabot.com/python-babel/babel/issues/909">#909</a>)</li>
<li><a href="https://github.com/python-babel/babel/commit/03c8fae835c16d63fa5f4e3141074abb8bad3e83"><code>03c8fae</code></a> Remove determining time zone via systemsetup on macOS</li>
<li><a href="https://github.com/python-babel/babel/commit/c7d04e8cb7bd60ff305d8bc6df2ce8a3f92c0223"><code>c7d04e8</code></a> Support for hex escapes in JavaScript string literals</li>
<li><a href="https://github.com/python-babel/babel/commit/8f5757cc85402a46c1db722f08c18cfa7f119858"><code>8f5757c</code></a> align license with OSI template (<a href="https://github-redirect.dependabot.com/python-babel/babel/issues/912">#912</a>)</li>
<li>Additional commits viewable in <a href="https://github.com/python-babel/babel/compare/v2.10.3...v2.11.0">compare view</a></li>
</ul>
</details>
<br />


You can trigger a rebase of this PR by commenting ``@dependabot` rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- ``@dependabot` rebase` will rebase this PR
- ``@dependabot` recreate` will recreate this PR, overwriting any edits that have been made to it
- ``@dependabot` merge` will merge this PR after your CI passes on it
- ``@dependabot` squash and merge` will squash and merge this PR after your CI passes on it
- ``@dependabot` cancel merge` will cancel a previously requested merge and block automerging
- ``@dependabot` reopen` will reopen this PR if it is closed
- ``@dependabot` close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- ``@dependabot` ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- ``@dependabot` ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- ``@dependabot` ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Is it possible to use the short number formats from CLDR?
3 participants