basic/qa/basic_coverage/da-DK/test_ccur_da_DK_locale.bas |   27 ++++
 basic/qa/basic_coverage/test_ccur_method.bas             |   69 +++++++++-
 basic/qa/basic_coverage/zh-CN/test_ccur_zh_CN_locale.bas |   27 ++++
 basic/source/sbx/sbxcurr.cxx                             |  100 ++++++---------
 4 files changed, 162 insertions(+), 61 deletions(-)

New commits:
commit 9cc8457abcae57c7f9de6e0fbca1fbc2a0cc9892
Author:     Jonathan Clark <[email protected]>
AuthorDate: Fri Dec 15 23:09:19 2023 -0700
Commit:     Andreas Heinisch <[email protected]>
CommitDate: Sat Dec 23 09:12:24 2023 +0100

    tdf#128122 Updated BASIC CCur to reuse SvNumberFormatter
    
    Previously, BASIC CCur used a custom, single-purpose currency string
    parser which did not properly accommodate the user's locale setting.
    This change replaces the custom parser with SvNumberFormatter, which
    does correctly respect system locale.
    
    Change-Id: I179915eb080e876e5e550dd350fdb86d7fa2bf4c
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/160848
    Tested-by: Jenkins
    Reviewed-by: Andreas Heinisch <[email protected]>

diff --git a/basic/qa/basic_coverage/da-DK/test_ccur_da_DK_locale.bas 
b/basic/qa/basic_coverage/da-DK/test_ccur_da_DK_locale.bas
new file mode 100644
index 000000000000..52b8d3b6f1aa
--- /dev/null
+++ b/basic/qa/basic_coverage/da-DK/test_ccur_da_DK_locale.bas
@@ -0,0 +1,27 @@
+'
+' This file is part of the LibreOffice project.
+'
+' This Source Code Form is subject to the terms of the Mozilla Public
+' License, v. 2.0. If a copy of the MPL was not distributed with this
+' file, You can obtain one at http://mozilla.org/MPL/2.0/.
+'
+
+Option Explicit
+
+Function doUnitTest as String
+    TestUtil.TestInit
+    verify_testCCurDaDKLocale
+    doUnitTest = TestUtil.GetResult()
+End Function
+
+Sub verify_testCCurDaDKLocale
+    On Error GoTo errorHandler
+
+    ' tdf#141050 - characteristic test for CCur() with the da_DK locale
+    TestUtil.AssertEqual(CCur("75,50"), 75.5, "CCur(75,50)")
+    TestUtil.AssertEqual(CCur("75,50 kr."), 75.5, "CCur(75,50 kr.)")
+
+    Exit Sub
+errorHandler:
+    TestUtil.ReportErrorHandler("verify_testCCurDaDKLocale", Err, Error$, Erl)
+End Sub
diff --git a/basic/qa/basic_coverage/test_ccur_method.bas 
b/basic/qa/basic_coverage/test_ccur_method.bas
index cd700cad3c8e..c42dcb938590 100644
--- a/basic/qa/basic_coverage/test_ccur_method.bas
+++ b/basic/qa/basic_coverage/test_ccur_method.bas
@@ -9,16 +9,73 @@
 Option Explicit
 
 Function doUnitTest as String
+    TestUtil.TestInit
+    verify_testCCur
+    doUnitTest = TestUtil.GetResult()
+End Function
 
-    doUnitTest = "FAIL"
+Sub verify_testCCur
+    On Error GoTo errorHandler
 
     ' CCUR
-    if (CCur("100") <> 100) Then Exit Function
+    TestUtil.AssertEqual(CCur("100"), 100, "CCur(100)")
+
     ' tdf#141050 - passing a number with + sign
-    if (CCur("+100") <> 100) Then Exit Function
+    TestUtil.AssertEqual(CCur("+100"), 100, "CCur(100)")
     ' tdf#141050 - passing a number with - sign
-    if (CCur("-100") <> -100) Then Exit Function
+    TestUtil.AssertEqual(CCur("-100"), -100, "CCur(-100)")
 
-    doUnitTest = "OK"
+    ' tdf#128122 - verify en_US locale currency format behavior
+    TestUtil.AssertEqual(CCur("$100"), 100, "CCur($100)")
+    TestUtil.AssertEqual(CCur("$1.50"), 1.5, "CCur($1.50)")
 
-End Function
+    verify_testCCurUnderflow
+    verify_testCCurOverflow
+    verify_testCCurInvalidFormat
+
+    Exit Sub
+errorHandler:
+    TestUtil.ReportErrorHandler("verify_testCCur", Err, Error$, Erl)
+End Sub
+
+sub verify_testCCurUnderflow
+    On Error GoTo underflowHandler
+
+    ' tdf$128122 - test underflow condition
+    CCur("-9223372036854775809")
+    TestUtil.Assert(False, "verify_testCCur", "underflow error not raised")
+
+    Exit Sub
+underflowHandler:
+    If(Err <> 6) Then
+        TestUtil.Assert(False, "verify_testCCur", "underflow error incorrect 
type")
+    Endif
+End Sub
+
+sub verify_testCCurOverflow
+    On Error GoTo overflowHandler
+
+    ' tdf$128122 - test overflow condition
+    CCur("9223372036854775808")
+    TestUtil.Assert(False, "verify_testCCur", "overflow error not raised")
+
+    Exit Sub
+overflowHandler:
+    If(Err <> 6) Then
+        TestUtil.Assert(False, "verify_testCCur", "overflow error incorrect 
type")
+    Endif
+End Sub
+
+sub verify_testCCurInvalidFormat
+    On Error GoTo invalidFormatHandler
+
+    ' tdf$128122 - test invalid format in en_US locale
+    CCur("75,50 kr")
+    TestUtil.Assert(False, "verify_testCCur", "invalid format error not 
raised")
+
+    Exit Sub
+invalidFormatHandler:
+    If(Err <> 13) Then
+        TestUtil.Assert(False, "verify_testCCur", "invalid format error 
incorrect type")
+    Endif
+End Sub
diff --git a/basic/qa/basic_coverage/zh-CN/test_ccur_zh_CN_locale.bas 
b/basic/qa/basic_coverage/zh-CN/test_ccur_zh_CN_locale.bas
new file mode 100644
index 000000000000..38a084e36c7f
--- /dev/null
+++ b/basic/qa/basic_coverage/zh-CN/test_ccur_zh_CN_locale.bas
@@ -0,0 +1,27 @@
+'
+' This file is part of the LibreOffice project.
+'
+' This Source Code Form is subject to the terms of the Mozilla Public
+' License, v. 2.0. If a copy of the MPL was not distributed with this
+' file, You can obtain one at http://mozilla.org/MPL/2.0/.
+'
+
+Option Explicit
+
+Function doUnitTest as String
+    TestUtil.TestInit
+    verify_testCCurZhCNLocale
+    doUnitTest = TestUtil.GetResult()
+End Function
+
+Sub verify_testCCurZhCNLocale
+    On Error GoTo errorHandler
+
+    ' tdf#141050 - characteristic test for CCur() with the zh_CN locale
+    TestUtil.AssertEqual(CCur("75.50"), 75.5, "CCur(75.50)")
+    TestUtil.AssertEqual(CCur("¥75.50"), 75.5, "CCur(¥75.50)")
+
+    Exit Sub
+errorHandler:
+    TestUtil.ReportErrorHandler("verify_testCCurZhCNLocale", Err, Error$, Erl)
+End Sub
diff --git a/basic/source/sbx/sbxcurr.cxx b/basic/source/sbx/sbxcurr.cxx
index 54b00102dd49..ca67977a3a56 100644
--- a/basic/source/sbx/sbxcurr.cxx
+++ b/basic/source/sbx/sbxcurr.cxx
@@ -22,6 +22,11 @@
 #include <basic/sberrors.hxx>
 #include <basic/sbxvar.hxx>
 #include <o3tl/string_view.hxx>
+#include <svl/numformat.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <sbintern.hxx>
+#include <runtime.hxx>
 #include "sbxconv.hxx"
 
 
@@ -85,76 +90,61 @@ static OUString ImpCurrencyToString( sal_Int64 rVal )
     return aAbsStr;
 }
 
-
-static sal_Int64 ImpStringToCurrency( std::u16string_view rStr )
+static sal_Int64 ImpStringToCurrency(const rtl::OUString& rStr)
 {
-
-    sal_Int32   nFractDigit = 4;
-
-    sal_Unicode const cDeciPnt = '.';
-    sal_Unicode const c1000Sep = ',';
-
-    // lets use the existing string number conversions
-    // there is a performance impact here ( multiple string copies )
-    // but better I think than a home brewed string parser, if we need a parser
-    // we should share some existing ( possibly from calc is there a currency
-    // conversion there ? #TODO check )
-
-    std::u16string_view sTmp = o3tl::trim( rStr );
-    auto p = sTmp.begin();
-    auto pEnd = sTmp.end();
-
-    // normalise string number by removing thousand & decimal point separators
-    OUStringBuffer sNormalisedNumString( static_cast<sal_Int32>(sTmp.size()) + 
nFractDigit );
-
-    if ( p != pEnd && (*p == '-'  || *p == '+' ) )
-        sNormalisedNumString.append( *p++ );
-
-    while ( p != pEnd && *p >= '0' && *p <= '9' )
+    LanguageType eLangType = 
Application::GetSettings().GetLanguageTag().getLanguageType();
+    std::shared_ptr<SvNumberFormatter> pFormatter;
+    if (GetSbData()->pInst)
+    {
+        pFormatter = GetSbData()->pInst->GetNumberFormatter();
+    }
+    else
     {
-        sNormalisedNumString.append( *p++ );
-        // #TODO in vba mode set runtime error when a space ( or other )
-        // illegal character is found
-        if( p != pEnd && *p == c1000Sep )
-            p++;
+        sal_uInt32 n; // Dummy
+        pFormatter = SbiInstance::PrepareNumberFormatter(/*date index*/ n, 
/*time index*/ n,
+                                                         /*date time index*/ 
n);
     }
 
-    bool bRoundUp = false;
+    // Passing a locale index switches IsNumberFormat() to use that locale,
+    // in case the formatter wasn't default-created with it.
+    sal_uInt32 nIndex = pFormatter->GetStandardIndex(eLangType);
 
-    if( p != pEnd && *p == cDeciPnt )
+    double fResult = 0.0;
+    bool bSuccess = pFormatter->IsNumberFormat(rStr, nIndex, fResult);
+    if (bSuccess)
     {
-        p++;
-        while( nFractDigit && p != pEnd && *p >= '0' && *p <= '9' )
+        SvNumFormatType nType = pFormatter->GetType(nIndex);
+        if (!(nType & (SvNumFormatType::CURRENCY | SvNumFormatType::NUMBER)))
         {
-            sNormalisedNumString.append( *p++ );
-            nFractDigit--;
+            bSuccess = false;
         }
-        // Consume trailing content
-        // Round up if necessary
-        if( p != pEnd && *p >= '5' && *p <= '9' )
-            bRoundUp = true;
-        while( p != pEnd && *p >= '0' && *p <= '9' )
-            p++;
     }
-    // can we raise error here ? ( previous behaviour was more forgiving )
-    // so... not sure that could break existing code, let's see if anyone
-    // complains.
 
-    if ( p != pEnd )
-        SbxBase::SetError( ERRCODE_BASIC_CONVERSION );
-    while( nFractDigit )
+    if (!bSuccess)
     {
-        sNormalisedNumString.append( '0' );
-        nFractDigit--;
+        SbxBase::SetError(ERRCODE_BASIC_CONVERSION);
     }
 
-    sal_Int64 result = o3tl::toInt64(sNormalisedNumString);
+    sal_Int64 nRes = 0;
+    const auto fShiftedResult = fResult * CURRENCY_FACTOR;
+    if (fShiftedResult + 0.5 > static_cast<double>(SAL_MAX_INT64)
+        || fShiftedResult - 0.5 < static_cast<double>(SAL_MIN_INT64))
+    {
+        nRes = SAL_MAX_INT64;
+        if (fShiftedResult - 0.5 < static_cast<double>(SAL_MIN_INT64))
+        {
+            nRes = SAL_MIN_INT64;
+        }
 
-    if ( bRoundUp )
-        ++result;
-    return result;
-}
+        SbxBase::SetError(ERRCODE_BASIC_MATH_OVERFLOW);
+    }
+    else
+    {
+        nRes = ImpDoubleToCurrency(fResult);
+    }
 
+    return nRes;
+}
 
 sal_Int64 ImpGetCurrency( const SbxValues* p )
 {

Reply via email to