function is a string representing a version number, e.g. '47'.
>>> get_cldr_version()
- '47'
+ '48'
.. versionadded:: 2.18
>>> format_currency(1099.98, 'JPY', locale='en_US')
'\\xa51,100'
>>> format_currency(1099.98, 'COP', '#,##0.00', locale='es_ES')
- '1.099,98'
+ '1.100'
However, the number of decimal digits can be overridden from the currency
information, by setting the last parameter to ``False``:
from __future__ import annotations
import decimal
+import warnings
from typing import Literal
from babel.core import Locale
from babel.numbers import LC_NUMERIC, format_decimal
+_DEPRECATED_UNIT_IDS: dict[str, str] = {
+ # Unit IDs deprecated in CLDR 48
+ "concentr-permillion": "concentr-part-per-1e6",
+ "concentr-portion": "concentr-part",
+ "concentr-portion-per-1e9": "concentr-part-per-1e9",
+ "permillion": "part-per-1e6",
+ "portion": "part",
+ "portion-per-1e9": "part-per-1e9",
+}
+
class UnknownUnitError(ValueError):
def __init__(self, unit: str, locale: Locale) -> None:
unit_patterns: dict[str, str] = locale._data["unit_patterns"]
if unit_id in unit_patterns:
return unit_id
+
+ # Only if a direct hit didn't work, try deprecated units...
+ new_id = _DEPRECATED_UNIT_IDS.get(unit_id)
+ if new_id is not None:
+ warnings.warn(
+ f"Unit identifier {unit_id!r} is deprecated; use {new_id!r} instead.",
+ DeprecationWarning,
+ stacklevel=4,
+ )
+ if new_id in unit_patterns:
+ return new_id
+
+ # ... and finally fuzzy matching.
for unit_pattern in sorted(unit_patterns, key=len):
if unit_pattern.endswith(unit_id):
return unit_pattern
def test_get_cldr_version():
- assert core.get_cldr_version() == "47"
+ assert core.get_cldr_version() == "48"
def test_format_skeleton(timezone_getter):
dt = datetime(2007, 4, 1, 15, 30)
assert (dates.format_skeleton('yMEd', dt, locale='en_US') == 'Sun, 4/1/2007')
- assert (dates.format_skeleton('yMEd', dt, locale='th') == 'อา. 1/4/2007')
+ assert (dates.format_skeleton('yMEd', dt, locale='th') == 'อาทิตย์ 1/4/2007')
assert (dates.format_skeleton('EHm', dt, locale='en') == 'Sun 15:30')
- assert (dates.format_skeleton('EHm', dt, tzinfo=timezone_getter('Asia/Bangkok'), locale='th') == 'อา. 22:30 น.')
+ assert (dates.format_skeleton('EHm', dt, tzinfo=timezone_getter('Asia/Bangkok'), locale='th') == 'อาทิตย์ 22:30 น.')
@pytest.mark.parametrize(('skeleton', 'expected'), [
# The actual returned value here is not actually strictly specified ("get_timezone_name"
# is not an operation specified as such). Issue #1192 concerned this invocation returning
# the invalid "no inheritance marker" value; _that_ should never be returned here.
- # IOW, if the below "Hawaii-Aleutian Time" changes with e.g. CLDR updates, that's fine.
- assert dates.get_timezone_name('Pacific/Honolulu', 'short', locale='en_GB') == "Hawaii-Aleutian Time"
+ # IOW, if the below "United States (Honolulu) Time" changes with e.g. CLDR updates, that's fine.
+ assert dates.get_timezone_name('Pacific/Honolulu', 'short', locale='en_GB') == "United States (Honolulu) Time"
@pytest.mark.xfail
(['string1', 'string2'], 'en', 'string1 and string2'),
(['string1', 'string2', 'string3'], 'en', 'string1, string2, and string3'),
(['string1', 'string2', 'string3'], 'zh', 'string1、string2和string3'),
- (['string1', 'string2', 'string3', 'string4'], 'ne', 'string1,string2, string3 र string4'),
+ (['string1', 'string2', 'string3', 'string4'], 'ne', 'string1, string2, string3 र string4'),
])
def test_format_list(list, locale, expected):
assert lists.format_list(list, locale=locale) == expected
assert list_currencies(locale='pa_Arab') == {'PKR', 'INR', 'EUR'}
- assert len(list_currencies()) == 307
+ assert len(list_currencies()) == 308
def test_validate_currency():
assert (numbers.format_currency(1099.98, 'JPY', locale='en_US')
== '\xa51,100')
assert (numbers.format_currency(1099.98, 'COP', '#,##0.00', locale='es_ES')
- == '1.099,98')
+ == '1.100')
assert (numbers.format_currency(1099.98, 'JPY', locale='en_US',
currency_digits=False)
== '\xa51,099.98')
assert numbers.format_compact_currency(123, 'EUR', locale='yav', format_type="short") == '€\xa0123'
assert numbers.format_compact_currency(12345, 'EUR', locale='yav', format_type="short") == '€\xa012K'
assert numbers.format_compact_currency(123456789, 'EUR', locale='de_DE', fraction_digits=1) == '123,5\xa0Mio.\xa0€'
- assert numbers.format_compact_currency(123456789, 'USD', locale='ar_EG', fraction_digits=2, format_type="short", numbering_system="default") == '123٫46\xa0مليون\xa0US$'
+ assert numbers.format_compact_currency(123456789, 'USD', locale='ar_EG', fraction_digits=2, format_type="short", numbering_system="default") == '\u200f123٫46\xa0مليون\xa0US$'
def test_format_compact_currency_invalid_format_type():
def test_format_compact_currency(ar_eg_format, en_us_format):
assert en_us_format.compact_currency(1099.98, 'USD') == '$1K'
assert en_us_format.compact_currency(Decimal("1099.98"), 'USD') == '$1K'
- assert ar_eg_format.compact_currency(1099.98, 'EGP') == '1\xa0ألف\xa0ج.م.\u200f'
+ assert ar_eg_format.compact_currency(1099.98, 'EGP') == '\u200f1\xa0ألف\xa0ج.م.\u200f'
def test_format_percent(ar_eg_format, en_us_format):
('speed-light-speed', 1, '1 světlo'),
('speed-light-speed', 2, '2 světla'),
('speed-light-speed', 5, '5 světel'),
- ('concentr-portion-per-1e9', 1, '1 částice na miliardu'),
- ('concentr-portion-per-1e9', 2, '2 částice na miliardu'),
- ('concentr-portion-per-1e9', 5, '5 částic na miliardu'),
+ ('concentr-part-per-1e9', 1, '1 částice na miliardu'),
+ ('concentr-part-per-1e9', 2, '2 částice na miliardu'),
+ ('concentr-part-per-1e9', 5, '5 částic na miliardu'),
('duration-night', 1, '1 noc'),
('duration-night', 2, '2 noci'),
('duration-night', 5, '5 nocí'),
])
def test_issue_1217(count, unit, locale, length, expected):
assert format_unit(count, unit, length, locale=locale) == expected
+
+
+def test_deprecated_unit_ids():
+ for id in ("concentr-permillion", "concentr-portion", "concentr-portion-per-1e9"):
+ with pytest.warns(DeprecationWarning, match=id):
+ format_unit(1, id, locale='en')