From 8c46e47ecf5574a7ea1eebcf930a53ff3c705644 Mon Sep 17 00:00:00 2001 From: Ulrich Telle Date: Thu, 9 Oct 2025 23:16:22 +0200 Subject: [PATCH] Support more aspects in wxUILocale and wxNumberFormatter Add support for currency amounts, including functions for retrieving currency symbol and currency code for the current locale and the possibility to format such amounts according to the locale rules. Allow retrieving more information about numeric values formatting, for both numbers and currency amounts, including grouping information when group separator is used and number of digits after decimal separator and also use them when formatting numbers. Finally add function to retrieve the measurement system (metric or imperial) used by the current locale. See #1781. Closes #25765. --- include/wx/localedefs.h | 49 ++++ include/wx/numformatter.h | 11 +- include/wx/private/uilocale.h | 8 + include/wx/uilocale.h | 21 ++ interface/wx/numformatter.h | 54 ++++ interface/wx/uilocale.h | 100 +++++++ src/common/numformatter.cpp | 175 +++++++++++- src/common/uilocale.cpp | 64 +++++ src/msw/uilocale.cpp | 128 ++++++++- src/osx/core/uilocale.mm | 143 +++++++++- src/unix/uilocale.cpp | 275 +++++++++++++++++- tests/intl/intltest.cpp | 32 ++- tests/strings/numformatter.cpp | 504 +++++++++++++++++++++++++++++++++ 13 files changed, 1534 insertions(+), 30 deletions(-) diff --git a/include/wx/localedefs.h b/include/wx/localedefs.h index a1c98f2b5c..c89dd56044 100644 --- a/include/wx/localedefs.h +++ b/include/wx/localedefs.h @@ -12,6 +12,8 @@ #include "wx/defs.h" +#include + // ---------------------------------------------------------------------------- // wxLayoutDirection: used by wxWindow, wxDC etc // ---------------------------------------------------------------------------- @@ -122,6 +124,53 @@ enum wxLocaleForm wxLOCALE_FORM_ENGLISH }; +enum class wxMeasurementSystem +{ + Unknown, + Metric, + NonMetric +}; + +enum class wxCurrencySymbolPosition +{ + PrefixNoSep, + PrefixWithSep, + SuffixNoSep, + SuffixWithSep +}; + +struct wxLocaleNumberFormatting +{ + wxLocaleNumberFormatting() = default; + wxLocaleNumberFormatting(const wxString& groupSeparator_, const std::vector& grouping_, + const wxString& decimalSeparator_, int fractionalDigits_) + : groupSeparator(groupSeparator_), grouping(grouping_), + decimalSeparator(decimalSeparator_), fractionalDigits(fractionalDigits_) + { + } + wxString groupSeparator; + std::vector grouping; + wxString decimalSeparator; + int fractionalDigits = 0; +}; + +struct wxLocaleCurrencyInfo +{ + wxLocaleCurrencyInfo() = default; + wxLocaleCurrencyInfo(const wxString& symbol_, const wxString& code_, + const wxCurrencySymbolPosition currencySymbolPos_, + const wxLocaleNumberFormatting currencyFormat_) + : currencySymbol(symbol_), currencyCode(code_), + currencySymbolPos(currencySymbolPos_), + currencyFormat(currencyFormat_) + { + } + wxString currencySymbol; // the currency symbol (for example "$") + wxString currencyCode; // the currency ISO code (for example "USD") + wxCurrencySymbolPosition currencySymbolPos = wxCurrencySymbolPosition::PrefixWithSep; + wxLocaleNumberFormatting currencyFormat; +}; + // ---------------------------------------------------------------------------- // wxLanguageInfo: encapsulates wxLanguage to OS native lang.desc. // translation information diff --git a/include/wx/numformatter.h b/include/wx/numformatter.h index da499361a4..1ff6d92641 100644 --- a/include/wx/numformatter.h +++ b/include/wx/numformatter.h @@ -25,6 +25,9 @@ public: Style_NoTrailingZeroes = 0x02, // Only for floating point numbers Style_SignPlus = 0x04, Style_SignSpace = 0x08, + Style_Currency = 0x10, // Currency, without currency symbol + Style_CurrencySymbol = 0x20, // Currency with currency symbol + Style_CurrencyCode = 0x40, // Currency with ISO 4217 code }; // Format a number as a string. By default, the thousands separator is @@ -75,18 +78,24 @@ public: // number. Also used by ToString(). static void RemoveTrailingZeroes(wxString& s); + // Remove currency symbol or code + static wxString RemoveCurrencySymbolOrCode(wxString s, int style); + private: // Post-process the string representing an integer. static wxString PostProcessIntString(wxString s, int style); // Add the thousands separators to a string representing a number without // the separators. This is used by ToString(Style_WithThousandsSep). - static void AddThousandsSeparators(wxString& s); + static void AddThousandsSeparators(wxString& s, int style); // Add the sign prefix to a string representing a number without // the prefix. This is used by ToString(). static void AddSignPrefix(wxString& s, int style); + // Add currency symbol or code depending on style + static void AddCurrency(wxString& s, int style); + // Remove all thousands separators from a string representing a number. static void RemoveThousandsSeparators(wxString& s); }; diff --git a/include/wx/private/uilocale.h b/include/wx/private/uilocale.h index d2d0eb8996..915ffec437 100644 --- a/include/wx/private/uilocale.h +++ b/include/wx/private/uilocale.h @@ -96,6 +96,14 @@ public: #endif // wxUSE_DATETIME virtual wxLayoutDirection GetLayoutDirection() const = 0; + + virtual wxLocaleNumberFormatting GetNumberFormatting() const = 0; + virtual wxString GetCurrencySymbol() const = 0; + virtual wxString GetCurrencyCode() const = 0; + virtual wxCurrencySymbolPosition GetCurrencySymbolPosition() const = 0; + virtual wxLocaleCurrencyInfo GetCurrencyInfo() const = 0; + virtual wxMeasurementSystem UsesMetricSystem() const = 0; + virtual int CompareStrings(const wxString& lhs, const wxString& rhs, int flags) const = 0; diff --git a/include/wx/uilocale.h b/include/wx/uilocale.h index e516ad5340..f37bc8aaaf 100644 --- a/include/wx/uilocale.h +++ b/include/wx/uilocale.h @@ -186,6 +186,27 @@ public: // Query the layout direction of the current locale. wxLayoutDirection GetLayoutDirection() const; + // Query infos about number formatting of the current locale + wxLocaleNumberFormatting GetNumberFormatting() const; + + // Query the curreny symbol of the current locale + wxString GetCurrencySymbol() const; + + // Query the currency code of the current locale + wxString GetCurrencyCode() const; + + // Query the currency symbol position of the current locale + wxCurrencySymbolPosition GetCurrencySymbolPosition() const; + + // Query the currency infos of the current locale + wxLocaleCurrencyInfo GetCurrencyInfo() const; + + // Query whether the current locale uses the metric system + wxMeasurementSystem UsesMetricSystem() const; + + // Guess from the region whether the current locale uses the metric system + static wxMeasurementSystem GuessMetricSystemFromRegion(const wxLocaleIdent& idLocale); + // Compares two strings in the order defined by this locale. int CompareStrings(const wxString& lhs, const wxString& rhs, int flags = wxCompare_CaseSensitive) const; diff --git a/interface/wx/numformatter.h b/interface/wx/numformatter.h index dea3514923..8e0848c740 100644 --- a/interface/wx/numformatter.h +++ b/interface/wx/numformatter.h @@ -37,6 +37,8 @@ public: /** If this flag is given, thousands separators will be inserted in the number string representation as defined by the current UI locale. + Thousands separators will be applied according to the grouping rules + of the current UI locale. */ Style_WithThousandsSep = 0x01, @@ -74,6 +76,40 @@ public: @since 3.3.1 */ Style_SignSpace = 0x08, + + /** + If this flag is given, the number will be interpreted + as a currency value and formatted according to the rules + of the current UI locale. + + @since 3.3.2 + */ + Style_Currency = 0x10, + + /** + If this flag is given, the currency symbol of the current + UI locale will be prepended or appended to the currency value + according to the rules of the current locale. + + The style will only take effect, if it is combined with Style_Currency. + The style should not be combined with Style_CurrencyCode. + + @since 3.3.2 + */ + Style_CurrencySymbol = 0x20, // Currency with currency symbol + + /** + If this flag is given, the ISO 42127 currency code of the current + UI locale will be prepended to the currency value. It will be separated + from the currency value by a space character. + + The style will only take effect, if it is combined with Style_Currency. + The style should not be combined with Style_CurrencySymbol. + + @since 3.3.2 + */ + Style_CurrencyCode = 0x40, // Currency with ISO 4217 code + }; /** @@ -180,4 +216,22 @@ public: */ static void RemoveTrailingZeroes(wxString& str); + /** + Remove currency symbol or code, and grouping separators, + according to the given style flags. + + @note This function allows to remove the currency symbol or code + from a string with a formatted currency value, so that + FromString() can be used afterwards to retrieve the numerical value. + Additionally, grouping separators are removed, if necessary. + + @param[in] str + The string to remove the currency symbol or code or grouping separators from. + @param flags + Combination of values from the Style enumeration. + + @since 3.3.2 + */ + static wxString RemoveCurrencySymbolOrCode(wxString str, int flags); + }; diff --git a/interface/wx/uilocale.h b/interface/wx/uilocale.h index aca01bfe75..de90f71dd4 100644 --- a/interface/wx/uilocale.h +++ b/interface/wx/uilocale.h @@ -296,6 +296,106 @@ public: */ wxLayoutDirection GetLayoutDirection() const; + /** + Return all settings related to number formatting for the current locale. + The information includes: + - the grouping separator + - the grouping specification + - the decimal separator + - the number of fractional digits + + @return + The wxLocaleNumberFormatting structure with the number formatting details. + @since 3.3.2 + */ + wxLocaleNumberFormatting GetNumberFormatting() const; + + /** + Query the currency symbol of the current locale. + + @return + The currency symbol that is typically used locally for currency values. + If no currency symbol could be determined, an empty string will be returned. + @since 3.3.2 + */ + wxString GetCurrencySymbol() const; + + /** + Query the ISO 4217 currency code of the current locale. + + @return + The 3-letter ISO 4217 currency code. + If no currency code could be determined, an empty string will be returned. + @since 3.3.2 + */ + wxString GetCurrencyCode() const; + + /** + Query the currency symbol position of the current locale. + + The currency symbol can be positioned in one of 4 ways: + as a prefix or suffix to the currency value, either separated from the value + by a space character or not. Accordingly, one of the following values is returned: + - wxCurrencySymbolPosition::PrefixWithSep + - wxCurrencySymbolPosition::PrefixNoSep + - wxCurrencySymbolPosition::SuffixWithSep + - wxCurrencySymbolPosition::SuffixNoSep + + @note The position of the currency symbol does not affect the representation + with the currency code. The currency code is always placed before the + currency value and separated by a space. + + @return + The currency symbol position. + @since 3.3.2 + */ + wxCurrencySymbolPosition GetCurrencySymbolPosition() const; + + /** + Return all settings related to currency formatting for the current locale. + + The currency information includes the following items: + - the currency symbol + - the currency code + - the currency symbol position + - the currency value formatting information + + @return + The wxLocaleCurrencyInfo structure. + @since 3.3.2 + */ + wxLocaleCurrencyInfo GetCurrencyInfo() const; + + /** + Query whether the current locale uses the metric system + + @note If wxMeasurementSystem::Unknown is returned, + GuessMetricSystemFromRegion() can be used as a fallback + + @return + The measurement system, one of the values + wxMeasurementSystem::Metric, wxMeasurementSystem::NonMetric, + or wxMeasurementSystem::Unknown. + @since 3.3.2 + + @see GuessMetricSystemFromRegion() + */ + wxMeasurementSystem UsesMetricSystem() const; + + /** + Guess whether the current locale uses the metric system + base on from the region part of the locale identification. + + @param idLocale + The id of the locale for which the measurement system should be guessed. + @return + The guessed measurement system, one of the values + wxMeasurementSystem::Metric, wxMeasurementSystem::NonMetric, + or wxMeasurementSystem::Unknown. + @since 3.3.2 + */ + static wxMeasurementSystem GuessMetricSystemFromRegion(const wxLocaleIdent& idLocale); + /** Return true if locale is supported on the current system. diff --git a/src/common/numformatter.cpp b/src/common/numformatter.cpp index da19ede437..9f4e62f54b 100644 --- a/src/common/numformatter.cpp +++ b/src/common/numformatter.cpp @@ -83,13 +83,15 @@ void ReplaceSeparatorIfNecessary(wxString& s, wxChar sepOld, wxChar sepNew) wxString wxNumberFormatter::PostProcessIntString(wxString s, int style) { if ( style & Style_WithThousandsSep ) - AddThousandsSeparators(s); + AddThousandsSeparators(s, style); wxASSERT_MSG( !(style & Style_NoTrailingZeroes), "Style_NoTrailingZeroes can't be used with integer values" ); AddSignPrefix(s, style); + AddCurrency(s, style); + return s; } @@ -121,13 +123,15 @@ wxString wxNumberFormatter::ToString(double val, int precision, int style) ReplaceSeparatorIfNecessary(s, '.', GetDecimalSeparator()); if ( style & Style_WithThousandsSep ) - AddThousandsSeparators(s); + AddThousandsSeparators(s, style); if ( style & Style_NoTrailingZeroes ) RemoveTrailingZeroes(s); AddSignPrefix(s, style); + AddCurrency(s, style); + return s; } @@ -148,17 +152,33 @@ wxString wxNumberFormatter::Format(const wxString& format, double val) return s; } -void wxNumberFormatter::AddThousandsSeparators(wxString& s) +void wxNumberFormatter::AddThousandsSeparators(wxString& s, int style) { // Thousands separators for numbers in scientific format are not relevant. if ( s.find_first_of("eE") != wxString::npos ) return; - wxChar thousandsSep; - if ( !GetThousandsSeparatorIfUsed(&thousandsSep) ) + wxString groupingSeparator; + std::vector grouping; + wxString decimalSeparator = GetDecimalSeparator(); +#if wxUSE_INTL + wxLocaleNumberFormatting numForm; + if (style & Style_Currency) + numForm = wxUILocale::GetCurrent().GetCurrencyInfo().currencyFormat; + else + numForm = wxUILocale::GetCurrent().GetNumberFormatting(); + if (!numForm.groupSeparator.empty()) + { + groupingSeparator = numForm.groupSeparator; + grouping = numForm.grouping; + decimalSeparator = numForm.decimalSeparator; + } +#endif // wxUSE_INTL + + if (groupingSeparator.empty() ) return; - size_t pos = s.find(GetDecimalSeparator()); + size_t pos = s.find(decimalSeparator); if ( pos == wxString::npos ) { // Start grouping at the end of an integer number. @@ -169,17 +189,47 @@ void wxNumberFormatter::AddThousandsSeparators(wxString& s) // before their start. const size_t start = s.find_first_of("0123456789"); - // We currently group digits by 3 independently of the locale. This is not - // the right thing to do and we should use lconv::grouping (under POSIX) - // and GetLocaleInfo(LOCALE_SGROUPING) (under MSW) to get information about - // the correct grouping to use. This is something that needs to be done at - // wxLocale level first and then used here in the future (TODO). - const size_t GROUP_LEN = 3; + if (grouping.empty()) + return; - while ( pos > start + GROUP_LEN ) + // The vector grouping conatins a list of group length. + // Beginning from the right, each group length is applied once + // to determine the next position of a group separator, + // unless the last entry entry is a zero, in which case + // the last group length is applied repeatedly. + size_t groupIndex = 0; + size_t groupLen = grouping[0]; + size_t nextGroupLen = groupLen; + + // Repeat while the group length is valid (i.e. > 0) + // and the potential group separator position lies + // within the number. + while (groupLen > 0 && pos > start) { - pos -= GROUP_LEN; - s.insert(pos, thousandsSep); + // Determine next group separator position + pos = (pos >= groupLen) ? pos - groupLen : 0; + + // Apply group separator if it is within the number + if (pos > start) + s.insert(pos, groupingSeparator); + + // Select next group length + if (++groupIndex < grouping.size()) + { + // Set the group length to the next group length entry + // unless the next group length is zero, + // in which case the last group length remains in effect. + nextGroupLen = grouping[groupIndex]; + if (nextGroupLen != 0) + groupLen = nextGroupLen; + } + else if (nextGroupLen != 0) + { + // If the next group length is not zero, + // the last group length was already applied once, and + // no further group is to be applied. + groupLen = 0; + } } } @@ -232,6 +282,101 @@ void wxNumberFormatter::AddSignPrefix(wxString& s, int style) } } +void wxNumberFormatter::AddCurrency(wxString& s, int style) +{ +#if wxUSE_INTL + if (style & Style_Currency) + { + auto currencyInfo = wxUILocale::GetCurrent().GetCurrencyInfo(); + wxString currencyStr; + wxCurrencySymbolPosition currencyPos{ wxCurrencySymbolPosition::PrefixWithSep }; + if (style & Style_CurrencySymbol) + { + currencyStr = currencyInfo.currencySymbol; + currencyPos = currencyInfo.currencySymbolPos; + } + else if (style & Style_CurrencyCode) + { + currencyStr = currencyInfo.currencyCode; + } + + if (!currencyStr.empty()) + { + switch (currencyPos) + { + case wxCurrencySymbolPosition::PrefixWithSep: + s = currencyStr + wxString(" ") + s; + break; + case wxCurrencySymbolPosition::PrefixNoSep: + s = currencyStr + s; + break; + case wxCurrencySymbolPosition::SuffixWithSep: + s = s + wxString(" ") + currencyStr; + break; + case wxCurrencySymbolPosition::SuffixNoSep: + s = s + currencyStr; + break; + } + } + } +#else + wxUnused(s); + wxUnused(style); +#endif // wxUSE_INTL +} + +wxString wxNumberFormatter::RemoveCurrencySymbolOrCode(wxString s, int style) +{ +#if wxUSE_INTL + if (style & Style_Currency) + { + auto currencyInfo = wxUILocale::GetCurrent().GetCurrencyInfo(); + wxString thousandsSep = currencyInfo.currencyFormat.groupSeparator; + wxString currencyStr; + wxCurrencySymbolPosition currencyPos{ wxCurrencySymbolPosition::PrefixWithSep }; + if (style & Style_CurrencySymbol) + { + currencyStr = currencyInfo.currencySymbol; + currencyPos = currencyInfo.currencySymbolPos; + } + else if (style & Style_CurrencyCode) + { + currencyStr = currencyInfo.currencyCode; + } + + if (!currencyStr.empty()) + { + wxString valueStr; + switch (currencyPos) + { + case wxCurrencySymbolPosition::PrefixWithSep: + currencyStr += wxString(" "); + // Fall through to case without separator + case wxCurrencySymbolPosition::PrefixNoSep: + if (s.StartsWith(currencyStr, &valueStr)) + s = valueStr; + break; + case wxCurrencySymbolPosition::SuffixWithSep: + currencyStr = wxString(" ") + currencyStr; + // Fall through to case without separator + case wxCurrencySymbolPosition::SuffixNoSep: + if (s.EndsWith(currencyStr, &valueStr)) + s = valueStr; + break; + } + } + if (style & Style_WithThousandsSep && !thousandsSep.empty()) + { + s.Replace(thousandsSep, wxString()); + } + } + return s; +#else + wxUnused(style); + return s; +#endif // wxUSE_INTL +} + // ---------------------------------------------------------------------------- // Conversion from strings // ---------------------------------------------------------------------------- diff --git a/src/common/uilocale.cpp b/src/common/uilocale.cpp index e0560b8004..68e7cece4c 100644 --- a/src/common/uilocale.cpp +++ b/src/common/uilocale.cpp @@ -994,6 +994,70 @@ wxLayoutDirection wxUILocale::GetLayoutDirection() const return dir; } +wxLocaleNumberFormatting wxUILocale::GetNumberFormatting() const +{ + if (!m_impl) + return wxLocaleNumberFormatting(); + + return m_impl->GetNumberFormatting(); +} + +wxString wxUILocale::GetCurrencySymbol() const +{ + if (!m_impl) + return wxString(); + + return m_impl->GetCurrencySymbol(); +} + +wxString wxUILocale::GetCurrencyCode() const +{ + if (!m_impl) + return wxString(); + + return m_impl->GetCurrencyCode(); +} + +wxCurrencySymbolPosition wxUILocale::GetCurrencySymbolPosition() const +{ + if (!m_impl) + return wxCurrencySymbolPosition::PrefixWithSep; + + return m_impl->GetCurrencySymbolPosition(); +} + +wxLocaleCurrencyInfo wxUILocale::GetCurrencyInfo() const +{ + if (!m_impl) + return wxLocaleCurrencyInfo(); + + return m_impl->GetCurrencyInfo(); +} + +wxMeasurementSystem wxUILocale::UsesMetricSystem() const +{ + if (!m_impl) + return wxMeasurementSystem::Unknown; + + return m_impl->UsesMetricSystem(); +} + +wxMeasurementSystem wxUILocale::GuessMetricSystemFromRegion(const wxLocaleIdent& idLocale) +{ + wxString region = idLocale.GetRegion(); + // In 2025 only in the United States, Liberia, and Myanmar + // the metric system is not the default measurement system. + if (!region.empty()) + { + if (region == "US" || region == "LR" || region == "MM") + return wxMeasurementSystem::NonMetric; + else + return wxMeasurementSystem::Metric; + } + else + return wxMeasurementSystem::Unknown; +} + int wxUILocale::CompareStrings(const wxString& lhs, const wxString& rhs, diff --git a/src/msw/uilocale.cpp b/src/msw/uilocale.cpp index c02d5a9912..fab0c64512 100644 --- a/src/msw/uilocale.cpp +++ b/src/msw/uilocale.cpp @@ -28,8 +28,12 @@ #include "wx/scopedarray.h" #include "wx/dynlib.h" +#include "wx/tokenzr.h" #include "wx/wxcrt.h" +#include +#include + #ifndef LOCALE_NAME_USER_DEFAULT #define LOCALE_NAME_USER_DEFAULT nullptr #endif @@ -304,6 +308,45 @@ public: return wxLayout_Default; } + wxLocaleNumberFormatting GetNumberFormatting() const override + { + wxLocaleNumberFormatting numForm; + numForm.decimalSeparator = "."; + numForm.groupSeparator = ""; + numForm.grouping = {}; + numForm.fractionalDigits = 2; + return numForm; + } + + wxString GetCurrencySymbol() const override + { + return "$"; + } + + wxString GetCurrencyCode() const override + { + return "USD"; + } + + wxCurrencySymbolPosition GetCurrencySymbolPosition() const override + { + return wxCurrencySymbolPosition::PrefixWithSep; + } + + wxLocaleCurrencyInfo GetCurrencyInfo() const override + { + return wxLocaleCurrencyInfo( + GetCurrencySymbol(), + GetCurrencyCode(), + GetCurrencySymbolPosition(), + GetNumberFormatting()); + } + + wxMeasurementSystem UsesMetricSystem() const override + { + return wxMeasurementSystem::Metric; + } + int CompareStrings(const wxString& lhs, const wxString& rhs, int flags) const override { @@ -472,13 +515,15 @@ public: switch ( index ) { case wxLOCALE_THOUSANDS_SEP: - str = DoGetInfo(LOCALE_STHOUSAND); + str = DoGetInfo(cat == wxLOCALE_CAT_MONEY + ? LOCALE_SMONTHOUSANDSEP + : LOCALE_STHOUSAND); break; case wxLOCALE_DECIMAL_POINT: str = DoGetInfo(cat == wxLOCALE_CAT_MONEY - ? LOCALE_SMONDECIMALSEP - : LOCALE_SDECIMAL); + ? LOCALE_SMONDECIMALSEP + : LOCALE_SDECIMAL); break; case wxLOCALE_SHORT_DATE_FMT: @@ -642,6 +687,53 @@ public: return m_layoutDir; } + wxLocaleNumberFormatting GetNumberFormatting() const override + { + return DoGetNumberFormatting(wxLOCALE_CAT_NUMBER); + } + + wxString GetCurrencySymbol() const override + { + return DoGetInfo(LOCALE_SCURRENCY); + } + + wxString GetCurrencyCode() const override + { + return wxString(DoGetInfo(LOCALE_SINTLSYMBOL)).Left(3); + } + + wxCurrencySymbolPosition GetCurrencySymbolPosition() const override + { + static std::array symPos = { + wxCurrencySymbolPosition::PrefixNoSep, wxCurrencySymbolPosition::SuffixNoSep, + wxCurrencySymbolPosition::PrefixWithSep, wxCurrencySymbolPosition::SuffixWithSep }; + wxString posStr = wxString(DoGetInfo(LOCALE_ICURRENCY)); + unsigned int posIdx; + return posStr.ToUInt(&posIdx) && posIdx < symPos.size() + ? symPos[posIdx] + : wxCurrencySymbolPosition::PrefixWithSep; + } + + wxLocaleCurrencyInfo GetCurrencyInfo() const override + { + wxLocaleNumberFormatting currencyFormatting = DoGetNumberFormatting(wxLOCALE_CAT_MONEY); + return wxLocaleCurrencyInfo( + GetCurrencySymbol(), + GetCurrencyCode(), + GetCurrencySymbolPosition(), + currencyFormatting); + } + + wxMeasurementSystem UsesMetricSystem() const override + { + wxString str = DoGetInfo(LOCALE_IMEASURE); + if (!str.empty()) + { + return (str.IsSameAs("0")) ? wxMeasurementSystem::Metric : wxMeasurementSystem::NonMetric; + } + return wxMeasurementSystem::Unknown; + } + int CompareStrings(const wxString& lhs, const wxString& rhs, int flags) const override { @@ -696,6 +788,36 @@ private: return buf; } + wxLocaleNumberFormatting DoGetNumberFormatting(wxLocaleCategory cat) const + { + wxString groupSeparator = DoGetInfo(cat == wxLOCALE_CAT_MONEY + ? LOCALE_SMONTHOUSANDSEP + : LOCALE_STHOUSAND); + wxString groupingInfo = DoGetInfo(cat == wxLOCALE_CAT_MONEY + ? LOCALE_SMONGROUPING + : LOCALE_SGROUPING); + wxString decimalSeparator = DoGetInfo(cat == wxLOCALE_CAT_MONEY + ? LOCALE_SMONDECIMALSEP + : LOCALE_SDECIMAL); + wxString digits = DoGetInfo(cat == wxLOCALE_CAT_MONEY + ? LOCALE_ICURRDIGITS + : LOCALE_IDIGITS); + int fractionalDigits; + if (digits.empty() || !digits.ToInt(&fractionalDigits)) + fractionalDigits = 0; + + // Extract grouping lengths from groupingInfo + std::vector grouping; + for (wxStringTokenizer tokenizer(groupingInfo, ";"); tokenizer.HasMoreTokens();) + { + int value = 0; + if (tokenizer.GetNextToken().ToInt(&value)) + grouping.push_back(value); + } + + return wxLocaleNumberFormatting(groupSeparator, grouping, decimalSeparator, fractionalDigits); + } + const wchar_t* const m_name; mutable wxLayoutDirection m_layoutDir = wxLayout_Default; diff --git a/src/osx/core/uilocale.mm b/src/osx/core/uilocale.mm index fccd218aa3..8fa0e137f7 100644 --- a/src/osx/core/uilocale.mm +++ b/src/osx/core/uilocale.mm @@ -27,13 +27,12 @@ #include "wx/osx/core/cfref.h" #include "wx/osx/core/cfstring.h" -#import -#import -#import -#import +#import #include "wx/osx/private/uilocale.h" +#include + extern wxString wxGetInfoFromCFLocale(CFLocaleRef cfloc, wxLocaleInfo index, wxLocaleCategory cat); @@ -165,10 +164,20 @@ public: #endif // wxUSE_DATETIME wxLayoutDirection GetLayoutDirection() const override; + + wxLocaleNumberFormatting GetNumberFormatting() const override; + wxString GetCurrencySymbol() const override; + wxString GetCurrencyCode() const override; + wxCurrencySymbolPosition GetCurrencySymbolPosition() const override; + wxLocaleCurrencyInfo GetCurrencyInfo() const override; + wxMeasurementSystem UsesMetricSystem() const override; + int CompareStrings(const wxString& lhs, const wxString& rhs, int flags) const override; private: + wxLocaleNumberFormatting DoGetNumberFormatting(wxLocaleCategory cat) const; + NSLocale* const m_nsloc; wxDECLARE_NO_COPY_CLASS(wxUILocaleImplCF); @@ -349,6 +358,132 @@ wxUILocaleImplCF::GetLayoutDirection() const return wxLayout_Default; } +wxLocaleNumberFormatting +wxUILocaleImplCF::GetNumberFormatting() const +{ + return DoGetNumberFormatting(wxLOCALE_CAT_NUMBER); +} + +wxString +wxUILocaleImplCF::GetCurrencySymbol() const +{ + NSString* str = [m_nsloc currencySymbol]; + return wxCFStringRef::AsString(str); +} + +wxString +wxUILocaleImplCF::GetCurrencyCode() const +{ + NSString* str = [m_nsloc currencyCode]; + return wxCFStringRef::AsString(str); +} + +wxCurrencySymbolPosition +wxUILocaleImplCF::GetCurrencySymbolPosition() const +{ + wxCFRef cfRefFormatter = [[NSNumberFormatter alloc] init]; + NSNumberFormatter* formatter = cfRefFormatter.get(); + formatter.locale = m_nsloc; + formatter.numberStyle = NSNumberFormatterCurrencyStyle; + + NSString* formatted = [formatter stringFromNumber:@123]; + NSString* symbol = formatter.currencySymbol; + + NSRange symbolRange = [formatted rangeOfString:symbol]; + BOOL symbolBefore = (symbolRange.location == 0); + + wxCurrencySymbolPosition symbolPos = wxCurrencySymbolPosition::PrefixWithSep; + BOOL hasSpace = NO; + if (symbolRange.location != NSNotFound) + { + if (symbolBefore) + { + // Check character succeeding the currency symbol + NSUInteger idx = NSMaxRange(symbolRange); + if (idx < formatted.length) + { + unichar after = [formatted characterAtIndex:idx]; + hasSpace = [[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:after]; + } + symbolPos = (hasSpace) + ? wxCurrencySymbolPosition::PrefixWithSep + : wxCurrencySymbolPosition::PrefixNoSep; + } + else + { + // Check character preceding the currency symbol + if (symbolRange.location > 0) + { + unichar before = [formatted characterAtIndex:symbolRange.location - 1]; + hasSpace = [[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:before]; + } + symbolPos = (hasSpace) + ? wxCurrencySymbolPosition::SuffixWithSep + : wxCurrencySymbolPosition::SuffixNoSep; + } + } + + return symbolPos; +} + +wxLocaleNumberFormatting +wxUILocaleImplCF::DoGetNumberFormatting(wxLocaleCategory cat) const +{ + wxCFRef cfRefFormatter = [[NSNumberFormatter alloc] init]; + NSNumberFormatter* formatter = cfRefFormatter.get(); + formatter.locale = m_nsloc; + formatter.numberStyle = (cat == wxLOCALE_CAT_MONEY) + ? NSNumberFormatterCurrencyStyle + : NSNumberFormatterDecimalStyle; + + wxString groupSeparator = wxCFStringRef::AsString(formatter.groupingSeparator); + + std::vector grouping; + int groupingSize = (int) formatter.groupingSize; + int secondaryGroupingSize = (int) formatter.secondaryGroupingSize; + if (groupingSize > 0) + { + if (secondaryGroupingSize > 0 && secondaryGroupingSize != groupingSize) + { + grouping.push_back(groupingSize); + grouping.push_back(secondaryGroupingSize); + grouping.push_back(0); + } + else + { + grouping.push_back(groupingSize); + grouping.push_back(0); + } + } + + wxString decimalSeparator = wxCFStringRef::AsString(formatter.decimalSeparator); + int fractionalDigits = (int) formatter.minimumFractionDigits; + + return wxLocaleNumberFormatting(groupSeparator, grouping, decimalSeparator, fractionalDigits); +} + +wxLocaleCurrencyInfo +wxUILocaleImplCF::GetCurrencyInfo() const +{ + wxLocaleNumberFormatting currencyFormatting = DoGetNumberFormatting(wxLOCALE_CAT_MONEY); + return wxLocaleCurrencyInfo( + GetCurrencySymbol(), + GetCurrencyCode(), + GetCurrencySymbolPosition(), + currencyFormatting); +} + +wxMeasurementSystem +wxUILocaleImplCF::UsesMetricSystem() const +{ + if ([m_nsloc respondsToSelector:@selector(usesMetricSystem)]) + { + BOOL isMetric = [m_nsloc usesMetricSystem]; + return (isMetric) ? wxMeasurementSystem::Metric : wxMeasurementSystem::NonMetric; + } + return wxMeasurementSystem::Unknown; +} + /* static */ wxUILocaleImpl* wxUILocaleImpl::CreateStdC() { diff --git a/src/unix/uilocale.cpp b/src/unix/uilocale.cpp index fdef511f07..6dd792f2e0 100644 --- a/src/unix/uilocale.cpp +++ b/src/unix/uilocale.cpp @@ -41,11 +41,27 @@ #include "wx/tokenzr.h" #include "wx/utils.h" +#include +#include + #define TRACE_I18N wxS("i18n") namespace { +// Enumeration for accessing member variables of the localeconv structure +// used as fallback, if nl_langinfo_l or nl_langinfo are not available +enum wxLocaleConvAttr +{ + LOCALE_CONV_THOUSANDS_SEP, + LOCALE_CONV_DECIMAL_POINT, + LOCALE_CONV_GROUPING, + LOCALE_CONV_DIGITS, + LOCALE_CONV_CURRENCY_SYMBOL, + LOCALE_CONV_CURRENCY_CODE, + LOCALE_CONV_CURRENCY_SYM_POS +}; + // Small helper function: get the value of the given environment variable and // return true only if the variable was found and has non-empty value. inline bool wxGetNonEmptyEnvVar(const wxString& name, wxString* value) @@ -191,6 +207,13 @@ public: #endif // wxUSE_DATETIME wxLayoutDirection GetLayoutDirection() const override; + wxLocaleNumberFormatting GetNumberFormatting() const override; + wxString GetCurrencySymbol() const override; + wxString GetCurrencyCode() const override; + wxCurrencySymbolPosition GetCurrencySymbolPosition() const override; + wxLocaleCurrencyInfo GetCurrencyInfo() const override; + wxMeasurementSystem UsesMetricSystem() const override; + int CompareStrings(const wxString& lhs, const wxString& rhs, int flags) const override; @@ -214,6 +237,11 @@ private: const wxString& GetCodeSet() const; + wxLocaleNumberFormatting DoGetNumberFormatting(wxLocaleCategory cat) const; + +#if !(defined(HAVE_LANGINFO_H) && defined(__GLIBC__)) + wxString DoGetInfoFromLocaleConv(wxLocaleConvAttr index, wxLocaleCategory cat) const; +#endif wxLocaleIdent m_locId; @@ -520,7 +548,7 @@ wxUILocaleImplUnix::GetLangInfo(nl_item item) const if ( m_locale ) return nl_langinfo_l(item, m_locale); #else - TempLocaleSetter setThisLocale(LC_CTYPE, m_locId.GetName()); + TempLocaleSetter setThisLocale(LC_ALL, m_locId.GetName()); #endif // HAVE_LOCALE_T return nl_langinfo(item); @@ -534,7 +562,7 @@ wxUILocaleImplUnix::GetLangInfoWide(nl_item item) const if ( m_locale ) return (wchar_t*) nl_langinfo_l(item, m_locale); #else - TempLocaleSetter setThisLocale(LC_CTYPE, m_locId.GetName()); + TempLocaleSetter setThisLocale(LC_ALL, m_locId.GetName()); #endif // HAVE_LOCALE_T return (wchar_t*) nl_langinfo(item); @@ -591,7 +619,6 @@ wxUILocaleImplUnix::GetInfo(wxLocaleInfo index, wxLocaleCategory cat) const #else wxUnusedVar(cat); #endif - return GetLangInfo(RADIXCHAR); case wxLOCALE_SHORT_DATE_FMT: @@ -832,6 +859,88 @@ wxUILocaleImplUnix::GetLayoutDirection() const return wxLayout_Default; } +wxLocaleNumberFormatting +wxUILocaleImplUnix::GetNumberFormatting() const +{ + return DoGetNumberFormatting(wxLOCALE_CAT_NUMBER); +} + +wxString +wxUILocaleImplUnix::GetCurrencySymbol() const +{ +#if defined(HAVE_LANGINFO_H) && defined(__GLIBC__) + wxString currencyStr = wxString(GetLangInfo(CURRENCY_SYMBOL), wxCSConv(GetCodeSet())); + if (!currencyStr.empty() && + (currencyStr[0] == '+' || + currencyStr[0] == '-' || + currencyStr[0] == '.')) + { + currencyStr.erase(0, 1); + } + return currencyStr; +#else + return DoGetInfoFromLocaleConv(LOCALE_CONV_CURRENCY_SYMBOL, wxLOCALE_CAT_DEFAULT); +#endif +} + +wxString +wxUILocaleImplUnix::GetCurrencyCode() const +{ +#if defined(HAVE_LANGINFO_H) && defined(__GLIBC__) + wxString currencyCode = wxString(GetLangInfo(INT_CURR_SYMBOL), wxCSConv(GetCodeSet())); + return currencyCode.Left(3); +#else + return DoGetInfoFromLocaleConv(LOCALE_CONV_CURRENCY_CODE, wxLOCALE_CAT_DEFAULT); +#endif +} + +wxCurrencySymbolPosition +wxUILocaleImplUnix::GetCurrencySymbolPosition() const +{ + static std::array symPos = { + wxCurrencySymbolPosition::PrefixNoSep, wxCurrencySymbolPosition::SuffixNoSep, + wxCurrencySymbolPosition::PrefixWithSep, wxCurrencySymbolPosition::SuffixWithSep }; + + wxUint32 posIdx = 0; +#if defined(HAVE_LANGINFO_H) && defined(__GLIBC__) + const char* csPrecedes = GetLangInfo(P_CS_PRECEDES); + const char* sepBySpace = GetLangInfo(P_SEP_BY_SPACE); + posIdx += (csPrecedes[0] == 0) ? 1 : 0; + posIdx += (sepBySpace[0] == 0) ? 0 : 2; +#else + wxString symbolPosition = DoGetInfoFromLocaleConv(LOCALE_CONV_CURRENCY_SYM_POS, wxLOCALE_CAT_DEFAULT); + posIdx = symbolPosition.GetChar(0).GetValue(); +#endif + return (posIdx < symPos.size()) ? symPos[posIdx] : wxCurrencySymbolPosition::PrefixWithSep; +} + +wxLocaleCurrencyInfo +wxUILocaleImplUnix::GetCurrencyInfo() const +{ + wxLocaleNumberFormatting currencyFormatting = DoGetNumberFormatting(wxLOCALE_CAT_MONEY); + return wxLocaleCurrencyInfo( + GetCurrencySymbol(), + GetCurrencyCode(), + GetCurrencySymbolPosition(), + currencyFormatting); +} + +wxMeasurementSystem +wxUILocaleImplUnix::UsesMetricSystem() const +{ +#if defined(HAVE_LANGINFO_H) && defined(__GLIBC__) + wxString measureStr = GetLangInfo(_NL_MEASUREMENT_MEASUREMENT); + if (!measureStr.empty() && measureStr[0].GetValue() == 1) + return wxMeasurementSystem::Metric; + else if (!measureStr.empty() && measureStr[0].GetValue() == 2) + return wxMeasurementSystem::NonMetric; + else + return wxMeasurementSystem::Unknown; +#else + return wxUILocale::GuessMetricSystemFromRegion(GetLocaleId()); +#endif +} + int wxUILocaleImplUnix::CompareStrings(const wxString& lhs, const wxString& rhs, int WXUNUSED(flags)) const @@ -854,6 +963,166 @@ wxUILocaleImplUnix::CompareStrings(const wxString& lhs, const wxString& rhs, return 0; } +wxLocaleNumberFormatting +wxUILocaleImplUnix::DoGetNumberFormatting(wxLocaleCategory cat) const +{ + wxString groupSeparator; +#if defined(HAVE_LANGINFO_H) && defined(__GLIBC__) + const char* groupSep = + #ifdef MON_THOUSANDS_SEP + (cat == wxLOCALE_CAT_MONEY) + ? GetLangInfo(MON_THOUSANDS_SEP) + : GetLangInfo(THOUSEP); + #else + GetLangInfo(THOUSEP); + #endif + groupSeparator = wxString(groupSep, wxCSConv(GetCodeSet())); +#else + groupSeparator = DoGetInfoFromLocaleConv(LOCALE_CONV_THOUSANDS_SEP, cat); +#endif + + std::vector grouping; +#if defined(HAVE_LANGINFO_H) && defined(__GLIBC__) + const char* groupingInfo = + #ifdef MON_GROUPING + (cat == wxLOCALE_CAT_MONEY) + ? GetLangInfo(MON_GROUPING) + : GetLangInfo(GROUPING); + #else + GetLangInfo(GROUPING); + #endif + + // Include the terminating '\0' in total length + size_t groupLen = strlen(groupingInfo) + 1; + for (size_t j = 0; j < groupLen; ++j) + { + auto val = groupingInfo[j]; + if (val == CHAR_MAX) + break; + grouping.push_back(static_cast(val)); + } +#else + wxString groupingStr = DoGetInfoFromLocaleConv(LOCALE_CONV_GROUPING, cat); + for (auto ch : groupingStr) + { + auto val = ch.GetValue(); + if (val == CHAR_MAX) + break; + grouping.push_back(static_cast(val)); + } +#endif + + wxString decimalSeparator; +#if defined(HAVE_LANGINFO_H) && defined(__GLIBC__) + const char* decimalSep = + #ifdef MON_DECIMAL_POINT + (cat == wxLOCALE_CAT_MONEY) + ? GetLangInfo(MON_DECIMAL_POINT) + : GetLangInfo(RADIXCHAR); + #else + GetLangInfo(RADIXCHAR); + #endif + decimalSeparator = wxString(decimalSep, wxCSConv(GetCodeSet())); +#else + decimalSeparator = DoGetInfoFromLocaleConv(LOCALE_CONV_DECIMAL_POINT, cat); +#endif + + int fractionalDigits = 0; +#if defined(HAVE_LANGINFO_H) && defined(__GLIBC__) + const char* fracDigits = GetLangInfo(FRAC_DIGITS); + fractionalDigits = (fracDigits) ? fracDigits[0] : 0; +#else + wxString fracDigits = DoGetInfoFromLocaleConv(LOCALE_CONV_DIGITS, cat); + fractionalDigits = (!fracDigits.empty()) ? fracDigits.GetChar(0).GetValue() : 0; +#endif + + return wxLocaleNumberFormatting(groupSeparator, grouping, decimalSeparator, fractionalDigits); +} + +#if !(defined(HAVE_LANGINFO_H) && defined(__GLIBC__)) + +wxString +wxUILocaleImplUnix::DoGetInfoFromLocaleConv(wxLocaleConvAttr index, wxLocaleCategory cat) const +{ + // localeconv accesses only the global locale + // temporarily set this locale + TempLocaleSetter setThisLocale(LC_ALL, m_locId.GetName()); + + lconv* const lc = localeconv(); + if (!lc) + return wxString(); + + switch (index) + { + case LOCALE_CONV_THOUSANDS_SEP: + { + if (cat == wxLOCALE_CAT_MONEY) + return wxString(lc->mon_thousands_sep, wxConvLibc); + else + return wxString(lc->thousands_sep, wxConvLibc); + } + + case LOCALE_CONV_DECIMAL_POINT: + { + if (cat == wxLOCALE_CAT_MONEY) + return wxString(lc->mon_decimal_point, wxConvLibc); + else + return wxString(lc->decimal_point, wxConvLibc); + } + + case LOCALE_CONV_GROUPING: + { + wxString groupingStr; + const char* grouping; + if (cat == wxLOCALE_CAT_MONEY) + grouping = lc->mon_grouping; + else + grouping = lc->grouping; + + size_t groupLen = strlen(grouping); + for (size_t j = 0; j < groupLen; ++j) + groupingStr.Append(wxUniChar(grouping[j])); + groupingStr.Append(wxUniChar(0)); + return groupingStr; + } + + case LOCALE_CONV_CURRENCY_SYMBOL: + { + return wxString(lc->currency_symbol, wxConvLibc); + } + + case LOCALE_CONV_CURRENCY_CODE: + { + return wxString(lc->int_curr_symbol, wxConvLibc).Left(3); + } + + case LOCALE_CONV_DIGITS: + { + wxString currencyDigitsStr(wxUniChar(lc->frac_digits)); + return currencyDigitsStr; + } + + case LOCALE_CONV_CURRENCY_SYM_POS: + { + char csPrecedes = lc->p_cs_precedes; + char sepBySpace = lc->p_sep_by_space; + wxUint32 posIdx = 0; + posIdx += (csPrecedes == 0) ? 1 : 0; + posIdx += (sepBySpace == 0) ? 0 : 2; + wxString symbolPosition; + symbolPosition.Append(wxUniChar(posIdx)); + return symbolPosition; + } + + default: + wxFAIL_MSG("unknown localeconv value"); + } + + return wxString(); +} + +#endif + /* static */ wxUILocaleImpl* wxUILocaleImpl::CreateStdC() { diff --git a/tests/intl/intltest.cpp b/tests/intl/intltest.cpp index 6eacfb79da..7e989e24dd 100644 --- a/tests/intl/intltest.cpp +++ b/tests/intl/intltest.cpp @@ -417,11 +417,35 @@ TEST_CASE("wxUILocale::IsSupported", "[uilocale]") TEST_CASE("wxUILocale::GetInfo", "[uilocale]") { - CHECK( wxUILocale::FromTag("en").GetInfo(wxLOCALE_DECIMAL_POINT) == "." ); + const wxUILocale locEN(wxUILocale::FromTag("en-US")); + CHECK( locEN.GetInfo(wxLOCALE_DECIMAL_POINT) == "." ); + CHECK( locEN.GetCurrencySymbol() == "$"); + CHECK( locEN.GetCurrencyCode() == "USD"); + CHECK( locEN.GetCurrencyInfo().currencyFormat.fractionalDigits == 2); + CHECK( locEN.GetCurrencySymbolPosition() == wxCurrencySymbolPosition::PrefixNoSep ); + CHECK( locEN.UsesMetricSystem() == wxMeasurementSystem::NonMetric); - const wxUILocale locDE(wxUILocale::FromTag("de")); - if ( CheckSupported(locDE, "German") ) - CHECK( locDE.GetInfo(wxLOCALE_DECIMAL_POINT) == "," ); + const wxUILocale locDE(wxUILocale::FromTag("de-DE")); + if (CheckSupported(locDE, "German")) + { + CHECK( locDE.GetInfo(wxLOCALE_DECIMAL_POINT) == ","); + CHECK( locDE.GetCurrencySymbol() == L"\u20AC"); + CHECK( locDE.GetCurrencyCode() == "EUR"); + CHECK( locDE.GetCurrencyInfo().currencyFormat.fractionalDigits == 2); + CHECK( locDE.GetCurrencySymbolPosition() == wxCurrencySymbolPosition::SuffixWithSep); + CHECK( locDE.UsesMetricSystem() == wxMeasurementSystem::Metric); + } + + const wxUILocale locFR(wxUILocale::FromTag("fr-FR")); + if (CheckSupported(locFR, "French")) + { + CHECK( locFR.GetInfo(wxLOCALE_DECIMAL_POINT) == ","); + CHECK( locFR.GetCurrencySymbol() == L"\u20AC"); + CHECK( locFR.GetCurrencyCode() == "EUR"); + CHECK( locFR.GetCurrencyInfo().currencyFormat.fractionalDigits == 2); + CHECK( locFR.GetCurrencySymbolPosition() == wxCurrencySymbolPosition::SuffixWithSep); + CHECK( locFR.UsesMetricSystem() == wxMeasurementSystem::Metric); + } // This one shows that "Swiss High German" locale (de_CH) correctly uses // dot, and not comma, as decimal separator, even under macOS, where POSIX diff --git a/tests/strings/numformatter.cpp b/tests/strings/numformatter.cpp index a1b80ca805..e44e2af67d 100644 --- a/tests/strings/numformatter.cpp +++ b/tests/strings/numformatter.cpp @@ -15,6 +15,9 @@ #include "wx/numformatter.h" #include "wx/intl.h" +#include "wx/uilocale.h" + +#include // ---------------------------------------------------------------------------- // test class @@ -341,3 +344,504 @@ TEST_CASE_METHOD(NumFormatterTestCase, "NumFormatter::DoubleFromString", "[numfo CHECK( wxNumberFormatter::FromString("123456789.012", &d) ); CHECK( d == 123456789.012 ); } + +// ---------------------------------------------------------------------------- +// test class for currency formatting +// ---------------------------------------------------------------------------- + +// Euro Sign in UTF-8 +#define EUROSIGN "\xe2\x82\xac" + +// No-Break Space in UTF-8 +#define NBSP "\xc2\xa0" + +// Narrow No-Break Space in UTF-8 +#define NNBSP "\xe2\x80\xaf" + +class CurrencyFormatterTestCase +{ +public: + CurrencyFormatterTestCase() : + // The locale 'de-AT' is known to have different grouping characters + // for ordinary numbers and currency values. + m_locale(wxLANGUAGE_GERMAN_AUSTRIAN, wxLOCALE_DONT_LOAD_DEFAULT) + { + } + +protected: + bool CanRunTest() const + { + bool ok = m_locale.IsOk(); + if (ok) + { + wxLocaleNumberFormatting numForm = wxUILocale::GetCurrent().GetNumberFormatting(); +#ifdef __GLIBC__ + ok = numForm.groupSeparator == "."; +#else + ok = numForm.groupSeparator == wxString::FromUTF8(NBSP); +#endif + } + return ok; + } + +private: + wxLocale m_locale; + + wxDECLARE_NO_COPY_CLASS(CurrencyFormatterTestCase); +}; + +// ---------------------------------------------------------------------------- +// tests themselves +// ---------------------------------------------------------------------------- + +TEST_CASE_METHOD(CurrencyFormatterTestCase, "CurrencyFormatterTestCase::NumericValuesToString", "[numformatter]") +{ + if (!CanRunTest()) + return; + +#ifdef __GLIBC__ + CHECK( wxString::FromUTF8("12.345.678") == wxNumberFormatter::ToString(12345678L, wxNumberFormatter::Style_WithThousandsSep)); + CHECK( wxString::FromUTF8("12" NNBSP "345" NNBSP "678") == wxNumberFormatter::ToString(12345678L, wxNumberFormatter::Style_Currency|wxNumberFormatter::Style_WithThousandsSep)); + CHECK( wxString::FromUTF8("12.345.678,12") == wxNumberFormatter::ToString(12345678.12, 2, wxNumberFormatter::Style_WithThousandsSep)); + CHECK( wxString::FromUTF8("12" NNBSP "345" NNBSP "678,12") == wxNumberFormatter::ToString(12345678.12, 2, wxNumberFormatter::Style_Currency + | wxNumberFormatter::Style_WithThousandsSep)); + CHECK( wxString::FromUTF8(EUROSIGN " 12" NNBSP "345" NNBSP "678,12") == wxNumberFormatter::ToString(12345678.12, 2, wxNumberFormatter::Style_Currency + | wxNumberFormatter::Style_CurrencySymbol | wxNumberFormatter::Style_WithThousandsSep)); + CHECK( wxString::FromUTF8("EUR 12" NNBSP "345" NNBSP "678,12") == wxNumberFormatter::ToString(12345678.12, 2, wxNumberFormatter::Style_Currency + | wxNumberFormatter::Style_CurrencyCode | wxNumberFormatter::Style_WithThousandsSep)); + CHECK( "12345678,12" == wxNumberFormatter::RemoveCurrencySymbolOrCode(wxString::FromUTF8("EUR 12" NNBSP "345" NNBSP "678,12"), wxNumberFormatter::Style_Currency + | wxNumberFormatter::Style_CurrencyCode | wxNumberFormatter::Style_WithThousandsSep)); +#else + CHECK( wxString::FromUTF8("12" NBSP "345" NBSP "678") == wxNumberFormatter::ToString(12345678L, wxNumberFormatter::Style_WithThousandsSep)); + CHECK( "12.345.678" == wxNumberFormatter::ToString(12345678L, wxNumberFormatter::Style_Currency|wxNumberFormatter::Style_WithThousandsSep)); + CHECK( wxString::FromUTF8("12" NBSP "345" NBSP "678,12") == wxNumberFormatter::ToString(12345678.12, 2, wxNumberFormatter::Style_WithThousandsSep)); + CHECK( "12.345.678,12" == wxNumberFormatter::ToString(12345678.12, 2, wxNumberFormatter::Style_Currency + | wxNumberFormatter::Style_WithThousandsSep)); + CHECK( wxString::FromUTF8(EUROSIGN " 12.345.678,12") == wxNumberFormatter::ToString(12345678.12, 2, wxNumberFormatter::Style_Currency + | wxNumberFormatter::Style_CurrencySymbol | wxNumberFormatter::Style_WithThousandsSep)); + CHECK( "EUR 12.345.678,12" == wxNumberFormatter::ToString(12345678.12, 2, wxNumberFormatter::Style_Currency + | wxNumberFormatter::Style_CurrencyCode | wxNumberFormatter::Style_WithThousandsSep)); + CHECK( "12345678,12" == wxNumberFormatter::RemoveCurrencySymbolOrCode("EUR 12.345.678,12", wxNumberFormatter::Style_Currency + | wxNumberFormatter::Style_CurrencyCode | wxNumberFormatter::Style_WithThousandsSep)); +#endif +} + +// ---------------------------------------------------------------------------- +// test class (India locale) +// ---------------------------------------------------------------------------- +class NumFormatterAlternateTestCase +{ +public: + NumFormatterAlternateTestCase() : + // We need to use a locale with known decimal point and which uses the + // thousands separator for the tests to make sense. + m_locale(wxLANGUAGE_ENGLISH_INDIA, wxLOCALE_DONT_LOAD_DEFAULT) + { + } + +protected: + bool CanRunTest() const + { + bool ok = m_locale.IsOk(); + if (ok) + { + wxLocaleNumberFormatting numForm = wxUILocale::GetCurrent().GetNumberFormatting(); + ok = (!numForm.grouping.empty() && numForm.grouping.back() == 0); + } + return ok; + } + +private: + wxLocale m_locale; + + wxDECLARE_NO_COPY_CLASS(NumFormatterAlternateTestCase); +}; + +// ---------------------------------------------------------------------------- +// tests themselves +// ---------------------------------------------------------------------------- + +TEST_CASE_METHOD(NumFormatterAlternateTestCase, "NumFormatterAlternateTestCase::LongToString", "[numformatter]") +{ + if (!CanRunTest()) + return; + + CHECK( "1" == wxNumberFormatter::ToString( 1L) ); + CHECK( "-1" == wxNumberFormatter::ToString( -1L) ); + CHECK( "12" == wxNumberFormatter::ToString( 12L) ); + CHECK( "-12" == wxNumberFormatter::ToString( -12L) ); + CHECK( "123" == wxNumberFormatter::ToString( 123L) ); + CHECK( "-123" == wxNumberFormatter::ToString( -123L) ); + CHECK( "1,234" == wxNumberFormatter::ToString( 1234L) ); + CHECK( "-1,234" == wxNumberFormatter::ToString( -1234L) ); + CHECK( "12,345" == wxNumberFormatter::ToString( 12345L) ); + CHECK( "-12,345" == wxNumberFormatter::ToString( -12345L) ); + CHECK( "1,23,456" == wxNumberFormatter::ToString( 123456L) ); + CHECK( "-1,23,456" == wxNumberFormatter::ToString( -123456L) ); + CHECK( "12,34,567" == wxNumberFormatter::ToString( 1234567L) ); + CHECK( "-12,34,567" == wxNumberFormatter::ToString( -1234567L) ); + CHECK( "1,23,45,678" == wxNumberFormatter::ToString( 12345678L) ); + CHECK( "-1,23,45,678" == wxNumberFormatter::ToString( -12345678L) ); + CHECK( "12,34,56,789" == wxNumberFormatter::ToString( 123456789L) ); +} + +#ifdef wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + +TEST_CASE_METHOD(NumFormatterAlternateTestCase, "NumFormatterAlternateTestCase::LongLongToString", "[numformatter]") +{ + if ( !CanRunTest()) + return; + + CHECK( "1" == wxNumberFormatter::ToString(wxLL( 1)) ); + CHECK( "12" == wxNumberFormatter::ToString(wxLL( 12)) ); + CHECK( "123" == wxNumberFormatter::ToString(wxLL( 123)) ); + CHECK( "1,234" == wxNumberFormatter::ToString(wxLL( 1234)) ); + CHECK( "12,345" == wxNumberFormatter::ToString(wxLL( 12345)) ); + CHECK( "1,23,456" == wxNumberFormatter::ToString(wxLL( 123456)) ); + CHECK( "12,34,567" == wxNumberFormatter::ToString(wxLL( 1234567)) ); + CHECK( "1,23,45,678" == wxNumberFormatter::ToString(wxLL( 12345678)) ); + CHECK( "12,34,56,789" == wxNumberFormatter::ToString(wxLL( 123456789)) ); +} + +#endif // wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + +TEST_CASE_METHOD(NumFormatterAlternateTestCase, "NumFormatterAlternateTestCase::DoubleToString", "[numformatter]") +{ + if ( !CanRunTest()) + return; + + CHECK( "1.0" == wxNumberFormatter::ToString(1., 1)); + CHECK( "0.123456" == wxNumberFormatter::ToString(0.123456, 6) ); + CHECK( "1.234567" == wxNumberFormatter::ToString(1.234567, 6) ); + CHECK( "12.34567" == wxNumberFormatter::ToString(12.34567, 5) ); + CHECK( "123.4567" == wxNumberFormatter::ToString(123.4567, 4) ); + CHECK( "1,234.56" == wxNumberFormatter::ToString(1234.56, 2) ); + CHECK( "12,345.6" == wxNumberFormatter::ToString(12345.6, 1) ); + CHECK( "12,345.6" == wxNumberFormatter::ToString(12345.6, 1) ); + CHECK( "12,34,56,789.0" == wxNumberFormatter::ToString(123456789., 1) ); + CHECK( "12,34,56,789.012" == wxNumberFormatter::ToString(123456789.012, 3) ); + CHECK( "12,345" == wxNumberFormatter::ToString(12345.012, -1) ); + CHECK( "-123.1230" == wxNumberFormatter::ToString(-123.123, 4, wxNumberFormatter::Style_None) ); + CHECK( "0.0" == wxNumberFormatter::ToString(0.02, 1, wxNumberFormatter::Style_None) ); + CHECK( "-0.0" == wxNumberFormatter::ToString(-0.02, 1, wxNumberFormatter::Style_None) ); +} + +TEST_CASE_METHOD(NumFormatterAlternateTestCase, "NumFormatterAlternateTestCase::NoTrailingZeroes", "[numformatter]") +{ + WX_ASSERT_FAILS_WITH_ASSERT + ( + wxNumberFormatter::ToString(123L, wxNumberFormatter::Style_NoTrailingZeroes) + ); + + if ( !CanRunTest()) + return; + + CHECK( "123.000" == wxNumberFormatter::ToString(123., 3) ); + CHECK( "123" == wxNumberFormatter::ToString(123., 3, wxNumberFormatter::Style_NoTrailingZeroes) ); + CHECK( "123" == wxNumberFormatter::ToString(123., 9, wxNumberFormatter::Style_NoTrailingZeroes) ); + CHECK( "123.456" == wxNumberFormatter::ToString(123.456, 3, wxNumberFormatter::Style_NoTrailingZeroes) ); + CHECK( "123.456000000" == wxNumberFormatter::ToString(123.456, 9) ); + CHECK( "123.456" == wxNumberFormatter::ToString(123.456, 9, wxNumberFormatter::Style_NoTrailingZeroes) ); + CHECK( "123.12" == wxNumberFormatter::ToString(123.123, 2, wxNumberFormatter::Style_NoTrailingZeroes) ); + CHECK( "123" == wxNumberFormatter::ToString(123.123, 0, wxNumberFormatter::Style_NoTrailingZeroes) ); + CHECK( "0" == wxNumberFormatter::ToString(-0.000123, 3, wxNumberFormatter::Style_NoTrailingZeroes) ); + CHECK( "123" == wxNumberFormatter::ToString(123., -1, wxNumberFormatter::Style_NoTrailingZeroes) ); + CHECK( "1e-120" == wxNumberFormatter::ToString(1e-120, -1, wxNumberFormatter::Style_NoTrailingZeroes) ); +} + +TEST_CASE_METHOD(NumFormatterAlternateTestCase, "NumFormatterAlternateTestCase::LongFromString", "[numformatter]") +{ + if ( !CanRunTest()) + return; + + WX_ASSERT_FAILS_WITH_ASSERT + ( + wxNumberFormatter::FromString("123", static_cast(0)) + ); + + long l; + CHECK( !wxNumberFormatter::FromString("", &l) ); + CHECK( !wxNumberFormatter::FromString("foo", &l) ); + CHECK( !wxNumberFormatter::FromString("1.234", &l) ); + + CHECK( wxNumberFormatter::FromString("123", &l) ); + CHECK( 123 == l ); + + CHECK( wxNumberFormatter::FromString("1234", &l) ); + CHECK( 1234 == l ); + + CHECK( wxNumberFormatter::FromString("1,234", &l) ); + CHECK( 1234 == l ); + + CHECK( wxNumberFormatter::FromString("12,345", &l) ); + CHECK( 12345 == l ); + + CHECK( wxNumberFormatter::FromString("1,23,456", &l) ); + CHECK( 123456 == l ); + + CHECK( wxNumberFormatter::FromString("1,234,567", &l) ); + CHECK( 1234567 == l ); +} + +#ifdef wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + +TEST_CASE_METHOD(NumFormatterAlternateTestCase, "NumFormatterAlternateTestCase::LongLongFromString", "[numformatter]") +{ + if ( !CanRunTest()) + return; + + WX_ASSERT_FAILS_WITH_ASSERT + ( + wxNumberFormatter::FromString("123", static_cast(0)) + ); + + wxLongLong_t l; + CHECK( !wxNumberFormatter::FromString("", &l) ); + CHECK( !wxNumberFormatter::FromString("foo", &l) ); + CHECK( !wxNumberFormatter::FromString("1.234", &l) ); + + CHECK( wxNumberFormatter::FromString("123", &l) ); + CHECK( 123 == l ); + + CHECK( wxNumberFormatter::FromString("1234", &l) ); + CHECK( 1234 == l ); + + CHECK( wxNumberFormatter::FromString("1,234", &l) ); + CHECK( 1234 == l ); + + CHECK( wxNumberFormatter::FromString("12,345", &l) ); + CHECK( 12345 == l ); + + CHECK( wxNumberFormatter::FromString("1,23,456", &l) ); + CHECK( 123456 == l ); + + CHECK( wxNumberFormatter::FromString("12,34,567", &l) ); + CHECK( 1234567 == l ); +} + +#endif // wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + +TEST_CASE_METHOD(NumFormatterAlternateTestCase, "NumFormatterAlternateTestCase::DoubleFromString", "[numformatter]") +{ + if ( !CanRunTest()) + return; + + WX_ASSERT_FAILS_WITH_ASSERT + ( + wxNumberFormatter::FromString("123", static_cast(0)) + ); + + double d; + CHECK( !wxNumberFormatter::FromString("", &d) ); + CHECK( !wxNumberFormatter::FromString("bar", &d) ); + + CHECK( wxNumberFormatter::FromString("123", &d) ); + CHECK( 123. == d ); + + CHECK( wxNumberFormatter::FromString("123.456789012", &d) ); + CHECK( 123.456789012 == d ); + + CHECK( wxNumberFormatter::FromString("1,234.56789012", &d) ); + CHECK( 1234.56789012 == d ); + + CHECK( wxNumberFormatter::FromString("12,345.6789012", &d) ); + CHECK( 12345.6789012 == d ); + + CHECK( wxNumberFormatter::FromString("1,23,456.789012", &d) ); + CHECK( 123456.789012 == d ); + + CHECK( wxNumberFormatter::FromString("1,234,567.89012", &d) ); + CHECK( 1234567.89012 == d ); + + CHECK( wxNumberFormatter::FromString("1,23,45,678.9012", &d) ); + CHECK( 12345678.9012 == d ); + + CHECK( wxNumberFormatter::FromString("12,34,56,789.012", &d) ); + CHECK( 123456789.012 == d ); + + CHECK( wxNumberFormatter::FromString("123456789.012", &d) ); + CHECK( 123456789.012 == d ); +} + +//-------------------------------------------------------------------------- +// Test case with grouping in US format i.e, "3;0" +//-------------------------------------------------------------------------- + +class FormatNumberTestCase +{ +public: + FormatNumberTestCase() : + // We need to use a locale with known decimal point and which uses the + // thousands separator for the tests to make sense. + m_locale(wxLANGUAGE_ENGLISH_US, wxLOCALE_DONT_LOAD_DEFAULT) + { + } + +protected: + bool CanRunTest() const { return m_locale.IsOk(); } + +private: + wxLocale m_locale; + +}; + +TEST_CASE_METHOD(FormatNumberTestCase, "FormatNumberTestCase::PositiveIntegers", "[numformatter]") +{ + if (!CanRunTest()) + return; + + CHECK( wxNumberFormatter::ToString( 0L) == "0" ); + CHECK( wxNumberFormatter::ToString( 1L) == "1" ); + CHECK( wxNumberFormatter::ToString( 12L) == "12" ); + CHECK( wxNumberFormatter::ToString( 123L) == "123" ); + CHECK( wxNumberFormatter::ToString( 1234L) == "1,234" ); + CHECK( wxNumberFormatter::ToString( 12345L) == "12,345" ); + CHECK( wxNumberFormatter::ToString( 123456L) == "123,456" ); + CHECK( wxNumberFormatter::ToString( 1234567L) == "1,234,567" ); + CHECK( wxNumberFormatter::ToString( 12345678L) == "12,345,678" ); + CHECK( wxNumberFormatter::ToString( 123456789L) == "123,456,789" ); + CHECK( wxNumberFormatter::ToString( 1234567890L) == "1,234,567,890" ); +} + +TEST_CASE_METHOD(FormatNumberTestCase, "FormatNumberTestCase::NegativeIntegers", "[numformatter]") +{ + if (!CanRunTest()) + return; + + CHECK( wxNumberFormatter::ToString( -1L) == "-1" ); + CHECK( wxNumberFormatter::ToString( -12L) == "-12" ); + CHECK( wxNumberFormatter::ToString( -123L) == "-123" ); + CHECK( wxNumberFormatter::ToString( -1234L) == "-1,234" ); + CHECK( wxNumberFormatter::ToString( -12345L) == "-12,345" ); + CHECK( wxNumberFormatter::ToString( -123456L) == "-123,456" ); + CHECK( wxNumberFormatter::ToString( -1234567L) == "-1,234,567" ); + CHECK( wxNumberFormatter::ToString( -12345678L) == "-12,345,678" ); + CHECK( wxNumberFormatter::ToString( -123456789L) == "-123,456,789" ); + CHECK( wxNumberFormatter::ToString( -1234567890L) == "-1,234,567,890" ); +} + +TEST_CASE_METHOD(FormatNumberTestCase, "FormatNumberTestCase::PositiveReals", "[numformatter]") +{ + if (!CanRunTest()) + return; + + CHECK( wxNumberFormatter::ToString( 0.1234, 4) == "0.1234" ); + CHECK( wxNumberFormatter::ToString( 1.1234, 4) == "1.1234" ); + CHECK( wxNumberFormatter::ToString( 12.1234, 4) == "12.1234" ); + CHECK( wxNumberFormatter::ToString( 123.1234, 4) == "123.1234" ); + CHECK( wxNumberFormatter::ToString( 1234.1234, 4) == "1,234.1234" ); + CHECK( wxNumberFormatter::ToString( 12345.1234, 4) == "12,345.1234" ); + CHECK( wxNumberFormatter::ToString( 123456.1234, 4) == "123,456.1234" ); + CHECK( wxNumberFormatter::ToString( 1234567.1234, 4) == "1,234,567.1234" ); + CHECK( wxNumberFormatter::ToString( 12345678.1234, 4) == "12,345,678.1234" ); + CHECK( wxNumberFormatter::ToString( 123456789.1234, 4) == "123,456,789.1234" ); + CHECK( wxNumberFormatter::ToString( 1234567890.1234, 4) == "1,234,567,890.1234" ); +} + +TEST_CASE_METHOD(FormatNumberTestCase, "FormatNumberTestCase::NegativeReals", "[numformatter]") +{ + if (!CanRunTest()) + return; + + CHECK( wxNumberFormatter::ToString( -1.1234, 4) == "-1.1234" ); + CHECK( wxNumberFormatter::ToString( -12.1234, 4) == "-12.1234" ); + CHECK( wxNumberFormatter::ToString( -123.1234, 4) == "-123.1234" ); + CHECK( wxNumberFormatter::ToString( -1234.1234, 4) == "-1,234.1234" ); + CHECK( wxNumberFormatter::ToString( -12345.1234, 4) == "-12,345.1234" ); + CHECK( wxNumberFormatter::ToString( -123456.1234, 4) == "-123,456.1234" ); + CHECK( wxNumberFormatter::ToString( -1234567.1234, 4) == "-1,234,567.1234" ); + CHECK( wxNumberFormatter::ToString( -12345678.1234, 4) == "-12,345,678.1234" ); + CHECK( wxNumberFormatter::ToString( -123456789.1234, 4) == "-123,456,789.1234" ); + CHECK( wxNumberFormatter::ToString( -1234567890.1234, 4) == "-1,234,567,890.1234" ); +} + +//-------------------------------------------------------------------------- +// Alternate test case with grouping in Indian format i.e, "3;2;0" +//-------------------------------------------------------------------------- + +class FormatNumberAlternateTestCase +{ +public: + FormatNumberAlternateTestCase() : + // We need to use a locale with known decimal point and which uses the + // thousands separator for the tests to make sense. + m_locale(wxLANGUAGE_ENGLISH_INDIA, wxLOCALE_DONT_LOAD_DEFAULT) + { + } + +protected: + bool CanRunTest() const { return m_locale.IsOk(); } + +private: + wxLocale m_locale; +}; + +TEST_CASE_METHOD(FormatNumberAlternateTestCase, "FormatNumberAlternateTestCase::PositiveIntegers", "[numformatter]") +{ + if (!CanRunTest()) + return; + + CHECK( wxNumberFormatter::ToString( 0L) == "0" ); + CHECK( wxNumberFormatter::ToString( 1L) == "1" ); + CHECK( wxNumberFormatter::ToString( 12L) == "12" ); + CHECK( wxNumberFormatter::ToString( 123L) == "123" ); + CHECK( wxNumberFormatter::ToString( 1234L) == "1,234" ); + CHECK( wxNumberFormatter::ToString( 12345L) == "12,345" ); + CHECK( wxNumberFormatter::ToString( 123456L) == "1,23,456" ); + CHECK( wxNumberFormatter::ToString( 1234567L) == "12,34,567" ); + CHECK( wxNumberFormatter::ToString( 12345678L) == "1,23,45,678" ); + CHECK( wxNumberFormatter::ToString( 123456789L) == "12,34,56,789" ); + CHECK( wxNumberFormatter::ToString( 1234567890L) == "1,23,45,67,890" ); +} + +TEST_CASE_METHOD(FormatNumberAlternateTestCase, "FormatNumberAlternateTestCase::NegativeIntegers", "[numformatter]") +{ + if (!CanRunTest()) + return; + + CHECK( wxNumberFormatter::ToString( -1L) == "-1" ); + CHECK( wxNumberFormatter::ToString( -12L) == "-12" ); + CHECK( wxNumberFormatter::ToString( -123L) == "-123" ); + CHECK( wxNumberFormatter::ToString( -1234L) == "-1,234" ); + CHECK( wxNumberFormatter::ToString( -12345L) == "-12,345" ); + CHECK( wxNumberFormatter::ToString( -123456L) == "-1,23,456" ); + CHECK( wxNumberFormatter::ToString( -1234567L) == "-12,34,567" ); + CHECK( wxNumberFormatter::ToString( -12345678L) == "-1,23,45,678" ); + CHECK( wxNumberFormatter::ToString( -123456789L) == "-12,34,56,789" ); + CHECK( wxNumberFormatter::ToString( -1234567890L) == "-1,23,45,67,890" ); +} + +TEST_CASE_METHOD(FormatNumberAlternateTestCase, "FormatNumberAlternateTestCase::PositiveReals", "[numformatter]") +{ + if (!CanRunTest()) + return; + + CHECK( wxNumberFormatter::ToString( 0.1234, 4) == "0.1234" ); + CHECK( wxNumberFormatter::ToString( 1.1234, 4) == "1.1234" ); + CHECK( wxNumberFormatter::ToString( 12.1234, 4) == "12.1234" ); + CHECK( wxNumberFormatter::ToString( 123.1234, 4) == "123.1234" ); + CHECK( wxNumberFormatter::ToString( 1234.1234, 4) == "1,234.1234" ); + CHECK( wxNumberFormatter::ToString( 12345.1234, 4) == "12,345.1234" ); + CHECK( wxNumberFormatter::ToString( 123456.1234, 4) == "1,23,456.1234" ); + CHECK( wxNumberFormatter::ToString( 1234567.1234, 4) == "12,34,567.1234" ); + CHECK( wxNumberFormatter::ToString( 12345678.1234, 4) == "1,23,45,678.1234" ); + CHECK( wxNumberFormatter::ToString( 123456789.1234, 4) == "12,34,56,789.1234" ); + CHECK( wxNumberFormatter::ToString( 1234567890.1234, 4) == "1,23,45,67,890.1234" ); +} + +TEST_CASE_METHOD(FormatNumberAlternateTestCase, "FormatNumberAlternateTestCase::NegativeReals", "[numformatter]") +{ + if (!CanRunTest()) + return; + + CHECK( wxNumberFormatter::ToString( -1.1234, 4) == "-1.1234" ); + CHECK( wxNumberFormatter::ToString( -12.1234, 4) == "-12.1234" ); + CHECK( wxNumberFormatter::ToString( -123.1234, 4) == "-123.1234" ); + CHECK( wxNumberFormatter::ToString( -1234.1234, 4) == "-1,234.1234" ); + CHECK( wxNumberFormatter::ToString( -12345.1234, 4) == "-12,345.1234" ); + CHECK( wxNumberFormatter::ToString( -123456.1234, 4) == "-1,23,456.1234" ); + CHECK( wxNumberFormatter::ToString( -1234567.1234, 4) == "-12,34,567.1234" ); + CHECK( wxNumberFormatter::ToString( -12345678.1234, 4) == "-1,23,45,678.1234" ); + CHECK( wxNumberFormatter::ToString( -123456789.1234, 4) == "-12,34,56,789.1234" ); + CHECK( wxNumberFormatter::ToString( -1234567890.1234, 4) == "-1,23,45,67,890.1234" ); +}