From cbb7e9ebc79319af74cb27a485e8e1efd4fe1d2f Mon Sep 17 00:00:00 2001 From: James Emerton Date: Wed, 1 Aug 2018 20:26:43 -0700 Subject: [PATCH 1/4] Implement support for locale specific decimal format Adds support for 'l' and 'L' which will format a string as per 'f' except that they will use locale specific grouping, separators, and decimal point. In the case of 'L' the LC_MONETARY values will be used. --- Lib/_pydecimal.py | 22 +++++++++----- Lib/test/test_decimal.py | 53 ++++++++++++++++++++++++++++++++++ Modules/_decimal/libmpdec/io.c | 31 ++++++++++++++++---- 3 files changed, 93 insertions(+), 13 deletions(-) diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py index 359690003fe160..6f5ca1c47e4396 100644 --- a/Lib/_pydecimal.py +++ b/Lib/_pydecimal.py @@ -6154,7 +6154,7 @@ def _convert_for_comparison(self, other, equality_op=False): (?P(?!0)\d+)? (?P,)? (?:\.(?P0|(?!0)\d+))? -(?P[eEfFgGn%])? +(?P[eEfFgGnLl%])? \Z """, re.VERBOSE|re.DOTALL) @@ -6184,7 +6184,7 @@ def _parse_format_specifier(format_spec, _localeconv=None): used by localeconv decimal_point: string to use for decimal point precision: nonnegative integer giving precision, or None - type: one of the characters 'eEfFgG%', or None + type: one of the characters 'eEfFgGlL%', or None """ m = _parse_format_specifier_regex.match(format_spec) @@ -6229,17 +6229,23 @@ def _parse_format_specifier(format_spec, _localeconv=None): # determine thousands separator, grouping, and decimal separator, and # add appropriate entries to format_dict - if format_dict['type'] == 'n': + if format_dict['type'] and format_dict['type'] in 'nNlL': # apart from separators, 'n' behaves just like 'g' - format_dict['type'] = 'g' if _localeconv is None: _localeconv = _locale.localeconv() if format_dict['thousands_sep'] is not None: raise ValueError("Explicit thousands separator conflicts with " - "'n' type in format specifier: " + format_spec) - format_dict['thousands_sep'] = _localeconv['thousands_sep'] - format_dict['grouping'] = _localeconv['grouping'] - format_dict['decimal_point'] = _localeconv['decimal_point'] + "type in format specifier: " + format_spec) + if format_dict['type'] == 'L': + format_dict['thousands_sep'] = _localeconv['mon_thousands_sep'] + format_dict['grouping'] = _localeconv['mon_grouping'] + format_dict['decimal_point'] = _localeconv['mon_decimal_point'] + else: + format_dict['thousands_sep'] = _localeconv['thousands_sep'] + format_dict['grouping'] = _localeconv['grouping'] + format_dict['decimal_point'] = _localeconv['decimal_point'] + format_dict['type'] = 'f' if format_dict['type'] in 'lL' else 'g' + else: if format_dict['thousands_sep'] is None: format_dict['thousands_sep'] = '' diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 58bde54b0a88a1..be487a806956ec 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -1164,6 +1164,59 @@ def get_fmt(x, override=None, fmt='n'): self.assertEqual(get_fmt(Decimal('-1.5'), dotsep_wide, '020n'), '-0\u00b4000\u00b4000\u00b4000\u00b4001\u00bf5') + @run_with_locale('LC_ALL', 'en_US') + def test_l_format_us(self): + Decimal = self.decimal.Decimal + def get_fmt(x, fmt='l'): + return Decimal(x).__format__(fmt) + + self.assertEqual(get_fmt(Decimal('12.7')), '12.7') + self.assertEqual(get_fmt(123456789), '123,456,789') + self.assertEqual(get_fmt(123456789, '.6l'), '123,456,789.000000') + + self.assertEqual(get_fmt(12345, '05l'), '12,345') + self.assertEqual(get_fmt(12345, '06l'), '12,345') + self.assertEqual(get_fmt(12345, '07l'), '012,345') + self.assertEqual(get_fmt(12345, '08l'), '0,012,345') + self.assertEqual(get_fmt(12345, '09l'), '0,012,345') + self.assertEqual(get_fmt(12345, '010l'), '00,012,345') + + @run_with_locale('LC_ALL', 'fr_FR') + def test_l_format_fr(self): + Decimal = self.decimal.Decimal + def get_fmt(x, fmt='l'): + return Decimal(x).__format__(fmt) + + self.assertEqual(get_fmt(Decimal('12.7')), '12,7') + self.assertEqual(get_fmt(123456789), '123456789') + self.assertEqual(get_fmt(123456789, '.6l'), '123456789,000000') + + # zero padding + self.assertEqual(get_fmt(1234, '03l'), '1234') + self.assertEqual(get_fmt(1234, '04l'), '1234') + self.assertEqual(get_fmt(1234, '05l'), '01234') + self.assertEqual(get_fmt(1234, '06l'), '001234') + + @run_with_locale('LC_ALL', 'ru_RU') + def test_l_format_ru(self): + Decimal = self.decimal.Decimal + def get_fmt(x, fmt='l'): + return Decimal(x).__format__(fmt) + + self.assertEqual(get_fmt(Decimal('12.7')), '12,7') + self.assertEqual(get_fmt(123456789), '123 456 789') + self.assertEqual(get_fmt(123456789, '.6l'), '123 456 789,000000') + + @run_with_locale('LC_ALL', 'pt_PT') + def test_L_format(self): + # locale that differentiates between monetary and non-monetary formats + Decimal = self.decimal.Decimal + def get_fmt(x, fmt='l'): + return Decimal(x).__format__(fmt) + + self.assertEqual(get_fmt('1234567.89', 'l'), '1234567,89') + self.assertEqual(get_fmt('1234567.89', 'L'), '1.234.567.89') + @run_with_locale('LC_ALL', 'ps_AF') def test_wide_char_separator_decimal_point(self): # locale with wide char separator and decimal point diff --git a/Modules/_decimal/libmpdec/io.c b/Modules/_decimal/libmpdec/io.c index f45e558f1a9573..fa5bfc7b87fd88 100644 --- a/Modules/_decimal/libmpdec/io.c +++ b/Modules/_decimal/libmpdec/io.c @@ -864,7 +864,7 @@ mpd_parse_fmt_str(mpd_spec_t *spec, const char *fmt, int caps) *cp == 'G' || *cp == 'g' || *cp == '%') { spec->type = *cp++; } - else if (*cp == 'N' || *cp == 'n') { + else if (*cp == 'N' || *cp == 'n' || *cp == 'L' || *cp == 'l') { /* locale specific conversion */ struct lconv *lc; /* separator has already been specified */ @@ -872,11 +872,32 @@ mpd_parse_fmt_str(mpd_spec_t *spec, const char *fmt, int caps) return 0; } spec->type = *cp++; - spec->type = (spec->type == 'N') ? 'G' : 'g'; + lc = localeconv(); - spec->dot = lc->decimal_point; - spec->sep = lc->thousands_sep; - spec->grouping = lc->grouping; + if(spec->type == 'L') { + spec->dot = lc->mon_decimal_point; + spec->sep = lc->mon_thousands_sep; + spec->grouping = lc->mon_grouping; + } + else { + spec->dot = lc->decimal_point; + spec->sep = lc->thousands_sep; + spec->grouping = lc->grouping; + } + + switch(spec->type) { + case 'N': + spec->type = 'G'; + break; + case 'n': + spec->type = 'g'; + break; + case 'L': + case 'l': + spec->type = 'f'; + break; + } + if (mpd_validate_lconv(spec) < 0) { return 0; /* GCOV_NOT_REACHED */ } From 0927901e179c8bc215a24e0255cb1fd3e8cf3d48 Mon Sep 17 00:00:00 2001 From: James Emerton Date: Sat, 4 Aug 2018 13:59:07 -0700 Subject: [PATCH 2/4] Improve Decimal format tests Compare the output of the 'l' and 'L' formats against the output of `locale.format_string()`. The locale data seems to differ between platforms, so testing against literals is challenging. (Other format tests are explicitly providing locale data, but that approach would fail to properly test my modifications.) Also added a test against literals for the en_US locale in the hopes that it's consistent across platforms. --- Lib/test/test_decimal.py | 84 +++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 49 deletions(-) diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index be487a806956ec..a2c8c8321f3313 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -1164,58 +1164,44 @@ def get_fmt(x, override=None, fmt='n'): self.assertEqual(get_fmt(Decimal('-1.5'), dotsep_wide, '020n'), '-0\u00b4000\u00b4000\u00b4000\u00b4001\u00bf5') - @run_with_locale('LC_ALL', 'en_US') - def test_l_format_us(self): - Decimal = self.decimal.Decimal - def get_fmt(x, fmt='l'): - return Decimal(x).__format__(fmt) - - self.assertEqual(get_fmt(Decimal('12.7')), '12.7') - self.assertEqual(get_fmt(123456789), '123,456,789') - self.assertEqual(get_fmt(123456789, '.6l'), '123,456,789.000000') + @run_with_locale('LC_ALL', 'en_US', 'fr_FR', 'ru_RU', 'pt_PT') + def test_l_format(self): + # Due to locale differences between platforms, test thet these + # formats are consistent with locale.format_string + Decimal = self.decimal.Decimal + + for val in ['12.7', + '123456789', + '12.3456', + '12345', + ]: + val = Decimal(val) + # NB: The 'f' format will display 6 digits right of the decimal + # place for floats but will respect the exponent of a Decimal + # which is why this test must be explicit about decimal places + self.assertEqual(val.__format__('.0l'), + locale.format_string('%.0f', val, True, False)) + self.assertEqual(val.__format__('.0L'), + locale.format_string('%.0f', val, True, True)) + + self.assertEqual(val.__format__('.3l'), + locale.format_string('%.3f', val, True, False)) + self.assertEqual(val.__format__('.3L'), + locale.format_string('%.3f', val, True, True)) + + self.assertEqual(val.__format__('04.1l'), + locale.format_string('%04.1f', val, True, False)) + self.assertEqual(val.__format__('04.1L'), + locale.format_string('%04.1f', val, True, True)) - self.assertEqual(get_fmt(12345, '05l'), '12,345') - self.assertEqual(get_fmt(12345, '06l'), '12,345') - self.assertEqual(get_fmt(12345, '07l'), '012,345') - self.assertEqual(get_fmt(12345, '08l'), '0,012,345') - self.assertEqual(get_fmt(12345, '09l'), '0,012,345') - self.assertEqual(get_fmt(12345, '010l'), '00,012,345') - @run_with_locale('LC_ALL', 'fr_FR') - def test_l_format_fr(self): - Decimal = self.decimal.Decimal - def get_fmt(x, fmt='l'): - return Decimal(x).__format__(fmt) - - self.assertEqual(get_fmt(Decimal('12.7')), '12,7') - self.assertEqual(get_fmt(123456789), '123456789') - self.assertEqual(get_fmt(123456789, '.6l'), '123456789,000000') - - # zero padding - self.assertEqual(get_fmt(1234, '03l'), '1234') - self.assertEqual(get_fmt(1234, '04l'), '1234') - self.assertEqual(get_fmt(1234, '05l'), '01234') - self.assertEqual(get_fmt(1234, '06l'), '001234') - - @run_with_locale('LC_ALL', 'ru_RU') - def test_l_format_ru(self): - Decimal = self.decimal.Decimal - def get_fmt(x, fmt='l'): - return Decimal(x).__format__(fmt) - - self.assertEqual(get_fmt(Decimal('12.7')), '12,7') - self.assertEqual(get_fmt(123456789), '123 456 789') - self.assertEqual(get_fmt(123456789, '.6l'), '123 456 789,000000') - - @run_with_locale('LC_ALL', 'pt_PT') - def test_L_format(self): - # locale that differentiates between monetary and non-monetary formats + @run_with_locale('LC_ALL', 'en_US') + def test_l_format_us(self): Decimal = self.decimal.Decimal - def get_fmt(x, fmt='l'): - return Decimal(x).__format__(fmt) - - self.assertEqual(get_fmt('1234567.89', 'l'), '1234567,89') - self.assertEqual(get_fmt('1234567.89', 'L'), '1.234.567.89') + self.assertEqual(Decimal('12.7').__format__('l'), '12.7') + self.assertEqual(Decimal('123456789').__format__('l'), '123,456,789') + self.assertEqual(Decimal('123456789.00').__format__('l'), + '123,456,789.00') @run_with_locale('LC_ALL', 'ps_AF') def test_wide_char_separator_decimal_point(self): From 79b9b83feb77f18222ba7a68fb91c2ba0917e797 Mon Sep 17 00:00:00 2001 From: James Emerton Date: Sat, 4 Aug 2018 16:51:31 -0700 Subject: [PATCH 3/4] Reverting Decimal format changes Upon further discussion and research it appears that we should be supporting grouping via the `'` modifier as per C99 --- Lib/_pydecimal.py | 22 +++++++------------ Lib/test/test_decimal.py | 39 ---------------------------------- Modules/_decimal/libmpdec/io.c | 31 +++++---------------------- 3 files changed, 13 insertions(+), 79 deletions(-) diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py index 6f5ca1c47e4396..359690003fe160 100644 --- a/Lib/_pydecimal.py +++ b/Lib/_pydecimal.py @@ -6154,7 +6154,7 @@ def _convert_for_comparison(self, other, equality_op=False): (?P(?!0)\d+)? (?P,)? (?:\.(?P0|(?!0)\d+))? -(?P[eEfFgGnLl%])? +(?P[eEfFgGn%])? \Z """, re.VERBOSE|re.DOTALL) @@ -6184,7 +6184,7 @@ def _parse_format_specifier(format_spec, _localeconv=None): used by localeconv decimal_point: string to use for decimal point precision: nonnegative integer giving precision, or None - type: one of the characters 'eEfFgGlL%', or None + type: one of the characters 'eEfFgG%', or None """ m = _parse_format_specifier_regex.match(format_spec) @@ -6229,23 +6229,17 @@ def _parse_format_specifier(format_spec, _localeconv=None): # determine thousands separator, grouping, and decimal separator, and # add appropriate entries to format_dict - if format_dict['type'] and format_dict['type'] in 'nNlL': + if format_dict['type'] == 'n': # apart from separators, 'n' behaves just like 'g' + format_dict['type'] = 'g' if _localeconv is None: _localeconv = _locale.localeconv() if format_dict['thousands_sep'] is not None: raise ValueError("Explicit thousands separator conflicts with " - "type in format specifier: " + format_spec) - if format_dict['type'] == 'L': - format_dict['thousands_sep'] = _localeconv['mon_thousands_sep'] - format_dict['grouping'] = _localeconv['mon_grouping'] - format_dict['decimal_point'] = _localeconv['mon_decimal_point'] - else: - format_dict['thousands_sep'] = _localeconv['thousands_sep'] - format_dict['grouping'] = _localeconv['grouping'] - format_dict['decimal_point'] = _localeconv['decimal_point'] - format_dict['type'] = 'f' if format_dict['type'] in 'lL' else 'g' - + "'n' type in format specifier: " + format_spec) + format_dict['thousands_sep'] = _localeconv['thousands_sep'] + format_dict['grouping'] = _localeconv['grouping'] + format_dict['decimal_point'] = _localeconv['decimal_point'] else: if format_dict['thousands_sep'] is None: format_dict['thousands_sep'] = '' diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index a2c8c8321f3313..58bde54b0a88a1 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -1164,45 +1164,6 @@ def get_fmt(x, override=None, fmt='n'): self.assertEqual(get_fmt(Decimal('-1.5'), dotsep_wide, '020n'), '-0\u00b4000\u00b4000\u00b4000\u00b4001\u00bf5') - @run_with_locale('LC_ALL', 'en_US', 'fr_FR', 'ru_RU', 'pt_PT') - def test_l_format(self): - # Due to locale differences between platforms, test thet these - # formats are consistent with locale.format_string - Decimal = self.decimal.Decimal - - for val in ['12.7', - '123456789', - '12.3456', - '12345', - ]: - val = Decimal(val) - # NB: The 'f' format will display 6 digits right of the decimal - # place for floats but will respect the exponent of a Decimal - # which is why this test must be explicit about decimal places - self.assertEqual(val.__format__('.0l'), - locale.format_string('%.0f', val, True, False)) - self.assertEqual(val.__format__('.0L'), - locale.format_string('%.0f', val, True, True)) - - self.assertEqual(val.__format__('.3l'), - locale.format_string('%.3f', val, True, False)) - self.assertEqual(val.__format__('.3L'), - locale.format_string('%.3f', val, True, True)) - - self.assertEqual(val.__format__('04.1l'), - locale.format_string('%04.1f', val, True, False)) - self.assertEqual(val.__format__('04.1L'), - locale.format_string('%04.1f', val, True, True)) - - - @run_with_locale('LC_ALL', 'en_US') - def test_l_format_us(self): - Decimal = self.decimal.Decimal - self.assertEqual(Decimal('12.7').__format__('l'), '12.7') - self.assertEqual(Decimal('123456789').__format__('l'), '123,456,789') - self.assertEqual(Decimal('123456789.00').__format__('l'), - '123,456,789.00') - @run_with_locale('LC_ALL', 'ps_AF') def test_wide_char_separator_decimal_point(self): # locale with wide char separator and decimal point diff --git a/Modules/_decimal/libmpdec/io.c b/Modules/_decimal/libmpdec/io.c index fa5bfc7b87fd88..f45e558f1a9573 100644 --- a/Modules/_decimal/libmpdec/io.c +++ b/Modules/_decimal/libmpdec/io.c @@ -864,7 +864,7 @@ mpd_parse_fmt_str(mpd_spec_t *spec, const char *fmt, int caps) *cp == 'G' || *cp == 'g' || *cp == '%') { spec->type = *cp++; } - else if (*cp == 'N' || *cp == 'n' || *cp == 'L' || *cp == 'l') { + else if (*cp == 'N' || *cp == 'n') { /* locale specific conversion */ struct lconv *lc; /* separator has already been specified */ @@ -872,32 +872,11 @@ mpd_parse_fmt_str(mpd_spec_t *spec, const char *fmt, int caps) return 0; } spec->type = *cp++; - + spec->type = (spec->type == 'N') ? 'G' : 'g'; lc = localeconv(); - if(spec->type == 'L') { - spec->dot = lc->mon_decimal_point; - spec->sep = lc->mon_thousands_sep; - spec->grouping = lc->mon_grouping; - } - else { - spec->dot = lc->decimal_point; - spec->sep = lc->thousands_sep; - spec->grouping = lc->grouping; - } - - switch(spec->type) { - case 'N': - spec->type = 'G'; - break; - case 'n': - spec->type = 'g'; - break; - case 'L': - case 'l': - spec->type = 'f'; - break; - } - + spec->dot = lc->decimal_point; + spec->sep = lc->thousands_sep; + spec->grouping = lc->grouping; if (mpd_validate_lconv(spec) < 0) { return 0; /* GCOV_NOT_REACHED */ } From 6cb606483156becc0a4e7500943c0f54ae8bff0e Mon Sep 17 00:00:00 2001 From: James Emerton Date: Sat, 4 Aug 2018 17:57:34 -0700 Subject: [PATCH 4/4] bpo-33731: Add locale modifier to Decimal.__format__ This implements the `'` modifier in place of the thousands separator to enable locale specific grouping and decimal point. --- Lib/_pydecimal.py | 12 +++++++++--- Lib/test/test_decimal.py | 36 +++++++++++++++++++++++++++++++++- Modules/_decimal/libmpdec/io.c | 9 +++++++++ 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py index 359690003fe160..6c0177d08bf846 100644 --- a/Lib/_pydecimal.py +++ b/Lib/_pydecimal.py @@ -6152,7 +6152,7 @@ def _convert_for_comparison(self, other, equality_op=False): (?P\#)? (?P0)? (?P(?!0)\d+)? -(?P,)? +(?P[,'])? (?:\.(?P0|(?!0)\d+))? (?P[eEfFgGn%])? \Z @@ -6241,10 +6241,16 @@ def _parse_format_specifier(format_spec, _localeconv=None): format_dict['grouping'] = _localeconv['grouping'] format_dict['decimal_point'] = _localeconv['decimal_point'] else: - if format_dict['thousands_sep'] is None: - format_dict['thousands_sep'] = '' format_dict['grouping'] = [3, 0] format_dict['decimal_point'] = '.' + if format_dict['thousands_sep'] is None: + format_dict['thousands_sep'] = '' + elif format_dict['thousands_sep'] == "'": + if _localeconv is None: + _localeconv = _locale.localeconv() + format_dict['thousands_sep'] = _localeconv['thousands_sep'] + format_dict['grouping'] = _localeconv['grouping'] + format_dict['decimal_point'] = _localeconv['decimal_point'] return format_dict diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 58bde54b0a88a1..1977ae7dd33813 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -1075,7 +1075,7 @@ def test_formatting(self): # bytes format argument self.assertRaises(TypeError, Decimal(1).__format__, b'-020') - def test_n_format(self): + def test_locale_format(self): Decimal = self.decimal.Decimal try: @@ -1164,6 +1164,40 @@ def get_fmt(x, override=None, fmt='n'): self.assertEqual(get_fmt(Decimal('-1.5'), dotsep_wide, '020n'), '-0\u00b4000\u00b4000\u00b4000\u00b4001\u00bf5') + # locale grouping modifier + self.assertEqual(get_fmt(123456789, en_US, "'f"), '123,456,789') + self.assertEqual(get_fmt(123456789, fr_FR, "'f"), '123456789') + self.assertEqual(get_fmt(123456789, ru_RU, "'f"), '123 456 789') + self.assertEqual(get_fmt(1234567890123, crazy, "'f"), + '123456-78-9012-3') + + self.assertEqual(get_fmt('123456789.123', en_US, "'.2f"), + '123,456,789.12') + self.assertEqual(get_fmt('123456789.123', fr_FR, "'.2f"), + '123456789,12') + self.assertEqual(get_fmt('123456789.123', ru_RU, "'.2f"), + '123 456 789,12') + self.assertEqual(get_fmt('1234567890123.123', crazy, "'.2f"), + '123456-78-9012-3&12') + + self.assertEqual(get_fmt(123456789, en_US, "'.6e"), '1.234568e+8') + self.assertEqual(get_fmt(123456789, fr_FR, "'.6e"), '1,234568e+8') + self.assertEqual(get_fmt(123456789, ru_RU, "'.6e"), '1,234568e+8') + self.assertEqual(get_fmt(123456789, crazy, "'.6e"), '1&234568e+8') + + self.assertEqual(get_fmt(123456789, en_US, "'.6g"), '1.23457e+8') + self.assertEqual(get_fmt(123456789, fr_FR, "'.6g"), '1,23457e+8') + self.assertEqual(get_fmt(123456789, ru_RU, "'.6g"), '1,23457e+8') + self.assertEqual(get_fmt(123456789, crazy, "'.6g"), '1&23457e+8') + + self.assertEqual(get_fmt(12345, en_US, "05'g"), '12,345') + self.assertEqual(get_fmt(12345, en_US, "06'g"), '12,345') + self.assertEqual(get_fmt(12345, en_US, "07'g"), '012,345') + self.assertEqual(get_fmt(12345, en_US, "08'g"), '0,012,345') + self.assertEqual(get_fmt(12345, en_US, "09'g"), '0,012,345') + self.assertEqual(get_fmt(12345, en_US, "010'g"), '00,012,345') + + @run_with_locale('LC_ALL', 'ps_AF') def test_wide_char_separator_decimal_point(self): # locale with wide char separator and decimal point diff --git a/Modules/_decimal/libmpdec/io.c b/Modules/_decimal/libmpdec/io.c index f45e558f1a9573..e86bc8ef552623 100644 --- a/Modules/_decimal/libmpdec/io.c +++ b/Modules/_decimal/libmpdec/io.c @@ -846,6 +846,15 @@ mpd_parse_fmt_str(mpd_spec_t *spec, const char *fmt, int caps) cp++; } + /* locale specific thousands separator */ + if (*cp == '\'') { + struct lconv *lc = localeconv(); + spec->dot = lc->decimal_point; + spec->sep = lc->thousands_sep; + spec->grouping = lc->grouping; + cp++; + } + /* fraction digits or significant digits */ if (*cp == '.') { cp++;