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 <andreas.heinisch@yahoo.de>
This commit is contained in:
Jonathan Clark
2023-12-23 09:12:24 +01:00
committed by Andreas Heinisch
parent 90b12c9bad
commit 9cc8457abc
4 changed files with 162 additions and 61 deletions
@@ -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
+63 -6
View File
@@ -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
@@ -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
+45 -55
View File
@@ -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,77 +90,62 @@ 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)
{
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++;
pFormatter = GetSbData()->pInst->GetNumberFormatter();
}
else
{
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 )
{
SbxValues aTmp;