    /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
    /*
     * 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/.
     *
     * This file incorporates work covered by the following license notice:
     *
     *   Licensed to the Apache Software Foundation (ASF) under one or more
     *   contributor license agreements. See the NOTICE file distributed
     *   with this work for additional information regarding copyright
     *   ownership. The ASF licenses this file to you under the Apache
     *   License, Version 2.0 (the "License"); you may not use this file
     *   except in compliance with the License. You may obtain a copy of
     *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
     */

    #include <config_features.h>

    #include <vcl/errcode.hxx>
    #include <unotools/resmgr.hxx>
    #include <basic/sbx.hxx>
    #include "sbxconv.hxx"

    #include <unotools/syslocale.hxx>

    #include <stdlib.h>

    #include <vcl/svapp.hxx>
    #include <vcl/settings.hxx>

    #include <math.h>
    #include <string.h>

    #include "sbxres.hxx"
    #include <sbxbase.hxx>
    #include <sbintern.hxx>
    #include <basic/sbxfac.hxx>
    #include <basic/sbxform.hxx>

    #include <date.hxx>
    #include <runtime.hxx>
    #include <strings.hrc>

    #include <rtl/strbuf.hxx>
    #include <rtl/character.hxx>
    #include <sal/log.hxx>
    #include <svl/zforlist.hxx>

    #include <o3tl/make_unique.hxx>

    void ImpGetIntntlSep( sal_Unicode& rcDecimalSep, sal_Unicode& rcThousandSep, sal_Unicode& rcDecimalSepAlt )
    {
        SvtSysLocale aSysLocale;
        const LocaleDataWrapper& rData = aSysLocale.GetLocaleData();
        rcDecimalSep = rData.getNumDecimalSep()[0];
        rcThousandSep = rData.getNumThousandSep()[0];
        rcDecimalSepAlt = rData.getNumDecimalSepAlt().toChar();
    }

    /** NOTE: slightly differs from strchr() in that it does not consider the
        terminating NULL character to be part of the string and returns bool
        instead of pointer, if character is 0 returns false.
     */
    bool ImpStrChr( const sal_Unicode* p, sal_Unicode c )
    {
        if (!c)
            return false;
        while (*p)
        {
            if (*p++ == c)
                return true;
        }
        return false;
    }

    // scanning a string according to BASIC-conventions
    // but exponent may also be a D, so data type is SbxDOUBLE
    // conversion error if data type is fixed and it doesn't fit

    ErrCode ImpScan( const OUString& rWSrc, double& nVal, SbxDataType& rType,
                      sal_uInt16* pLen, bool bOnlyIntntl )
    {
        sal_Unicode cIntntlDecSep, cIntntlGrpSep, cIntntlDecSepAlt;
        sal_Unicode cNonIntntlDecSep = '.';
        if( bOnlyIntntl )
        {
            ImpGetIntntlSep( cIntntlDecSep, cIntntlGrpSep, cIntntlDecSepAlt );
            cNonIntntlDecSep = cIntntlDecSep;
            // Ensure that the decimal separator alternative is really one.
            if (cIntntlDecSepAlt && cIntntlDecSepAlt == cNonIntntlDecSep)
                cIntntlDecSepAlt = 0;
        }
        else
        {
            cIntntlDecSep = cNonIntntlDecSep;
            cIntntlGrpSep = 0;  // no group separator accepted in non-i18n
            cIntntlDecSepAlt = 0;
        }

        const sal_Unicode* const pStart = rWSrc.getStr();
        const sal_Unicode* p = pStart;
        OUStringBuffer aBuf( rWSrc.getLength());
        bool bRes = true;
        bool bMinus = false;
        nVal = 0;
        SbxDataType eScanType = SbxSINGLE;
        while( *p == ' ' || *p == '\t' )
            p++;
        if( *p == '-' )
        {
            p++;
            bMinus = true;
        }
        if( rtl::isAsciiDigit( *p ) || ((*p == cNonIntntlDecSep || *p == cIntntlDecSep ||
                        (cIntntlDecSep && *p == cIntntlGrpSep) || (cIntntlDecSepAlt && *p == cIntntlDecSepAlt)) &&
                    rtl::isAsciiDigit( *(p+1) )))
        {
            // tdf#118442: Whitespace and minus are skipped; store the position to calculate index
            const sal_Unicode* const pDigitsStart = p;
            short exp = 0;
            short decsep = 0;
            short ndig = 0;
            short ncdig = 0;    // number of digits after decimal point
            OUStringBuffer aSearchStr("0123456789DEde");
            aSearchStr.append(cNonIntntlDecSep);
            if( cIntntlDecSep != cNonIntntlDecSep )
                aSearchStr.append(cIntntlDecSep);
            if( cIntntlDecSepAlt && cIntntlDecSepAlt != cNonIntntlDecSep )
                aSearchStr.append(cIntntlDecSepAlt);
            if( bOnlyIntntl )
                aSearchStr.append(cIntntlGrpSep);
            const sal_Unicode* const pSearchStr = aSearchStr.getStr();
            const sal_Unicode pDdEe[] = { 'D', 'd', 'E', 'e', 0 };
            while( ImpStrChr( pSearchStr, *p ) )
            {
                aBuf.append( *p );
                if( bOnlyIntntl && *p == cIntntlGrpSep )
                {
                    p++;
                    continue;
                }
                if( *p == cNonIntntlDecSep || *p == cIntntlDecSep || (cIntntlDecSepAlt && *p == cIntntlDecSepAlt) )
                {
                    // Use the separator that is passed to stringToDouble()
                    aBuf[p - pDigitsStart] = cIntntlDecSep;
                    p++;
                    if( ++decsep > 1 )
                        continue;
                }
                else if( ImpStrChr( pDdEe, *p ) )
                {
                    if( ++exp > 1 )
                    {
                        p++;
                        continue;
                    }
                    if( *p == 'D' || *p == 'd' )
                        eScanType = SbxDOUBLE;
                    aBuf[p - pDigitsStart] = 'E';
                    p++;
                    if (*p == '+')
                        ++p;
                    else if (*p == '-')
                    {
                        aBuf.append('-');
                        ++p;
                    }
                }
                else
                {
                    p++;
                    if( decsep && !exp )
                        ncdig++;
                }
                if( !exp )
                    ndig++;
            }

            if( decsep > 1 || exp > 1 )
                bRes = false;

            OUString aBufStr( aBuf.makeStringAndClear());
            rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok;
            sal_Int32 nParseEnd = 0;
            nVal = rtl::math::stringToDouble( aBufStr, cIntntlDecSep, cIntntlGrpSep, &eStatus, &nParseEnd );
            if( eStatus != rtl_math_ConversionStatus_Ok || nParseEnd != aBufStr.getLength() )
                bRes = false;

            if( !decsep && !exp )
            {
                if( nVal >= SbxMININT && nVal <= SbxMAXINT )
                    eScanType = SbxINTEGER;
                else if( nVal >= SbxMINLNG && nVal <= SbxMAXLNG )
                    eScanType = SbxLONG;
            }

            ndig = ndig - decsep;
            // too many numbers for SINGLE?
            if( ndig > 15 || ncdig > 6 )
                eScanType = SbxDOUBLE;

            // type detection?
            const sal_Unicode pTypes[] = { '%', '!', '&', '#', 0 };
            if( ImpStrChr( pTypes, *p ) )
                p++;
        }
        // hex/octal number? read in and convert:
        else if( *p == '&' )
        {
            p++;
            eScanType = SbxLONG;
            OUString aCmp( "0123456789ABCDEFabcdef" );
            char base = 16;
            char ndig = 8;
            switch( *p++ )
            {
                case 'O':
                case 'o':
                    aCmp = "01234567";
                    base = 8;
                    ndig = 11;
                    break;
                case 'H':
                case 'h':
                    break;
                default :
                    bRes = false;
            }
            const sal_Unicode* const pCmp = aCmp.getStr();
            while( rtl::isAsciiAlphanumeric( *p ) )    /* XXX: really munge all alnum also when error? */
            {
                sal_Unicode ch = *p;
                if( ImpStrChr( pCmp, ch ) )
                {
                    if (ch > 0x60)
                        ch -= 0x20;     // convert ASCII lower to upper case
                    aBuf.append( ch );
                }
                else
                    bRes = false;
                p++;
            }
            OUString aBufStr( aBuf.makeStringAndClear());
            sal_Int32 l = 0;
            for( const sal_Unicode* q = aBufStr.getStr(); bRes && *q; q++ )
            {
                int i = *q - '0';
                if( i > 9 )
                    i -= 7;     // 'A'-'0' = 17 => 10, ...
                l = ( l * base ) + i;
                if( !ndig-- )
                    bRes = false;
            }
            if( *p == '&' )
                p++;
            nVal = static_cast<double>(l);
            if( l >= SbxMININT && l <= SbxMAXINT )
                eScanType = SbxINTEGER;
        }
    #if HAVE_FEATURE_SCRIPTING
        else if ( SbiRuntime::isVBAEnabled() )
        {
            SAL_WARN("basic", "Reporting error converting");
            return ERRCODE_BASIC_CONVERSION;
        }
    #endif
        if( pLen )
            *pLen = static_cast<sal_uInt16>( p - pStart );
        if( !bRes )
            return ERRCODE_BASIC_CONVERSION;
        if( bMinus )
            nVal = -nVal;
        rType = eScanType;
        return ERRCODE_NONE;
    }

    // port for CDbl in the Basic
    ErrCode SbxValue::ScanNumIntnl( const OUString& rSrc, double& nVal, bool bSingle )
    {
        SbxDataType t;
        sal_uInt16 nLen = 0;
        ErrCode nRetError = ImpScan( rSrc, nVal, t, &nLen,
            /*bOnlyIntntl*/true );
        // read completely?
        if( nRetError == ERRCODE_NONE && nLen != rSrc.getLength() )
        {
            nRetError = ERRCODE_BASIC_CONVERSION;
        }
        if( bSingle )
        {
            SbxValues aValues( nVal );
            nVal = static_cast<double>(ImpGetSingle( &aValues ));    // here error at overflow
        }
        return nRetError;
    }

    static const double roundArray[] = {
        5.0e+0, 0.5e+0, 0.5e-1, 0.5e-2, 0.5e-3, 0.5e-4, 0.5e-5, 0.5e-6, 0.5e-7,
        0.5e-8, 0.5e-9, 0.5e-10,0.5e-11,0.5e-12,0.5e-13,0.5e-14,0.5e-15 };

    /*
    |*
    |*  void myftoa( double, char *, short, short, bool, bool )
    |*
    |*  description:        conversion double --> ASCII
    |*  parameters:         double              the number
    |*                      char *              target buffer
    |*                      short               number of positions after decimal point
    |*                      short               range of the exponent ( 0=no E )
    |*                      bool                true: with 1000-separators
    |*                      bool                true: output without formatting
    |*
     */

    static void myftoa( double nNum, char * pBuf, short nPrec, short nExpWidth,
                        sal_Unicode cForceThousandSep )
    {

        short nExp = 0;
        short nDig = nPrec + 1;
        short nDec;                         // number of positions before decimal point
        int i;

        sal_Unicode cDecimalSep, cThousandSep, cDecimalSepAlt;
        ImpGetIntntlSep( cDecimalSep, cThousandSep, cDecimalSepAlt );
        if( cForceThousandSep )
            cThousandSep = cForceThousandSep;

        // compute exponent
        nExp = 0;
        if( nNum > 0.0 )
        {
            while( nNum <   1.0 )
            {
                nNum *= 10.0;
                nExp--;
            }
            while( nNum >= 10.0 )
            {
                nNum /= 10.0;
                nExp++;
            }
        }
        if( !nPrec )
            nDig = nExp + 1;

        // round number
        if( (nNum += roundArray [std::min<short>( nDig, 16 )] ) >= 10.0 )
        {
            nNum = 1.0;
            ++nExp;
            if( !nExpWidth ) ++nDig;
        }

        // determine positions before decimal point
        if( !nExpWidth )
        {
            if( nExp < 0 )
            {
                // #41691: also a 0 at bFix
                *pBuf++ = '0';
                if( nPrec ) *pBuf++ = static_cast<char>(cDecimalSep);
                i = -nExp - 1;
                if( nDig <= 0 ) i = nPrec;
                while( i-- )    *pBuf++ = '0';
                nDec = 0;
            }
            else
                nDec = nExp+1;
        }
        else
            nDec = 1;

        // output number
        if( nDig > 0 )
        {
            int digit;
            for( i = 0 ; ; ++i )
            {
                if( i < 16 )
                {
                    digit = static_cast<int>(nNum);
                    *pBuf++ = sal::static_int_cast< char >(digit + '0');
                    nNum =( nNum - digit ) * 10.0;
                } else
                    *pBuf++ = '0';
                if( --nDig == 0 ) break;
                if( nDec )
                {
                    nDec--;
                    if( !nDec )
                        *pBuf++ = static_cast<char>(cDecimalSep);
                }
            }
        }

        // output exponent
        if( nExpWidth )
        {
            if( nExpWidth < 3 ) nExpWidth = 3;
            nExpWidth -= 2;
            *pBuf++ = 'E';
            *pBuf++ =( nExp < 0 ) ?( (nExp = -nExp ), '-' ) : '+';
            while( nExpWidth > 3 )
            {
                *pBuf++ = '0';
                nExpWidth--;
            }
            if( nExp >= 100 || nExpWidth == 3 )
            {
                *pBuf++ = sal::static_int_cast< char >(nExp/100 + '0');
                nExp %= 100;
            }
            if( nExp/10 || nExpWidth >= 2 )
                *pBuf++ = sal::static_int_cast< char >(nExp/10 + '0');
            *pBuf++ = sal::static_int_cast< char >(nExp%10 + '0');
        }
        *pBuf = 0;
    }

    // The number is prepared unformattedly with the given number of
    // NK-positions. A leading minus is added if applicable.
    // This routine is public because it's also used by the Put-functions
    // in the class SbxImpSTRING.

    void ImpCvtNum( double nNum, short nPrec, OUString& rRes, bool bCoreString )
    {
        char *q;
        char cBuf[ 40 ], *p = cBuf;

        sal_Unicode cDecimalSep, cThousandSep, cDecimalSepAlt;
        ImpGetIntntlSep( cDecimalSep, cThousandSep, cDecimalSepAlt );
        if( bCoreString )
            cDecimalSep = '.';

        if( nNum < 0.0 ) {
            nNum = -nNum;
            *p++ = '-';
        }
        double dMaxNumWithoutExp = (nPrec == 6) ? 1E6 : 1E14;
        myftoa( nNum, p, nPrec,( nNum &&( nNum < 1E-1 || nNum >= dMaxNumWithoutExp ) ) ? 4:0,
            cDecimalSep );
        // remove trailing zeros
        for( p = cBuf; *p &&( *p != 'E' ); p++ ) {}
        q = p; p--;
        while( nPrec && *p == '0' )
        {
            nPrec--;
            p--;
        }
        if( *p == cDecimalSep ) p--;
        while( *q ) *++p = *q++;
        *++p = 0;
        rRes = OUString::createFromAscii( cBuf );
    }

    bool ImpConvStringExt( OUString& rSrc, SbxDataType eTargetType )
    {
        bool bChanged = false;
        OUString aNewString;

        // only special cases are handled, nothing on default
        switch( eTargetType )
        {
            // consider international for floating point
            case SbxSINGLE:
            case SbxDOUBLE:
            case SbxCURRENCY:
            {
                sal_Unicode cDecimalSep, cThousandSep, cDecimalSepAlt;
                ImpGetIntntlSep( cDecimalSep, cThousandSep, cDecimalSepAlt );
                aNewString = rSrc;

                if( cDecimalSep != '.' || (cDecimalSepAlt && cDecimalSepAlt != '.') )
                {
                    sal_Int32 nPos = aNewString.indexOf( cDecimalSep );
                    if( nPos == -1 && cDecimalSepAlt )
                        nPos = aNewString.indexOf( cDecimalSepAlt );
                    if( nPos != -1 )
                    {
                        sal_Unicode* pStr = const_cast<sal_Unicode*>(aNewString.getStr());
                        pStr[nPos] = '.';
                        bChanged = true;
                    }
                }
                break;
            }

            // check as string in case of sal_Bool sal_True and sal_False
            case SbxBOOL:
            {
                if( rSrc.equalsIgnoreAsciiCase("true") )
                {
                    aNewString = OUString::number( SbxTRUE );
                    bChanged = true;
                }
                else if( rSrc.equalsIgnoreAsciiCase("false") )
                {
                    aNewString = OUString::number( SbxFALSE );
                    bChanged = true;
                }
                break;
            }
            default: break;
        }

        if( bChanged )
            rSrc = aNewString;
        return bChanged;
    }

    // formatted number output
    // the return value is the number of characters used
    // from the format

    static sal_uInt16 printfmtstr( const OUString& rStr, OUString& rRes, const OUString& rFmt )
    {
        OUStringBuffer aTemp;
        const sal_Unicode* pStr = rStr.getStr();
        const sal_Unicode* pFmtStart = rFmt.getStr();
        const sal_Unicode* pFmt = pFmtStart;

        switch( *pFmt )
        {
        case '!':
            aTemp.append(*pStr++);
            pFmt++;
            break;
        case '\\':
            do
            {
                aTemp.append( *pStr ? *pStr++ : u' ');
                pFmt++;
            }
            while( *pFmt && *pFmt != '\\' );
            aTemp.append(*pStr ? *pStr++ : u' ');
            pFmt++; break;
        case '&':
            aTemp = rStr;
            pFmt++; break;
        default:
            aTemp = rStr;
            break;
        }
        rRes = aTemp.makeStringAndClear();
        return static_cast<sal_uInt16>( pFmt - pFmtStart );
    }

    bool SbxValue::Scan( const OUString& rSrc, sal_uInt16* pLen )
    {
        ErrCode eRes = ERRCODE_NONE;
        if( !CanWrite() )
        {
            eRes = ERRCODE_BASIC_PROP_READONLY;
        }
        else
        {
            double n;
            SbxDataType t;
            eRes = ImpScan( rSrc, n, t, pLen, false );
            if( eRes == ERRCODE_NONE )
            {
                if( !IsFixed() )
                {
                    SetType( t );
                }
                PutDouble( n );
            }
        }
        if( eRes )
        {
            SetError( eRes );
            return false;
        }
        else
        {
            return true;
        }
    }

    std::locale BasResLocale()
    {
        return Translate::Create("sb");
    }

    OUString BasResId(const char *pId)
    {
        return Translate::get(pId, BasResLocale());
    }

    namespace
    {

    enum class VbaFormatType
    {
        Offset,      // standard number format
        UserDefined, // user defined number format
        Null
    };

    struct VbaFormatInfo
    {
        VbaFormatType meType;
        OUStringLiteral mpVbaFormat; // Format string in vba
        NfIndexTableOffset meOffset; // SvNumberFormatter format index, if meType = VbaFormatType::Offset
        const char* mpOOoFormat;     // if meType = VbaFormatType::UserDefined
    };

    #if HAVE_FEATURE_SCRIPTING
    const VbaFormatInfo pFormatInfoTable[] =
    {
        { VbaFormatType::Offset,      OUStringLiteral("Long Date"),   NF_DATE_SYSTEM_LONG,    nullptr },
        { VbaFormatType::UserDefined, OUStringLiteral("Medium Date"), NF_NUMBER_STANDARD,     "DD-MMM-YY" },
        { VbaFormatType::Offset,      OUStringLiteral("Short Date"),  NF_DATE_SYSTEM_SHORT,   nullptr },
        { VbaFormatType::UserDefined, OUStringLiteral("Long Time"),   NF_NUMBER_STANDARD,     "H:MM:SS AM/PM" },
        { VbaFormatType::Offset,      OUStringLiteral("Medium Time"), NF_TIME_HHMMAMPM,       nullptr },
        { VbaFormatType::Offset,      OUStringLiteral("Short Time"),  NF_TIME_HHMM,           nullptr },
        { VbaFormatType::Offset,      OUStringLiteral("ddddd"),       NF_DATE_SYSTEM_SHORT,   nullptr },
        { VbaFormatType::Offset,      OUStringLiteral("dddddd"),      NF_DATE_SYSTEM_LONG,    nullptr },
        { VbaFormatType::UserDefined, OUStringLiteral("ttttt"),       NF_NUMBER_STANDARD,     "H:MM:SS AM/PM" },
        { VbaFormatType::Offset,      OUStringLiteral("ww"),          NF_DATE_WW,             nullptr },
        { VbaFormatType::Null,        OUStringLiteral(""),            NF_INDEX_TABLE_ENTRIES, nullptr }
    };

    const VbaFormatInfo* getFormatInfo( const OUString& rFmt )
    {
        const VbaFormatInfo* pInfo = pFormatInfoTable;
        while( pInfo->meType != VbaFormatType::Null )
        {
            if( rFmt.equalsIgnoreAsciiCase( pInfo->mpVbaFormat ) )
                break;
            ++pInfo;
        }
        return pInfo;
    }
    #endif

    } // namespace

    #if HAVE_FEATURE_SCRIPTING
    #define VBAFORMAT_GENERALDATE       "General Date"
    #define VBAFORMAT_C                 "c"
    #define VBAFORMAT_N                 "n"
    #define VBAFORMAT_NN                "nn"
    #define VBAFORMAT_W                 "w"
    #define VBAFORMAT_Y                 "y"
    #define VBAFORMAT_LOWERCASE         "<"
    #define VBAFORMAT_UPPERCASE         ">"
    #endif

    void SbxValue::Format( OUString& rRes, const OUString* pFmt ) const
    {
        short nComma = 0;
        double d = 0;

        // pflin, It is better to use SvNumberFormatter to handle the date/time/number format.
        // the SvNumberFormatter output is mostly compatible with
        // VBA output besides the OOo-basic output
    #if HAVE_FEATURE_SCRIPTING
        if( pFmt && !SbxBasicFormater::isBasicFormat( *pFmt ) )
        {
            OUString aStr = GetOUString();

            SvtSysLocale aSysLocale;
            const CharClass& rCharClass = aSysLocale.GetCharClass();

            if( pFmt->equalsIgnoreAsciiCase( VBAFORMAT_LOWERCASE ) )
            {
                rRes = rCharClass.lowercase( aStr );
                return;
            }
            if( pFmt->equalsIgnoreAsciiCase( VBAFORMAT_UPPERCASE ) )
            {
                rRes = rCharClass.uppercase( aStr );
                return;
            }

            LanguageType eLangType = Application::GetSettings().GetLanguageTag().getLanguageType();
            std::shared_ptr<SvNumberFormatter> pFormatter;
            if (GetSbData()->pInst)
            {
                pFormatter = GetSbData()->pInst->GetNumberFormatter();
            }
            else
            {
                sal_uInt32 n;   // Dummy
                pFormatter = SbiInstance::PrepareNumberFormatter( n, n, n );
            }

            // Passing an index of a locale switches IsNumberFormat() to use that
            // locale in case the formatter wasn't default created with it.
            sal_uInt32 nIndex = pFormatter->GetStandardIndex( eLangType);
            double nNumber;
            Color* pCol;

            bool bSuccess = pFormatter->IsNumberFormat( aStr, nIndex, nNumber );

            // number format, use SvNumberFormatter to handle it.
            if( bSuccess )
            {
                sal_Int32 nCheckPos = 0;
                SvNumFormatType nType;
                OUString aFmtStr = *pFmt;
                const VbaFormatInfo* pInfo = getFormatInfo( aFmtStr );
                if( pInfo->meType != VbaFormatType::Null )
                {
                    if( pInfo->meType == VbaFormatType::Offset )
                    {
                        nIndex = pFormatter->GetFormatIndex( pInfo->meOffset, eLangType );
                    }
                    else
                    {
                        aFmtStr = OUString::createFromAscii(pInfo->mpOOoFormat);
                        pFormatter->PutandConvertEntry( aFmtStr, nCheckPos, nType, nIndex, LANGUAGE_ENGLISH, eLangType, true);
                    }
                    pFormatter->GetOutputString( nNumber, nIndex, rRes, &pCol );
                }
                else if( aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_GENERALDATE )
                        || aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_C ))
                {
                    if( nNumber <=-1.0 || nNumber >= 1.0 )
                    {
                        // short date
                        nIndex = pFormatter->GetFormatIndex( NF_DATE_SYSTEM_SHORT, eLangType );
                        pFormatter->GetOutputString( nNumber, nIndex, rRes, &pCol );

                        // long time
                        if( floor( nNumber ) != nNumber )
                        {
                            aFmtStr = "H:MM:SS AM/PM";
                            pFormatter->PutandConvertEntry( aFmtStr, nCheckPos, nType, nIndex, LANGUAGE_ENGLISH, eLangType, true);
                            OUString aTime;
                            pFormatter->GetOutputString( nNumber, nIndex, aTime, &pCol );
                            rRes += " " + aTime;
                        }
                    }
                    else
                    {
                        // long time only
                        aFmtStr = "H:MM:SS AM/PM";
                        pFormatter->PutandConvertEntry( aFmtStr, nCheckPos, nType, nIndex, LANGUAGE_ENGLISH, eLangType, true);
                        pFormatter->GetOutputString( nNumber, nIndex, rRes, &pCol );
                    }
                }
                else if( aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_N ) ||
                         aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_NN ))
                {
                    sal_Int32 nMin = implGetMinute( nNumber );
                    if( nMin < 10 && aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_NN ))
                    {
                        // Minute in two digits
                         sal_Unicode aBuf[2];
                         aBuf[0] = '0';
                         aBuf[1] = '0' + nMin;
                         rRes = OUString(aBuf, SAL_N_ELEMENTS(aBuf));
                    }
                    else
                    {
                        rRes = OUString::number(nMin);
                    }
                }
                else if( aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_W ))
                {
                    sal_Int32 nWeekDay = implGetWeekDay( nNumber );
                    rRes = OUString::number(nWeekDay);
                }
                else if( aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_Y ))
                {
                    sal_Int16 nYear = implGetDateYear( nNumber );
                    double dBaseDate;
                    implDateSerial( nYear, 1, 1, true, SbDateCorrection::None, dBaseDate );
                    sal_Int32 nYear32 = 1 + sal_Int32( nNumber - dBaseDate );
                    rRes = OUString::number(nYear32);
                }
                else
                {
                    pFormatter->PutandConvertEntry( aFmtStr, nCheckPos, nType, nIndex, LANGUAGE_ENGLISH, eLangType, true);
                    pFormatter->GetOutputString( nNumber, nIndex, rRes, &pCol );
                }

                return;
            }
        }
    #endif

        SbxDataType eType = GetType();
        switch( eType )
        {
        case SbxCHAR:
        case SbxBYTE:
        case SbxINTEGER:
        case SbxUSHORT:
        case SbxLONG:
        case SbxULONG:
        case SbxINT:
        case SbxUINT:
        case SbxNULL:       // #45929 NULL with a little cheating
            nComma = 0;     goto cvt;
        case SbxSINGLE:
            nComma = 6;     goto cvt;
        case SbxDOUBLE:
            nComma = 14;

        cvt:
            if( eType != SbxNULL )
            {
                d = GetDouble();
            }
            // #45355 another point to jump in for isnumeric-String
        cvt2:
            if( pFmt )
            {
                SbxAppData& rAppData = GetSbxData_Impl();

                LanguageType eLangType = Application::GetSettings().GetLanguageTag().getLanguageType();
                if( rAppData.pBasicFormater )
                {
                    if( rAppData.eBasicFormaterLangType != eLangType )
                    {
                        rAppData.pBasicFormater.reset();
                    }
                }
                rAppData.eBasicFormaterLangType = eLangType;

                if( !rAppData.pBasicFormater )
                {
                    SvtSysLocale aSysLocale;
                    const LocaleDataWrapper& rData = aSysLocale.GetLocaleData();
                    sal_Unicode cComma = rData.getNumDecimalSep()[0];
                    sal_Unicode c1000  = rData.getNumThousandSep()[0];
                    const OUString& aCurrencyStrg = rData.getCurrSymbol();

                    // initialize the Basic-formater help object:
                    // get resources for predefined output
                    // of the Format()-command, e. g. for "On/Off"
                    OUString aOnStrg = BasResId(STR_BASICKEY_FORMAT_ON);
                    OUString aOffStrg = BasResId(STR_BASICKEY_FORMAT_OFF);
                    OUString aYesStrg = BasResId(STR_BASICKEY_FORMAT_YES);
                    OUString aNoStrg = BasResId(STR_BASICKEY_FORMAT_NO);
                    OUString aTrueStrg = BasResId(STR_BASICKEY_FORMAT_TRUE);
                    OUString aFalseStrg = BasResId(STR_BASICKEY_FORMAT_FALSE);
                    OUString aCurrencyFormatStrg = BasResId(STR_BASICKEY_FORMAT_CURRENCY);

                    rAppData.pBasicFormater = o3tl::make_unique<SbxBasicFormater>(
                                                                    cComma,c1000,aOnStrg,aOffStrg,
                                                                    aYesStrg,aNoStrg,aTrueStrg,aFalseStrg,
                                                                    aCurrencyStrg,aCurrencyFormatStrg );
                }
                // Remark: For performance reasons there's only ONE BasicFormater-
                //    object created and 'stored', so that the expensive resource-
                //    loading is saved (for country-specific predefined outputs,
                //    e. g. "On/Off") and the continuous string-creation
                //    operations, too.
                // BUT: therefore this code is NOT multithreading capable!

                // here are problems with ;;;Null because this method is only
                // called, if SbxValue is a number!!!
                // in addition rAppData.pBasicFormater->BasicFormatNull( *pFmt ); could be called!
                if( eType != SbxNULL )
                {
                    rRes = rAppData.pBasicFormater->BasicFormat( d ,*pFmt );
                }
                else
                {
                    rRes = SbxBasicFormater::BasicFormatNull( *pFmt );
                }

            }
            else
            {
                OUString aTmpString( rRes );
                ImpCvtNum( GetDouble(), nComma, aTmpString );
                rRes = aTmpString;
            }
            break;
        case SbxSTRING:
            if( pFmt )
            {
                // #45355 converting if numeric
                if( IsNumericRTL() )
                {
                    ScanNumIntnl( GetOUString(), d );
                    goto cvt2;
                }
                else
                {
                    printfmtstr( GetOUString(), rRes, *pFmt );
                }
            }
            else
            {
                rRes = GetOUString();
            }
            break;
        default:
            rRes = GetOUString();
        }
    }

    /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

↑ V793 It is odd that the result of the 'nExp / 10' statement is a part of the condition. Perhaps, this statement should have been compared with something else.