diff --git a/include/wx/intl.h b/include/wx/intl.h index d66e3043d87a..799fe194e338 100644 --- a/include/wx/intl.h +++ b/include/wx/intl.h @@ -118,6 +118,10 @@ enum wxLocaleInfo // the character used as decimal point (for wxLOCALE_CAT_NUMBER or MONEY) wxLOCALE_DECIMAL_POINT, + // Specifies the amount of digits that form each of the groups to be + // separated by thousands separator (for wxLOCALE_CAT_NUMBER or MONEY) + wxLOCALE_GROUPING, + // the stftime()-formats used for short/long date and time representations // (under some platforms short and long date formats are the same) // @@ -356,6 +360,10 @@ class WXDLLIMPEXP_BASE wxLocale const wxString& shortName, bool bLoadDefault); + // Converts a grouping format string returned by localeconv() + // to the kind used on Windows platform + static wxString StandardizeGroupingString(const wxString& g); + wxString m_strLocale, // this locale name m_strShort; // short name for the locale diff --git a/include/wx/language.h b/include/wx/language.h index 5d788abcc247..6a04d8f66934 100644 --- a/include/wx/language.h +++ b/include/wx/language.h @@ -108,6 +108,7 @@ enum wxLanguage wxLANGUAGE_ENGLISH_SOUTH_AFRICA, wxLANGUAGE_ENGLISH_TRINIDAD, wxLANGUAGE_ENGLISH_ZIMBABWE, + wxLANGUAGE_ENGLISH_INDIA, wxLANGUAGE_ESPERANTO, wxLANGUAGE_ESTONIAN, wxLANGUAGE_FAEROESE, diff --git a/include/wx/numformatter.h b/include/wx/numformatter.h index 13b47b210b1f..14dd24dcfd32 100644 --- a/include/wx/numformatter.h +++ b/include/wx/numformatter.h @@ -58,6 +58,14 @@ class WXDLLIMPEXP_BASE wxNumberFormatter // function returns true. static bool GetThousandsSeparatorIfUsed(wxChar *sep); + // Same as the above method but provides the grouping format as well + static bool GetThousandsSeparatorAndGroupingIfUsed(wxChar *sep, wxString *gr); + + // Format a number s with the specified thousands separator, decimal separator + // and grouping format for the thousands separator + static void FormatNumber(wxString &s, wxChar thousandsSep, + wxChar decSep, wxString grouping); + private: // Post-process the string representing an integer. static wxString PostProcessIntString(wxString s, int style); diff --git a/interface/wx/intl.h b/interface/wx/intl.h index 8fbe58fbd4e7..36c0deb20f7a 100644 --- a/interface/wx/intl.h +++ b/interface/wx/intl.h @@ -127,6 +127,18 @@ enum wxLocaleInfo */ wxLOCALE_DECIMAL_POINT, + /** + Specifies the amount of digits that form each of the groups + to be separated by thousands_sep separator for non-monetary + quantities. + + This value can be used with either wxLOCALE_CAT_NUMBER or + wxLOCALE_CAT_MONEY categories. + + @since 3.1.5 + */ + wxLOCALE_GROUPING, + /** Short date format. @@ -526,4 +538,3 @@ class wxLocale Get the current locale object (note that it may be NULL!) */ wxLocale* wxGetLocale(); - diff --git a/interface/wx/language.h b/interface/wx/language.h index 7ad4df159a05..f0a08c4e9cb0 100644 --- a/interface/wx/language.h +++ b/interface/wx/language.h @@ -93,6 +93,7 @@ enum wxLanguage wxLANGUAGE_ENGLISH_SOUTH_AFRICA, wxLANGUAGE_ENGLISH_TRINIDAD, wxLANGUAGE_ENGLISH_ZIMBABWE, + wxLANGUAGE_ENGLISH_INDIA, wxLANGUAGE_ESPERANTO, wxLANGUAGE_ESTONIAN, wxLANGUAGE_FAEROESE, diff --git a/interface/wx/numformatter.h b/interface/wx/numformatter.h index db678d08f5e3..6fa8c165f032 100644 --- a/interface/wx/numformatter.h +++ b/interface/wx/numformatter.h @@ -123,4 +123,53 @@ class wxNumberFormatter */ static bool GetThousandsSeparatorIfUsed(wxChar *sep); + /** + Get the thousands separator and grouping format if grouping of the + digits is used by the current locale. + + The value returned in @a sep and @a gr should be only used if the + function returns @true, otherwise no thousands separator should be + used at all. + + @param sep + Points to the variable receiving the thousands separator character + if it is used by the current locale. May be @NULL if only the + function return value is needed. + + @param gr + Points to the variable receiving the grouping format string + if it is used by the current locale. May be @NULL if only the + function return value is needed. + + @since 3.1.5 + */ + static bool GetThousandsSeparatorAndGroupingIfUsed(wxChar *sep, wxString *gr); + + /** + Format a number with the thousands separator based on grouping format. + The grouping format is a string of digits separated by ';'. Each digit + indicates the number of digits of the string s to be grouped starting + from the right. If the last digit is '0' then the last but one digit + is used for grouping the remaining digits. + + Examples: + Number: "123456789", Grouping format: "3;0", result: "123,456,789" + Number: "123456789", Grouping format: "3;2;0", result: "12,34,56,789" + + @param s + The number to be formatted as a string + + @param thousandsSep + The thousands separator + + @param decSep + The decimal separator + + @param grouping + The string representing the thousands separator grouping format. + + @since 3.1.5 + */ + static void FormatNumber(wxString &s, wxChar thousandsSep, wxChar decSep, + wxString grouping); }; diff --git a/misc/languages/langtabl.txt b/misc/languages/langtabl.txt index 5264da8ceb2d..932bfafb2491 100644 --- a/misc/languages/langtabl.txt +++ b/misc/languages/langtabl.txt @@ -63,6 +63,7 @@ wxLANGUAGE_ENGLISH_CANADA en_CA LANG_ENGLISH SUBLANG_ENGLISH_C wxLANGUAGE_ENGLISH_CARIBBEAN en_CB LANG_ENGLISH SUBLANG_ENGLISH_CARIBBEAN LTR "English (Caribbean)" wxLANGUAGE_ENGLISH_DENMARK en_DK - - LTR "English (Denmark)" wxLANGUAGE_ENGLISH_EIRE en_IE LANG_ENGLISH SUBLANG_ENGLISH_EIRE LTR "English (Eire)" +wxLANGUAGE_ENGLISH_INDIA en_IN LANG_ENGLISH SUBLANG_ENGLISH_INDIA LTR "English (India)" wxLANGUAGE_ENGLISH_JAMAICA en_JM LANG_ENGLISH SUBLANG_ENGLISH_JAMAICA LTR "English (Jamaica)" wxLANGUAGE_ENGLISH_NEW_ZEALAND en_NZ LANG_ENGLISH SUBLANG_ENGLISH_NZ LTR "English (New Zealand)" wxLANGUAGE_ENGLISH_PHILIPPINES en_PH LANG_ENGLISH SUBLANG_ENGLISH_PHILIPPINES LTR "English (Philippines)" diff --git a/src/common/intl.cpp b/src/common/intl.cpp index 0ba14ae52475..5b7d4886b5d2 100644 --- a/src/common/intl.cpp +++ b/src/common/intl.cpp @@ -69,6 +69,7 @@ #include "wx/osx/core/cfstring.h" #include #include + #include #include #endif @@ -1639,6 +1640,10 @@ GetInfoFromLCID(LCID lcid, } break; + case wxLOCALE_GROUPING: + if ( ::GetLocaleInfo(lcid, LOCALE_SGROUPING, buf, WXSIZEOF(buf)) ) + str = buf; + break; case wxLOCALE_SHORT_DATE_FMT: case wxLOCALE_LONG_DATE_FMT: case wxLOCALE_TIME_FMT: @@ -1712,6 +1717,9 @@ wxString wxLocale::GetInfo(wxLocaleInfo index, wxLocaleCategory cat) case wxLOCALE_DECIMAL_POINT: return "."; + case wxLOCALE_GROUPING: + return "3;0"; + case wxLOCALE_SHORT_DATE_FMT: return "%m/%d/%y"; @@ -1770,6 +1778,42 @@ wxString wxLocale::GetInfo(wxLocaleInfo index, wxLocaleCategory WXUNUSED(cat)) cfstr = (CFStringRef) CFLocaleGetValue(userLocaleRef, kCFLocaleDecimalSeparator); break; + case wxLOCALE_GROUPING: + { + wxCFRef numFormatterRef( + CFNumberFormatterCreate(NULL, userLocaleRef, kCFNumberFormatterDecimalStyle)); + CFNumberRef size = (CFNumberRef) CFNumberFormatterCopyProperty( + numFormatterRef, kCFNumberFormatterGroupingSize); + CFNumberRef secSize = (CFNumberRef) CFNumberFormatterCopyProperty( + numFormatterRef, kCFNumberFormatterSecondaryGroupingSize); + // Convert the size and secondary size to char and create the grouping string + char s, ss; + if (CFNumberGetValue(size, kCFNumberCharType, &s)) + { + if (CFNumberGetValue(secSize, kCFNumberCharType, &ss) && ss != s) + { + s += '0'; + ss += '0'; + const char gstr[] = {s, ';', ss, ';', '0', '\0'}; + cfstr = CFStringCreateWithCString( + NULL, &gstr[0], kCFStringEncodingASCII); + } + else + { + s += '0'; + const char gstr[] = {s, ';', '0', '\0'}; + cfstr = CFStringCreateWithCString( + NULL, &gstr[0], kCFStringEncodingASCII); + } + } + else + { + // No grouping + cfstr = CFStringCreateWithCString(NULL, "", kCFStringEncodingASCII); + } + } + break; + case wxLOCALE_SHORT_DATE_FMT: case wxLOCALE_LONG_DATE_FMT: case wxLOCALE_DATE_TIME_FMT: @@ -1900,6 +1944,32 @@ wxString GetDateFormatFromLangInfo(wxLocaleInfo index) } // anonymous namespace +// Convert a grouping format string returned by localeconv() to +// a standardized format. Our standard format is the one used for +// Windows SGROUPING. Here we convert char sized integers to ASCII +// characters. That is, for example '\3' becomes '3'. We also add +// a ';' delimiter between each number. A '\0' or a CHAR_MAX +// signifies the end of the argument string. If the argument string +// ends with a '\0' then we insert a '0' into the return string. +// The return string will be NULL terminated. + +/* static */ +wxString wxLocale::StandardizeGroupingString(const wxString& g) +{ + wxString s; + int i; + for (i = 0; g[i] != '\0' && g[i] != CHAR_MAX; i++) + { + s.Append((char)((int)g[i] + (int)'0')); + s.Append(';'); + } + if (g[i] == '\0') + s.Append('0'); + else + s.RemoveLast(); // Remove extra ; + return s; +} + /* static */ wxString wxLocale::GetInfo(wxLocaleInfo index, wxLocaleCategory cat) { @@ -1930,6 +2000,15 @@ wxString wxLocale::GetInfo(wxLocaleInfo index, wxLocaleCategory cat) wxFAIL_MSG( "invalid wxLocaleCategory" ); break; + case wxLOCALE_GROUPING: + if ( cat == wxLOCALE_CAT_NUMBER ) + return StandardizeGroupingString(lc->grouping); + else if ( cat == wxLOCALE_CAT_MONEY ) + return StandardizeGroupingString(lc->mon_grouping); + + wxFAIL_MSG( "invalid wxLocaleCategory" ); + break; + case wxLOCALE_SHORT_DATE_FMT: case wxLOCALE_LONG_DATE_FMT: case wxLOCALE_DATE_TIME_FMT: diff --git a/src/common/languageinfo.cpp b/src/common/languageinfo.cpp index 9929b0f656c3..d514055f4064 100644 --- a/src/common/languageinfo.cpp +++ b/src/common/languageinfo.cpp @@ -388,6 +388,9 @@ #ifndef SUBLANG_ENGLISH_ZIMBABWE #define SUBLANG_ENGLISH_ZIMBABWE SUBLANG_DEFAULT #endif +#ifndef SUBLANG_ENGLISH_INDIA +#define SUBLANG_ENGLISH_INDIA SUBLANG_DEFAULT +#endif #ifndef SUBLANG_FRENCH #define SUBLANG_FRENCH SUBLANG_DEFAULT #endif @@ -628,6 +631,7 @@ void wxLocale::InitLanguagesDB() LNG(wxLANGUAGE_ENGLISH_SOUTH_AFRICA, "en_ZA", LANG_ENGLISH , SUBLANG_ENGLISH_SOUTH_AFRICA , wxLayout_LeftToRight, "English (South Africa)") LNG(wxLANGUAGE_ENGLISH_TRINIDAD, "en_TT", LANG_ENGLISH , SUBLANG_ENGLISH_TRINIDAD , wxLayout_LeftToRight, "English (Trinidad)") LNG(wxLANGUAGE_ENGLISH_ZIMBABWE, "en_ZW", LANG_ENGLISH , SUBLANG_ENGLISH_ZIMBABWE , wxLayout_LeftToRight, "English (Zimbabwe)") + LNG(wxLANGUAGE_ENGLISH_INDIA, "en_IN", LANG_ENGLISH , SUBLANG_ENGLISH_INDIA , wxLayout_LeftToRight, "English (India)") LNG(wxLANGUAGE_ESPERANTO, "eo" , 0 , 0 , wxLayout_LeftToRight, "Esperanto") LNG(wxLANGUAGE_ESTONIAN, "et_EE", LANG_ESTONIAN , SUBLANG_DEFAULT , wxLayout_LeftToRight, "Estonian") LNG(wxLANGUAGE_FAEROESE, "fo_FO", LANG_FAEROESE , SUBLANG_DEFAULT , wxLayout_LeftToRight, "Faeroese") diff --git a/src/common/numformatter.cpp b/src/common/numformatter.cpp index 369ec15b5c3b..b56b7836504c 100644 --- a/src/common/numformatter.cpp +++ b/src/common/numformatter.cpp @@ -171,6 +171,44 @@ bool wxNumberFormatter::GetThousandsSeparatorIfUsed(wxChar *sep) #endif // wxUSE_INTL/!wxUSE_INTL } +bool wxNumberFormatter::GetThousandsSeparatorAndGroupingIfUsed(wxChar *sep, wxString *gr) +{ +#if wxUSE_INTL + static wxChar s_thousandsSeparator = 0; + static wxString s_grouping; + static LocaleId s_localeUsedForInit; + + if ( s_localeUsedForInit.NotInitializedOrHasChanged() ) + { + const wxString + s = wxLocale::GetInfo(wxLOCALE_THOUSANDS_SEP, wxLOCALE_CAT_NUMBER); + if ( s.length() == 1 ) + { + s_thousandsSeparator = s[0]; + s_grouping = wxLocale::GetInfo(wxLOCALE_GROUPING, wxLOCALE_CAT_NUMBER); + } + //else: Unlike above it's perfectly fine for the thousands separator to + // be empty if grouping is not used, so just leave it as 0. + } + + if ( !s_thousandsSeparator ) + return false; + + if ( sep ) + { + *sep = s_thousandsSeparator; + if ( gr ) + *gr = s_grouping; + } + + return true; +#else // !wxUSE_INTL + wxUnusedVar(sep); + wxUnusedVar(gr); + return false; +#endif // wxUSE_INTL/!wxUSE_INTL +} + // ---------------------------------------------------------------------------- // Conversion to string and helpers // ---------------------------------------------------------------------------- @@ -221,10 +259,20 @@ void wxNumberFormatter::AddThousandsSeparators(wxString& s) return; wxChar thousandsSep; - if ( !GetThousandsSeparatorIfUsed(&thousandsSep) ) + wxChar decSep; + wxString grouping; + if ( !GetThousandsSeparatorAndGroupingIfUsed(&thousandsSep, &grouping) ) return; - size_t pos = s.find(GetDecimalSeparator()); + decSep = GetDecimalSeparator(); + wxNumberFormatter::FormatNumber( + s, thousandsSep, decSep, grouping); +} + +void wxNumberFormatter::FormatNumber( + wxString& s, wxChar thousandsSep, wxChar decSep, wxString grouping) +{ + size_t pos = s.find(decSep); if ( pos == wxString::npos ) { // Start grouping at the end of an integer number. @@ -235,17 +283,31 @@ 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; + // We get the grouping style from locale. This is represented by a ';' + // delimited character array where each element is the number of digits + // in a group starting from the right of the number. If the last element + // in the grouping is a 0 then the last but one element is the number + // used for grouping the remaining digits. - while ( pos > start + GROUP_LEN ) + size_t i = 0; + while((grouping[i] != '\0') && (grouping[i] != '0')) + { + if (grouping[i] != ';') + { + if (pos <= start + (size_t)(grouping[i] - '0')) + break; + pos -= (size_t)(grouping[i] - '0'); + s.insert(pos, thousandsSep); + } + i++; + } + if ( grouping[i] == '0' && i > 0 ) { - pos -= GROUP_LEN; - s.insert(pos, thousandsSep); + while ( pos > start + (size_t)(grouping[i - 2] - '0')) + { + pos -= (size_t)(grouping[i - 2] - '0'); + s.insert(pos, thousandsSep); + } } } diff --git a/tests/strings/numformatter.cpp b/tests/strings/numformatter.cpp index 9a583f8a7c51..42a59b916067 100644 --- a/tests/strings/numformatter.cpp +++ b/tests/strings/numformatter.cpp @@ -20,7 +20,7 @@ #include "wx/intl.h" // ---------------------------------------------------------------------------- -// test class +// test class (UK locale) // ---------------------------------------------------------------------------- class NumFormatterTestCase : public CppUnit::TestCase @@ -347,3 +347,544 @@ void NumFormatterTestCase::DoubleFromString() CPPUNIT_ASSERT( wxNumberFormatter::FromString("123456789.012", &d) ); CPPUNIT_ASSERT_EQUAL( 123456789.012, d ); } + +// ---------------------------------------------------------------------------- +// test class (India locale) +// ---------------------------------------------------------------------------- +class NumFormatterAlternateTestCase : public CppUnit::TestCase +{ +public: + NumFormatterAlternateTestCase() { m_locale = NULL; } + + virtual void setUp() wxOVERRIDE + { + // We need to use a locale with known decimal point and which uses the + // thousands separator for the tests to make sense. + m_locale = new wxLocale(wxLANGUAGE_ENGLISH_INDIA, + wxLOCALE_DONT_LOAD_DEFAULT); + if ( !m_locale->IsOk() ) + tearDown(); + else + { + wxChar thousandsSep; + wxString grouping; + if ( wxNumberFormatter::GetThousandsSeparatorAndGroupingIfUsed(&thousandsSep, &grouping) ) + { + // On some Windows systems the grouping is "3;2" which is incorrect + // Skip this test in that case + if ( !grouping.IsSameAs(wxString("3;2;0")) ) + tearDown(); + } + else + tearDown(); + } + } + + virtual void tearDown() wxOVERRIDE + { + delete m_locale; + m_locale = NULL; + } + +private: + CPPUNIT_TEST_SUITE( NumFormatterAlternateTestCase ); + CPPUNIT_TEST( LongToString ); +#ifdef wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + CPPUNIT_TEST( LongLongToString ); +#endif // wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + CPPUNIT_TEST( DoubleToString ); + CPPUNIT_TEST( NoTrailingZeroes ); + CPPUNIT_TEST( LongFromString ); +#ifdef wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + CPPUNIT_TEST( LongLongFromString ); +#endif // wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + CPPUNIT_TEST( DoubleFromString ); + CPPUNIT_TEST_SUITE_END(); + + void LongToString(); +#ifdef wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + void LongLongToString(); +#endif // wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + void DoubleToString(); + void NoTrailingZeroes(); + void LongFromString(); +#ifdef wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + void LongLongFromString(); +#endif // wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + void DoubleFromString(); + + wxLocale *m_locale; + + wxDECLARE_NO_COPY_CLASS(NumFormatterAlternateTestCase); +}; + +// register in the unnamed registry so that these tests are run by default +CPPUNIT_TEST_SUITE_REGISTRATION( NumFormatterAlternateTestCase ); + +// also include in its own registry so that these tests can be run alone +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( NumFormatterAlternateTestCase, "NumFormatterAlternateTestCase" ); + +// ---------------------------------------------------------------------------- +// tests themselves +// ---------------------------------------------------------------------------- + +void NumFormatterAlternateTestCase::LongToString() +{ + if ( !m_locale ) + return; + + CPPUNIT_ASSERT_EQUAL( "1", wxNumberFormatter::ToString( 1L)); + CPPUNIT_ASSERT_EQUAL( "-1", wxNumberFormatter::ToString( -1L)); + CPPUNIT_ASSERT_EQUAL( "12", wxNumberFormatter::ToString( 12L)); + CPPUNIT_ASSERT_EQUAL( "-12", wxNumberFormatter::ToString( -12L)); + CPPUNIT_ASSERT_EQUAL( "123", wxNumberFormatter::ToString( 123L)); + CPPUNIT_ASSERT_EQUAL( "-123", wxNumberFormatter::ToString( -123L)); + CPPUNIT_ASSERT_EQUAL( "1,234", wxNumberFormatter::ToString( 1234L)); + CPPUNIT_ASSERT_EQUAL( "-1,234", wxNumberFormatter::ToString( -1234L)); + CPPUNIT_ASSERT_EQUAL( "12,345", wxNumberFormatter::ToString( 12345L)); + CPPUNIT_ASSERT_EQUAL( "-12,345", wxNumberFormatter::ToString( -12345L)); + CPPUNIT_ASSERT_EQUAL( "1,23,456", wxNumberFormatter::ToString( 123456L)); + CPPUNIT_ASSERT_EQUAL( "-1,23,456", wxNumberFormatter::ToString( -123456L)); + CPPUNIT_ASSERT_EQUAL( "12,34,567", wxNumberFormatter::ToString( 1234567L)); + CPPUNIT_ASSERT_EQUAL( "-12,34,567", wxNumberFormatter::ToString( -1234567L)); + CPPUNIT_ASSERT_EQUAL( "1,23,45,678", wxNumberFormatter::ToString( 12345678L)); + CPPUNIT_ASSERT_EQUAL("-1,23,45,678", wxNumberFormatter::ToString( -12345678L)); + CPPUNIT_ASSERT_EQUAL("12,34,56,789", wxNumberFormatter::ToString( 123456789L)); +} + +#ifdef wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + +void NumFormatterAlternateTestCase::LongLongToString() +{ + if ( !m_locale ) + return; + + CPPUNIT_ASSERT_EQUAL( "1", wxNumberFormatter::ToString(wxLL( 1))); + CPPUNIT_ASSERT_EQUAL( "12", wxNumberFormatter::ToString(wxLL( 12))); + CPPUNIT_ASSERT_EQUAL( "123", wxNumberFormatter::ToString(wxLL( 123))); + CPPUNIT_ASSERT_EQUAL( "1,234", wxNumberFormatter::ToString(wxLL( 1234))); + CPPUNIT_ASSERT_EQUAL( "12,345", wxNumberFormatter::ToString(wxLL( 12345))); + CPPUNIT_ASSERT_EQUAL( "1,23,456", wxNumberFormatter::ToString(wxLL( 123456))); + CPPUNIT_ASSERT_EQUAL( "12,34,567", wxNumberFormatter::ToString(wxLL( 1234567))); + CPPUNIT_ASSERT_EQUAL( "1,23,45,678", wxNumberFormatter::ToString(wxLL( 12345678))); + CPPUNIT_ASSERT_EQUAL("12,34,56,789", wxNumberFormatter::ToString(wxLL( 123456789))); +} + +#endif // wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + +void NumFormatterAlternateTestCase::DoubleToString() +{ + if ( !m_locale ) + return; + + CPPUNIT_ASSERT_EQUAL("1.0", wxNumberFormatter::ToString(1., 1)); + CPPUNIT_ASSERT_EQUAL("0.123456", wxNumberFormatter::ToString(0.123456, 6)); + CPPUNIT_ASSERT_EQUAL("1.234567", wxNumberFormatter::ToString(1.234567, 6)); + CPPUNIT_ASSERT_EQUAL("12.34567", wxNumberFormatter::ToString(12.34567, 5)); + CPPUNIT_ASSERT_EQUAL("123.4567", wxNumberFormatter::ToString(123.4567, 4)); + CPPUNIT_ASSERT_EQUAL("1,234.56", wxNumberFormatter::ToString(1234.56, 2)); + CPPUNIT_ASSERT_EQUAL("12,345.6", wxNumberFormatter::ToString(12345.6, 1)); + CPPUNIT_ASSERT_EQUAL("12,345.6", wxNumberFormatter::ToString(12345.6, 1)); + CPPUNIT_ASSERT_EQUAL("12,34,56,789.0", + wxNumberFormatter::ToString(123456789., 1)); + CPPUNIT_ASSERT_EQUAL("12,34,56,789.012", + wxNumberFormatter::ToString(123456789.012, 3)); + CPPUNIT_ASSERT_EQUAL("12,345", + wxNumberFormatter::ToString(12345.012, -1)); + CPPUNIT_ASSERT_EQUAL("-123.1230", + wxNumberFormatter::ToString(-123.123, 4, wxNumberFormatter::Style_None)); + CPPUNIT_ASSERT_EQUAL("0.0", + wxNumberFormatter::ToString(0.02, 1, wxNumberFormatter::Style_None)); + CPPUNIT_ASSERT_EQUAL("-0.0", + wxNumberFormatter::ToString(-0.02, 1, wxNumberFormatter::Style_None)); +} + +void NumFormatterAlternateTestCase::NoTrailingZeroes() +{ + WX_ASSERT_FAILS_WITH_ASSERT + ( + wxNumberFormatter::ToString(123L, wxNumberFormatter::Style_NoTrailingZeroes) + ); + + if ( !m_locale ) + return; + + CPPUNIT_ASSERT_EQUAL + ( + "123.000", + wxNumberFormatter::ToString(123., 3) + ); + + CPPUNIT_ASSERT_EQUAL + ( + "123", + wxNumberFormatter::ToString(123., 3, wxNumberFormatter::Style_NoTrailingZeroes) + ); + + CPPUNIT_ASSERT_EQUAL + ( + "123", + wxNumberFormatter::ToString(123., 9, wxNumberFormatter::Style_NoTrailingZeroes) + ); + + CPPUNIT_ASSERT_EQUAL + ( + "123.456", + wxNumberFormatter::ToString(123.456, 3, wxNumberFormatter::Style_NoTrailingZeroes) + ); + + CPPUNIT_ASSERT_EQUAL + ( + "123.456000000", + wxNumberFormatter::ToString(123.456, 9) + ); + + CPPUNIT_ASSERT_EQUAL + ( + "123.456", + wxNumberFormatter::ToString(123.456, 9, wxNumberFormatter::Style_NoTrailingZeroes) + ); + + CPPUNIT_ASSERT_EQUAL + ( + "123.12", + wxNumberFormatter::ToString(123.123, 2, wxNumberFormatter::Style_NoTrailingZeroes) + ); + + CPPUNIT_ASSERT_EQUAL + ( + "123", + wxNumberFormatter::ToString(123.123, 0, wxNumberFormatter::Style_NoTrailingZeroes) + ); + + CPPUNIT_ASSERT_EQUAL + ( + "0", + wxNumberFormatter::ToString(-0.000123, 3, wxNumberFormatter::Style_NoTrailingZeroes) + ); + + CPPUNIT_ASSERT_EQUAL + ( + "123", + wxNumberFormatter::ToString(123., -1, wxNumberFormatter::Style_NoTrailingZeroes) + ); + + CPPUNIT_ASSERT_EQUAL + ( + "1e-120", + wxNumberFormatter::ToString(1e-120, -1, wxNumberFormatter::Style_NoTrailingZeroes) + ); +} + +void NumFormatterAlternateTestCase::LongFromString() +{ + if ( !m_locale ) + return; + + WX_ASSERT_FAILS_WITH_ASSERT + ( + wxNumberFormatter::FromString("123", static_cast(0)) + ); + + long l; + CPPUNIT_ASSERT( !wxNumberFormatter::FromString("", &l) ); + CPPUNIT_ASSERT( !wxNumberFormatter::FromString("foo", &l) ); + CPPUNIT_ASSERT( !wxNumberFormatter::FromString("1.234", &l) ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("123", &l) ); + CPPUNIT_ASSERT_EQUAL( 123, l ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("1234", &l) ); + CPPUNIT_ASSERT_EQUAL( 1234, l ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("1,234", &l) ); + CPPUNIT_ASSERT_EQUAL( 1234, l ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("12,345", &l) ); + CPPUNIT_ASSERT_EQUAL( 12345, l ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("1,23,456", &l) ); + CPPUNIT_ASSERT_EQUAL( 123456, l ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("1,234,567", &l) ); + CPPUNIT_ASSERT_EQUAL( 1234567, l ); +} + +#ifdef wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + +void NumFormatterAlternateTestCase::LongLongFromString() +{ + if ( !m_locale ) + return; + + WX_ASSERT_FAILS_WITH_ASSERT + ( + wxNumberFormatter::FromString("123", static_cast(0)) + ); + + wxLongLong_t l; + CPPUNIT_ASSERT( !wxNumberFormatter::FromString("", &l) ); + CPPUNIT_ASSERT( !wxNumberFormatter::FromString("foo", &l) ); + CPPUNIT_ASSERT( !wxNumberFormatter::FromString("1.234", &l) ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("123", &l) ); + CPPUNIT_ASSERT_EQUAL( 123, l ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("1234", &l) ); + CPPUNIT_ASSERT_EQUAL( 1234, l ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("1,234", &l) ); + CPPUNIT_ASSERT_EQUAL( 1234, l ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("12,345", &l) ); + CPPUNIT_ASSERT_EQUAL( 12345, l ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("1,23,456", &l) ); + CPPUNIT_ASSERT_EQUAL( 123456, l ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("12,34,567", &l) ); + CPPUNIT_ASSERT_EQUAL( 1234567, l ); +} + +#endif // wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + +void NumFormatterAlternateTestCase::DoubleFromString() +{ + if ( !m_locale ) + return; + + WX_ASSERT_FAILS_WITH_ASSERT + ( + wxNumberFormatter::FromString("123", static_cast(0)) + ); + + double d; + CPPUNIT_ASSERT( !wxNumberFormatter::FromString("", &d) ); + CPPUNIT_ASSERT( !wxNumberFormatter::FromString("bar", &d) ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("123", &d) ); + CPPUNIT_ASSERT_EQUAL( 123., d ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("123.456789012", &d) ); + CPPUNIT_ASSERT_EQUAL( 123.456789012, d ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("1,234.56789012", &d) ); + CPPUNIT_ASSERT_EQUAL( 1234.56789012, d ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("12,345.6789012", &d) ); + CPPUNIT_ASSERT_EQUAL( 12345.6789012, d ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("1,23,456.789012", &d) ); + CPPUNIT_ASSERT_EQUAL( 123456.789012, d ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("1,234,567.89012", &d) ); + CPPUNIT_ASSERT_EQUAL( 1234567.89012, d ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("1,23,45,678.9012", &d) ); + CPPUNIT_ASSERT_EQUAL( 12345678.9012, d ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("12,34,56,789.012", &d) ); + CPPUNIT_ASSERT_EQUAL( 123456789.012, d ); + + CPPUNIT_ASSERT( wxNumberFormatter::FromString("123456789.012", &d) ); + CPPUNIT_ASSERT_EQUAL( 123456789.012, d ); +} + +//-------------------------------------------------------------------------- +// Test case for wxNumberFormatter::FormatNumber +//-------------------------------------------------------------------------- + +class FormatNumberTestCase : public CppUnit::TestCase +{ +public: + FormatNumberTestCase() {} +private: + static const wxChar thousandsSep = ',', decSep = '.'; + static const wxString grouping_UK; + + static wxString FormatNumberAndReturn(wxString s); + + CPPUNIT_TEST_SUITE( FormatNumberTestCase ); + CPPUNIT_TEST( PositiveIntegers ); + CPPUNIT_TEST( NegativeIntegers ); + CPPUNIT_TEST( PositiveReals ); + CPPUNIT_TEST( NegativeReals ); + CPPUNIT_TEST_SUITE_END(); + + void PositiveIntegers(); + void NegativeIntegers(); + void PositiveReals(); + void NegativeReals(); +}; + +const wxString FormatNumberTestCase::grouping_UK = "3;0"; +wxString FormatNumberTestCase::FormatNumberAndReturn(wxString s) +{ + wxString ret = s.Clone(); + wxNumberFormatter::FormatNumber(ret, thousandsSep, decSep, grouping_UK); + return ret; +} + +// register in the unnamed registry so that these tests are run by default +CPPUNIT_TEST_SUITE_REGISTRATION( FormatNumberTestCase ); + +// also include in its own registry so that these tests can be run alone +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( FormatNumberTestCase, "FormatNumberTestCase" ); + +void FormatNumberTestCase::PositiveIntegers() +{ + CPPUNIT_ASSERT(FormatNumberAndReturn( "0").IsSameAs( "0")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "1").IsSameAs( "1")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "12").IsSameAs( "12")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "123").IsSameAs( "123")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "1234").IsSameAs( "1,234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "12345").IsSameAs( "12,345")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "123456").IsSameAs( "123,456")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "1234567").IsSameAs( "1,234,567")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "12345678").IsSameAs( "12,345,678")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "123456789").IsSameAs( "123,456,789")); + CPPUNIT_ASSERT(FormatNumberAndReturn("1234567890").IsSameAs("1,234,567,890")); +} + +void FormatNumberTestCase::NegativeIntegers() +{ + CPPUNIT_ASSERT(FormatNumberAndReturn( "-1").IsSameAs( "-1")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-12").IsSameAs( "-12")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-123").IsSameAs( "-123")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-1234").IsSameAs( "-1,234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-12345").IsSameAs( "-12,345")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-123456").IsSameAs( "-123,456")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-1234567").IsSameAs( "-1,234,567")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-12345678").IsSameAs( "-12,345,678")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-123456789").IsSameAs( "-123,456,789")); + CPPUNIT_ASSERT(FormatNumberAndReturn("-1234567890").IsSameAs("-1,234,567,890")); +} + +void FormatNumberTestCase::PositiveReals() +{ + CPPUNIT_ASSERT(FormatNumberAndReturn( "0.1234").IsSameAs( "0.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "1.1234").IsSameAs( "1.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "12.1234").IsSameAs( "12.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "123.1234").IsSameAs( "123.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "1234.1234").IsSameAs( "1,234.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "12345.1234").IsSameAs( "12,345.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "123456.1234").IsSameAs( "123,456.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "1234567.1234").IsSameAs( "1,234,567.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "12345678.1234").IsSameAs( "12,345,678.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "123456789.1234").IsSameAs( "123,456,789.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn("1234567890.1234").IsSameAs("1,234,567,890.1234")); +} + +void FormatNumberTestCase::NegativeReals() +{ + CPPUNIT_ASSERT(FormatNumberAndReturn( "-1.1234").IsSameAs( "-1.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-12.1234").IsSameAs( "-12.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-123.1234").IsSameAs( "-123.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-1234.1234").IsSameAs( "-1,234.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-12345.1234").IsSameAs( "-12,345.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-123456.1234").IsSameAs( "-123,456.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-1234567.1234").IsSameAs( "-1,234,567.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-12345678.1234").IsSameAs( "-12,345,678.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-123456789.1234").IsSameAs( "-123,456,789.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn("-1234567890.1234").IsSameAs("-1,234,567,890.1234")); +} + +//-------------------------------------------------------------------------- +// Alternate test case for wxNumberFormatter::FormatNumber with grouping in +// Indian format i.e, "3;2;0" +//-------------------------------------------------------------------------- + +class FormatNumberAlternateTestCase : public CppUnit::TestCase +{ +public: + FormatNumberAlternateTestCase() {} +private: + static const wxChar thousandsSep = ',', decSep = '.'; + static const wxString grouping_India; + + static wxString FormatNumberAndReturn(wxString s); + + CPPUNIT_TEST_SUITE( FormatNumberAlternateTestCase ); + CPPUNIT_TEST( PositiveIntegers ); + CPPUNIT_TEST( NegativeIntegers ); + CPPUNIT_TEST( PositiveReals ); + CPPUNIT_TEST( NegativeReals ); + CPPUNIT_TEST_SUITE_END(); + + void PositiveIntegers(); + void NegativeIntegers(); + void PositiveReals(); + void NegativeReals(); +}; + +const wxString FormatNumberAlternateTestCase::grouping_India = "3;2;0"; +wxString FormatNumberAlternateTestCase::FormatNumberAndReturn(wxString s) +{ + wxString ret = s.Clone(); + wxNumberFormatter::FormatNumber(ret, thousandsSep, decSep, grouping_India); + return ret; +} + +// register in the unnamed registry so that these tests are run by default +CPPUNIT_TEST_SUITE_REGISTRATION( FormatNumberAlternateTestCase ); + +// also include in its own registry so that these tests can be run alone +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( FormatNumberAlternateTestCase, "FormatNumberAlternateTestCase" ); + +void FormatNumberAlternateTestCase::PositiveIntegers() +{ + CPPUNIT_ASSERT(FormatNumberAndReturn( "0").IsSameAs( "0")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "1").IsSameAs( "1")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "12").IsSameAs( "12")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "123").IsSameAs( "123")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "1234").IsSameAs( "1,234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "12345").IsSameAs( "12,345")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "123456").IsSameAs( "1,23,456")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "1234567").IsSameAs( "12,34,567")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "12345678").IsSameAs( "1,23,45,678")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "123456789").IsSameAs( "12,34,56,789")); + CPPUNIT_ASSERT(FormatNumberAndReturn("1234567890").IsSameAs("1,23,45,67,890")); +} + +void FormatNumberAlternateTestCase::NegativeIntegers() +{ + CPPUNIT_ASSERT(FormatNumberAndReturn( "-1").IsSameAs( "-1")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-12").IsSameAs( "-12")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-123").IsSameAs( "-123")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-1234").IsSameAs( "-1,234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-12345").IsSameAs( "-12,345")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-123456").IsSameAs( "-1,23,456")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-1234567").IsSameAs( "-12,34,567")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-12345678").IsSameAs( "-1,23,45,678")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-123456789").IsSameAs( "-12,34,56,789")); + CPPUNIT_ASSERT(FormatNumberAndReturn("-1234567890").IsSameAs("-1,23,45,67,890")); +} + +void FormatNumberAlternateTestCase::PositiveReals() +{ + CPPUNIT_ASSERT(FormatNumberAndReturn( "0.1234").IsSameAs( "0.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "1.1234").IsSameAs( "1.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "12.1234").IsSameAs( "12.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "123.1234").IsSameAs( "123.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "1234.1234").IsSameAs( "1,234.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "12345.1234").IsSameAs( "12,345.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "123456.1234").IsSameAs( "1,23,456.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "1234567.1234").IsSameAs( "12,34,567.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "12345678.1234").IsSameAs( "1,23,45,678.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "123456789.1234").IsSameAs( "12,34,56,789.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn("1234567890.1234").IsSameAs("1,23,45,67,890.1234")); +} + +void FormatNumberAlternateTestCase::NegativeReals() +{ + CPPUNIT_ASSERT(FormatNumberAndReturn( "-1.1234").IsSameAs( "-1.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-12.1234").IsSameAs( "-12.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-123.1234").IsSameAs( "-123.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-1234.1234").IsSameAs( "-1,234.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-12345.1234").IsSameAs( "-12,345.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-123456.1234").IsSameAs( "-1,23,456.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-1234567.1234").IsSameAs( "-12,34,567.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-12345678.1234").IsSameAs( "-1,23,45,678.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn( "-123456789.1234").IsSameAs( "-12,34,56,789.1234")); + CPPUNIT_ASSERT(FormatNumberAndReturn("-1234567890.1234").IsSameAs("-1,23,45,67,890.1234")); +}