/* -*- 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 <tools/date.hxx>
#include <basic/sbxvar.hxx>
#include <basic/sbuno.hxx>
#include <osl/process.h>
#include <vcl/dibtools.hxx>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <vcl/sound.hxx>
#include <vcl/wintypes.hxx>
#include <vcl/stdtext.hxx>
#include <vcl/weld.hxx>
#include <basic/sbx.hxx>
#include <svl/zforlist.hxx>
#include <rtl/character.hxx>
#include <rtl/math.hxx>
#include <tools/urlobj.hxx>
#include <osl/time.h>
#include <unotools/charclass.hxx>
#include <unotools/ucbstreamhelper.hxx>
#include <unotools/wincodepage.hxx>
#include <tools/wldcrd.hxx>
#include <i18nlangtag/lang.h>
#include <rtl/string.hxx>
#include <sal/log.hxx>
#include <comphelper/DirectoryHelper.hxx>
 
#include <runtime.hxx>
#include <sbunoobj.hxx>
#include <osl/file.hxx>
#include <errobject.hxx>
 
#include <comphelper/string.hxx>
#include <comphelper/processfactory.hxx>
 
#include <com/sun/star/uno/Sequence.hxx>
#include <com/sun/star/util/DateTime.hpp>
#include <com/sun/star/lang/Locale.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/ucb/SimpleFileAccess.hpp>
#include <com/sun/star/script/XErrorQuery.hpp>
#include <ooo/vba/VbStrConv.hpp>
#include <ooo/vba/VbTriState.hpp>
#include <com/sun/star/bridge/oleautomation/XAutomationObject.hpp>
#include <memory>
#include <random>
#include <string_view>
#include <o3tl/char16_t2wchar_t.hxx>
 
// include search util
#include <com/sun/star/i18n/Transliteration.hpp>
#include <com/sun/star/util/SearchAlgorithms2.hpp>
#include <i18nutil/searchopt.hxx>
#include <unotools/textsearch.hxx>
#include <svl/numformat.hxx>
 
#include <date.hxx>
#include <sbstdobj.hxx>
#include <rtlproto.hxx>
#include <image.hxx>
#include <iosys.hxx>
#include "ddectrl.hxx"
#include <sbintern.hxx>
#include <basic/vbahelper.hxx>
 
#include <vector>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
 
#include <sbobjmod.hxx>
#include <sbxmod.hxx>
 
#ifdef _WIN32
#include <prewin.h>
#include <direct.h>
#include <io.h>
#include <postwin.h>
#else
#include <unistd.h>
#endif
 
#include <vcl/TypeSerializer.hxx>
 
using namespace comphelper;
using namespace osl;
using namespace com::sun::star;
using namespace com::sun::star::lang;
using namespace com::sun::star::uno;
 
static sal_Int32 GetDayDiff(const Date& rDate) { return rDate - Date(1899'12'30); }
 
#if HAVE_FEATURE_SCRIPTING
 
static sal_Int32 nanoSecToMilliSec(sal_Int64 nNanoSeconds)
{
    // Rounding nanoseconds to milliseconds precision to avoid comparison inaccuracies
    return o3tl::convert(nNanoSeconds, 1, tools::Time::nanoPerMilli);
}
 
static void FilterWhiteSpace( OUString& rStr )
{
    if (rStr.isEmpty())
    {
        return;
    }
    OUStringBuffer aRet;
 
    for (sal_Int32 i = 0; i < rStr.getLength(); ++i)
    {
        sal_Unicode cChar = rStr[i];
        if ((cChar != ' ') && (cChar != '\t') &&
           (cChar != '\n') && (cChar != '\r'))
        {
            aRet.append(cChar);
        }
    }
 
    rStr = aRet.makeStringAndClear();
}
 
static const CharClass& GetCharClass()
{
    static CharClass aCharClass( Application::GetSettings().GetLanguageTag() );
    return aCharClass;
}
 
static bool isFolder( FileStatus::Type aType )
{
    return ( aType == FileStatus::Directory || aType == FileStatus::Volume );
}
 
 
//*** UCB file access ***
 
// Converts possibly relative paths to absolute paths
// according to the setting done by ChDir/ChDrive
OUString getFullPath( const OUString& aRelPath )
{
    OUString aFileURL;
 
    // #80204 Try first if it already is a valid URL
    INetURLObject aURLObj( aRelPath );
    aFileURL = aURLObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
 
    if( aFileURL.isEmpty() )
    {
        File::getFileURLFromSystemPath( aRelPath, aFileURL );
    }
 
    return aFileURL;
}
 
// TODO: -> SbiGlobals
static uno::Reference< ucb::XSimpleFileAccess3 > const & getFileAccess()
{
    static uno::Reference< ucb::XSimpleFileAccess3 > xSFI = ucb::SimpleFileAccess::create( comphelper::getProcessComponentContext() );
    return xSFI;
}
 
 
// Properties and methods lie down the return value at the Get (bPut = sal_False) in the
// element 0 of the Argv; the value of element 0 is saved at Put (bPut = sal_True)
 
// CreateObject( class )
 
void SbRtl_CreateObject(StarBASIC * pBasic, SbxArray & rPar, bool)
{
    if( rPar.Count() < 2 )
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    OUString aClass(rPar.Get(1)->GetOUString());
    SbxObjectRef p = SbxBase::CreateObject( aClass );
    if( !p.is() )
        return StarBASIC::Error( ERRCODE_BASIC_CANNOT_LOAD );
 
    // Convenience: enter BASIC as parent
    p->SetParent( pBasic );
    rPar.Get(0)->PutObject(p.get());
}
 
// Error( n )
 
void SbRtl_Error(StarBASIC * pBasic, SbxArray & rPar, bool)
{
    if( !pBasic )
        return StarBASIC::Error( ERRCODE_BASIC_INTERNAL_ERROR );
 
    OUString aErrorMsg;
    ErrCode nErr = ERRCODE_NONE;
    sal_Int32 nCode = 0;
    if (rPar.Count() == 1)
    {
        nErr = StarBASIC::GetErrBasic();
        aErrorMsg = StarBASIC::GetErrorMsg();
    }
    else
    {
        nCode = rPar.Get(1)->GetLong();
        if( nCode > 65535 )
        {
            StarBASIC::Error( ERRCODE_BASIC_CONVERSION );
        }
        else
        {
            nErr = StarBASIC::GetSfxFromVBError( static_cast<sal_uInt16>(nCode) );
        }
    }
    bool bVBA = SbiRuntime::isVBAEnabled();
    OUString tmpErrMsg;
    if( bVBA && !aErrorMsg.isEmpty())
    {
        tmpErrMsg = aErrorMsg;
    }
    else
    {
        StarBASIC::MakeErrorText( nErr, aErrorMsg );
        tmpErrMsg = StarBASIC::GetErrorText();
    }
    // If this rtlfunc 'Error' passed an errcode the same as the active Err Objects's
    // current err then  return the description for the error message if it is set
    // ( complicated isn't it ? )
    if (bVBA && rPar.Count() > 1)
    {
        uno::Reference< ooo::vba::XErrObject > xErrObj( SbxErrObject::getUnoErrObject() );
        if ( xErrObj.is() && xErrObj->getNumber() == nCode && !xErrObj->getDescription().isEmpty() )
        {
            tmpErrMsg = xErrObj->getDescription();
        }
    }
    rPar.Get(0)->PutString(tmpErrMsg);
}
 
// Sinus
 
void SbRtl_Sin(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    SbxVariableRef pArg = rPar.Get(1);
    rPar.Get(0)->PutDouble(sin(pArg->GetDouble()));
}
 
 
void SbRtl_Cos(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    SbxVariableRef pArg = rPar.Get(1);
    rPar.Get(0)->PutDouble(cos(pArg->GetDouble()));
}
 
 
void SbRtl_Atn(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    SbxVariableRef pArg = rPar.Get(1);
    rPar.Get(0)->PutDouble(atan(pArg->GetDouble()));
}
 
 
void SbRtl_Abs(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    SbxVariableRef pArg = rPar.Get(1);
    rPar.Get(0)->PutDouble(fabs(pArg->GetDouble()));
}
 
 
void SbRtl_Asc(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    SbxVariableRef pArg = rPar.Get(1);
    OUString aStr( pArg->GetOUString() );
    if ( aStr.isEmpty())
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
        rPar.Get(0)->PutEmpty();
        return;
    }
    sal_Unicode aCh = aStr[0];
    rPar.Get(0)->PutLong(aCh);
}
 
static void implChr( SbxArray& rPar, bool bChrW )
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    SbxVariableRef pArg = rPar.Get(1);
 
    OUString aStr;
    if( !bChrW && SbiRuntime::isVBAEnabled() )
    {
        char c = static_cast<char>(pArg->GetByte());
        aStr = OUString(&c, 1, osl_getThreadTextEncoding());
    }
    else
    {
        // Map negative 16-bit values to large positive ones, so that code like Chr(&H8000)
        // still works after the fix for tdf#62326 changed those four-digit hex notations to
        // produce negative values:
        sal_Int32 aCh = pArg->GetLong();
        if (aCh < -0x8000 || aCh > 0xFFFF)
        {
            StarBASIC::Error(ERRCODE_BASIC_MATH_OVERFLOW);
            aCh = 0;
        }
        aStr = OUString(static_cast<sal_Unicode>(aCh));
    }
    rPar.Get(0)->PutString(aStr);
}
 
void SbRtl_Chr(StarBASIC *, SbxArray & rPar, bool)
{
    implChr( rPar, false/*bChrW*/ );
}
 
void SbRtl_ChrW(StarBASIC *, SbxArray & rPar, bool)
{
    implChr( rPar, true/*bChrW*/ );
}
 
#if defined _WIN32
 
namespace {
 
extern "C" void invalidParameterHandler(
    wchar_t const * expression, wchar_t const * function, wchar_t const * file, unsigned int line,
    uintptr_t)
{
    SAL_INFO(
        "basic",
        "invalid parameter during _wgetdcwd; \""
            << (expression ? OUString(o3tl::toU(expression)) : OUString("???"))
            << "\" (" << (function ? OUString(o3tl::toU(function)) : OUString("???")) << ") at "
            << (file ? OUString(o3tl::toU(file)) : OUString("???")) << ":" << line);
}
 
}
 
#endif
 
void SbRtl_CurDir(StarBASIC *, SbxArray & rPar, bool)
{
    // #57064 Although this function doesn't work with DirEntry, it isn't touched
    // by the adjustment to virtual URLs, as, using the DirEntry-functionality,
    // there's no possibility to detect the current one in a way that a virtual URL
    // could be delivered.
 
    if (rPar.Count() > 2)
       return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
#if defined(_WIN32)
    int nCurDir = 0;  // Current dir // JSM
    if (rPar.Count() == 2)
    {
        OUString aDrive = rPar.Get(1)->GetOUString();
        if ( aDrive.getLength() != 1 )
            return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
        auto c = rtl::toAsciiUpperCase(aDrive[0]);
        if ( !rtl::isAsciiUpperCase( c ) )
            return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
        nCurDir = c - 'A' + 1;
    }
    wchar_t pBuffer[ _MAX_PATH ];
    // _wgetdcwd calls the C runtime's invalid parameter handler (which by default terminates the
    // process) if nCurDir does not correspond to an existing drive, so temporarily set a "harmless"
    // handler:
    auto const handler = _set_thread_local_invalid_parameter_handler(&invalidParameterHandler);
    auto const ok = _wgetdcwd( nCurDir, pBuffer, _MAX_PATH ) != nullptr;
    _set_thread_local_invalid_parameter_handler(handler);
    if ( !ok )
        return StarBASIC::Error( ERRCODE_BASIC_NO_DEVICE );
 
    rPar.Get(0)->PutString(OUString(o3tl::toU(pBuffer)));
 
#else
 
    const int PATH_INCR = 250;
 
    int nSize = PATH_INCR;
    std::unique_ptr<char[]> pMem;
    while( true )
    {
        pMem.reset(new char[nSize]);
        if( !pMem )
            return StarBASIC::Error( ERRCODE_BASIC_NO_MEMORY );
 
        if( getcwd( pMem.get(), nSize-1 ) != nullptr )
        {
            rPar.Get(0)->PutString(OUString::createFromAscii(pMem.get()));
            return;
        }
        if( errno != ERANGE )
            return StarBASIC::Error( ERRCODE_BASIC_INTERNAL_ERROR );
 
        nSize += PATH_INCR;
    };
 
#endif
}
 
void SbRtl_ChDir(StarBASIC * pBasic, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    // VBA: track current directory per document type (separately for Writer, Calc, Impress, etc.)
    if( SbiRuntime::isVBAEnabled() )
    {
        ::basic::vba::registerCurrentDirectory(getDocumentModel(pBasic),
                                                rPar.Get(1)->GetOUString());
    }
}
 
void SbRtl_ChDrive(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
}
 
 
// Implementation of StepRENAME with UCB
void implStepRenameUCB( const OUString& aSource, const OUString& aDest )
{
    const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
    if( !xSFI.is() )
        return;
 
    try
    {
        OUString aSourceFullPath = getFullPath( aSource );
        if( !xSFI->exists( aSourceFullPath ) )
        {
            StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
            return;
        }
 
        OUString aDestFullPath = getFullPath( aDest );
        if( xSFI->exists( aDestFullPath ) )
        {
            StarBASIC::Error( ERRCODE_BASIC_FILE_EXISTS );
        }
        else
        {
            xSFI->move( aSourceFullPath, aDestFullPath );
        }
    }
    catch(const Exception & )
    {
        StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
    }
}
 
// Implementation of StepRENAME with OSL
void implStepRenameOSL( const OUString& aSource, const OUString& aDest )
{
    FileBase::RC nRet = File::move( getFullPath( aSource ), getFullPath( aDest ) );
    if( nRet != FileBase::E_None )
    {
        StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
    }
}
 
void SbRtl_FileCopy(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    if (rPar.Count() != 3)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    OUString aSource = rPar.Get(1)->GetOUString();
    OUString aDest = rPar.Get(2)->GetOUString();
    if( hasUno() )
    {
        const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
        if( xSFI.is() )
        {
            try
            {
                xSFI->copy( getFullPath( aSource ), getFullPath( aDest ) );
            }
            catch(const Exception & )
            {
                StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
            }
        }
    }
    else
    {
        FileBase::RC nRet = File::copy( getFullPath( aSource ), getFullPath( aDest ) );
        if( nRet != FileBase::E_None )
        {
            StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
        }
    }
}
 
void SbRtl_Kill(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    OUString aFileSpec = rPar.Get(1)->GetOUString();
 
    if( hasUno() )
    {
        const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
        if( xSFI.is() )
        {
            OUString aFullPath = getFullPath( aFileSpec );
            if( !xSFI->exists( aFullPath ) || xSFI->isFolder( aFullPath ) )
            {
                StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
                return;
            }
            try
            {
                xSFI->kill( aFullPath );
            }
            catch(const Exception & )
            {
                StarBASIC::Error( ERRCODE_IO_GENERAL );
            }
        }
    }
    else
    {
        File::remove( getFullPath( aFileSpec ) );
    }
}
 
void SbRtl_MkDir(StarBASIC * pBasic, SbxArray & rPar, bool bWrite)
{
    rPar.Get(0)->PutEmpty();
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    OUString aPath = rPar.Get(1)->GetOUString();
    if ( SbiRuntime::isVBAEnabled() )
    {
        // In vba if the full path is not specified then
        // folder is created relative to the curdir
        INetURLObject aURLObj( getFullPath( aPath ) );
        if ( aURLObj.GetProtocol() != INetProtocol::File )
        {
            SbxArrayRef pPar = new SbxArray();
            SbxVariableRef pResult = new SbxVariable();
            SbxVariableRef pParam = new SbxVariable();
            pPar->Insert(pResult.get(), pPar->Count());
            pPar->Insert(pParam.get(), pPar->Count());
            SbRtl_CurDir( pBasic, *pPar, bWrite );
 
            OUString sCurPathURL;
            File::getFileURLFromSystemPath(pPar->Get(0)->GetOUString(), sCurPathURL);
 
            aURLObj.SetURL( sCurPathURL );
            aURLObj.Append( aPath );
            File::getSystemPathFromFileURL(aURLObj.GetMainURL( INetURLObject::DecodeMechanism::ToIUri  ),aPath ) ;
        }
    }
 
    if( hasUno() )
    {
        const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
        if( xSFI.is() )
        {
            try
            {
                xSFI->createFolder( getFullPath( aPath ) );
            }
            catch(const Exception & )
            {
                StarBASIC::Error( ERRCODE_IO_GENERAL );
            }
        }
    }
    else
    {
        Directory::create( getFullPath( aPath ) );
    }
}
 
 
static void implRemoveDirRecursive( const OUString& aDirPath )
{
    DirectoryItem aItem;
    FileBase::RC nRet = DirectoryItem::get( aDirPath, aItem );
    bool bExists = (nRet == FileBase::E_None);
 
    FileStatus aFileStatus( osl_FileStatus_Mask_Type );
    nRet = aItem.getFileStatus( aFileStatus );
    bool bFolder = nRet == FileBase::E_None
        && isFolder( aFileStatus.getFileType() );
 
    if( !bExists || !bFolder )
    {
        return StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
    }
 
    Directory aDir( aDirPath );
    nRet = aDir.open();
    if( nRet != FileBase::E_None )
    {
        return StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
    }
    aDir.close();
 
    comphelper::DirectoryHelper::deleteDirRecursively(aDirPath);
}
 
 
void SbRtl_RmDir(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    if (rPar.Count() == 2)
    {
        OUString aPath = rPar.Get(1)->GetOUString();
        if( hasUno() )
        {
            const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
            if( xSFI.is() )
            {
                try
                {
                    if( !xSFI->isFolder( aPath ) )
                    {
                        return StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
                    }
                    SbiInstance* pInst = GetSbData()->pInst;
                    bool bCompatibility = ( pInst && pInst->IsCompatibility() );
                    if( bCompatibility )
                    {
                        Sequence< OUString > aContent = xSFI->getFolderContents( aPath, true );
                        if( aContent.hasElements() )
                        {
                            return StarBASIC::Error( ERRCODE_BASIC_ACCESS_ERROR );
                        }
                    }
 
                    xSFI->kill( getFullPath( aPath ) );
                }
                catch(const Exception & )
                {
                    StarBASIC::Error( ERRCODE_IO_GENERAL );
                }
            }
        }
        else
        {
            implRemoveDirRecursive( getFullPath( aPath ) );
        }
    }
    else
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
}
 
void SbRtl_SendKeys(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    StarBASIC::Error(ERRCODE_BASIC_NOT_IMPLEMENTED);
}
 
void SbRtl_Exp(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    double aDouble = rPar.Get(1)->GetDouble();
    aDouble = exp( aDouble );
    checkArithmeticOverflow( aDouble );
    rPar.Get(0)->PutDouble(aDouble);
}
 
void SbRtl_FileLen(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
    SbxVariableRef pArg = rPar.Get(1);
    OUString aStr( pArg->GetOUString() );
    sal_Int32 nLen = 0;
    if( hasUno() )
    {
        const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
        if( xSFI.is() )
        {
            try
            {
                nLen = xSFI->getSize( getFullPath( aStr ) );
            }
            catch(const Exception & )
            {
                StarBASIC::Error( ERRCODE_IO_GENERAL );
            }
        }
    }
    else
    {
        DirectoryItem aItem;
        (void)DirectoryItem::get( getFullPath( aStr ), aItem );
        FileStatus aFileStatus( osl_FileStatus_Mask_FileSize );
        (void)aItem.getFileStatus( aFileStatus );
        nLen = static_cast<sal_Int32>(aFileStatus.getFileSize());
    }
    rPar.Get(0)->PutLong(nLen);
}
 
 
 
void SbRtl_Hex(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
    SbxVariableRef pArg = rPar.Get(1);
    // converting value to unsigned and limit to 2 or 4 byte representation
    sal_uInt32 nVal = pArg->IsInteger() ?
        static_cast<sal_uInt16>(pArg->GetInteger()) :
        static_cast<sal_uInt32>(pArg->GetLong());
    rPar.Get(0)->PutString(OUString::number(nVal, 16).toAsciiUpperCase());
}
 
void SbRtl_FuncCaller(StarBASIC *, SbxArray & rPar, bool)
{
    if ( SbiRuntime::isVBAEnabled() &&  GetSbData()->pInst && GetSbData()->pInst->pRun )
    {
        if ( GetSbData()->pInst->pRun->GetExternalCaller() )
            *rPar.Get(0) = *GetSbData()->pInst->pRun->GetExternalCaller();
        else
        {
            SbxVariableRef pVar = new SbxVariable(SbxVARIANT);
            *rPar.Get(0) = *pVar;
        }
    }
    else
    {
        StarBASIC::Error( ERRCODE_BASIC_NOT_IMPLEMENTED );
    }
 
}
// InStr( [start],string,string,[compare] )
 
void SbRtl_InStr(StarBASIC *, SbxArray & rPar, bool)
{
    const sal_uInt32 nArgCount = rPar.Count() - 1;
    if ( nArgCount < 2 )
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    else
    {
        sal_Int32 nStartPos = 1;
        sal_Int32 nFirstStringPos = 1;
 
        if ( nArgCount >= 3 )
        {
            nStartPos = rPar.Get(1)->GetLong();
            if( nStartPos <= 0 )
            {
                StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
                nStartPos = 1;
            }
            nFirstStringPos++;
        }
 
        SbiInstance* pInst = GetSbData()->pInst;
        bool bTextMode;
        bool bCompatibility = ( pInst && pInst->IsCompatibility() );
        if( bCompatibility )
        {
            SbiRuntime* pRT = pInst->pRun;
            bTextMode = pRT && pRT->IsImageFlag( SbiImageFlags::COMPARETEXT );
        }
        else
        {
            bTextMode = true;
        }
        if ( nArgCount == 4 )
        {
            bTextMode = rPar.Get(4)->GetInteger();
        }
        sal_Int32 nPos;
        const OUString aToken = rPar.Get(nFirstStringPos + 1)->GetOUString();
 
        // #97545 Always find empty string
        if( aToken.isEmpty() )
        {
            nPos = nStartPos;
        }
        else
        {
            const OUString aStr1 = rPar.Get(nFirstStringPos)->GetOUString();
            const sal_Int32 nrStr1Len = aStr1.getLength();
            if (nStartPos > nrStr1Len)
            {
                // Start position is greater than the string being searched
                nPos = 0;
            }
            else
            {
                if( !bTextMode )
                {
                    nPos = aStr1.indexOf( aToken, nStartPos - 1 ) + 1;
                }
                else
                {
                    // tdf#139840 - case-insensitive operation for non-ASCII characters
                    i18nutil::SearchOptions2 aSearchOptions;
                    aSearchOptions.searchString = aToken;
                    aSearchOptions.AlgorithmType2 = util::SearchAlgorithms2::ABSOLUTE;
                    aSearchOptions.transliterateFlags |= TransliterationFlags::IGNORE_CASE;
                    utl::TextSearch textSearch(aSearchOptions);
 
                    sal_Int32 nStart = nStartPos - 1;
                    sal_Int32 nEnd = nrStr1Len;
                    nPos = textSearch.SearchForward(aStr1, &nStart, &nEnd) ? nStart + 1 : 0;
                }
            }
        }
        rPar.Get(0)->PutLong(nPos);
    }
}
 
 
// InstrRev(string1, string2[, start[, compare]])
 
void SbRtl_InStrRev(StarBASIC *, SbxArray & rPar, bool)
{
    const sal_uInt32 nArgCount = rPar.Count() - 1;
    if ( nArgCount < 2 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
    const OUString aStr1 = rPar.Get(1)->GetOUString();
    const OUString aToken = rPar.Get(2)->GetOUString();
 
    sal_Int32 nStartPos = -1;
    if ( nArgCount >= 3 )
    {
        nStartPos = rPar.Get(3)->GetLong();
        if( nStartPos <= 0 && nStartPos != -1 )
        {
            StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
            nStartPos = -1;
        }
    }
 
    SbiInstance* pInst = GetSbData()->pInst;
    bool bTextMode;
    bool bCompatibility = ( pInst && pInst->IsCompatibility() );
    if( bCompatibility )
    {
        SbiRuntime* pRT = pInst->pRun;
        bTextMode = pRT && pRT->IsImageFlag( SbiImageFlags::COMPARETEXT );
    }
    else
    {
        bTextMode = true;
    }
    if ( nArgCount == 4 )
    {
        bTextMode = rPar.Get(4)->GetInteger();
    }
    const sal_Int32 nStrLen = aStr1.getLength();
    if( nStartPos == -1 )
    {
        nStartPos = nStrLen;
    }
 
    sal_Int32 nPos = 0;
    if( nStartPos <= nStrLen )
    {
        sal_Int32 nTokenLen = aToken.getLength();
        if( !nTokenLen )
        {
           // Always find empty string
           nPos = nStartPos;
        }
        else if( nStrLen > 0 )
        {
            if( !bTextMode )
            {
               nPos = aStr1.lastIndexOf( aToken, nStartPos ) + 1;
            }
            else
            {
               // tdf#143332 - case-insensitive operation for non-ASCII characters
               i18nutil::SearchOptions2 aSearchOptions;
               aSearchOptions.searchString = aToken;
               aSearchOptions.AlgorithmType2 = util::SearchAlgorithms2::ABSOLUTE;
               aSearchOptions.transliterateFlags |= TransliterationFlags::IGNORE_CASE;
               utl::TextSearch textSearch(aSearchOptions);
 
               sal_Int32 nStart = 0;
               sal_Int32 nEnd = nStartPos;
               nPos = textSearch.SearchBackward(aStr1, &nEnd, &nStart) ? nStart : 0;
            }
        }
    }
    rPar.Get(0)->PutLong(nPos);
}
 
 
/*
    Int( 2.8 )  =  2.0
    Int( -2.8 ) = -3.0
    Fix( 2.8 )  =  2.0
    Fix( -2.8 ) = -2.0    <- !!
*/
 
void SbRtl_Int(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    SbxVariableRef pArg = rPar.Get(1);
    double aDouble= pArg->GetDouble();
    /*
        floor( 2.8 ) =  2.0
        floor( -2.8 ) = -3.0
    */
    aDouble = floor( aDouble );
    rPar.Get(0)->PutDouble(aDouble);
}
 
 
void SbRtl_Fix(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    SbxVariableRef pArg = rPar.Get(1);
    double aDouble = pArg->GetDouble();
    if ( aDouble >= 0.0 )
        aDouble = floor( aDouble );
    else
        aDouble = ceil( aDouble );
    rPar.Get(0)->PutDouble(aDouble);
}
 
 
void SbRtl_LCase(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    const CharClass& rCharClass = GetCharClass();
    OUString aStr(rPar.Get(1)->GetOUString());
    aStr = rCharClass.lowercase(aStr);
    rPar.Get(0)->PutString(aStr);
}
 
void SbRtl_Left(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 3)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    OUString aStr(rPar.Get(1)->GetOUString());
    sal_Int32 nResultLen = rPar.Get(2)->GetLong();
    if( nResultLen < 0 )
    {
        nResultLen = 0;
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else if(nResultLen > aStr.getLength())
    {
        nResultLen = aStr.getLength();
    }
    aStr = aStr.copy(0, nResultLen );
    rPar.Get(0)->PutString(aStr);
}
 
void SbRtl_Log(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    double aArg = rPar.Get(1)->GetDouble();
    if ( aArg <= 0 )
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    double d = log( aArg );
    checkArithmeticOverflow( d );
    rPar.Get(0)->PutDouble(d);
}
 
void SbRtl_LTrim(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    OUString aStr(comphelper::string::stripStart(rPar.Get(1)->GetOUString(), ' '));
    rPar.Get(0)->PutString(aStr);
}
 
 
// Mid( String, nStart, nLength )
 
void SbRtl_Mid(StarBASIC *, SbxArray & rPar, bool bWrite)
{
    int nArgCount = rPar.Count() - 1;
    if ( nArgCount < 2 )
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        // #23178: replicate the functionality of Mid$ as a command
        // by adding a replacement-string as a fourth parameter.
        // In contrast to the original the third parameter (nLength)
        // can't be left out here. That's considered in bWrite already.
        if( nArgCount == 4 )
        {
            bWrite = true;
        }
        OUString aArgStr = rPar.Get(1)->GetOUString();
        sal_Int32 nStartPos = rPar.Get(2)->GetLong();
        if ( nStartPos < 1 )
        {
            StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
        }
        else
        {
            nStartPos--;
            sal_Int32 nLen = -1;
            bool bWriteNoLenParam = false;
            if ( nArgCount == 3 || bWrite )
            {
                sal_Int32 n = rPar.Get(3)->GetLong();
                if( bWrite && n == -1 )
                {
                    bWriteNoLenParam = true;
                }
                nLen = n;
            }
            if ( bWrite )
            {
                sal_Int32 nArgLen = aArgStr.getLength();
                if( nStartPos > nArgLen )
                {
                    SbiInstance* pInst = GetSbData()->pInst;
                    bool bCompatibility = ( pInst && pInst->IsCompatibility() );
                    if( bCompatibility )
                    {
                        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
                    }
                    nStartPos = nArgLen;
                }
 
                OUString aReplaceStr = rPar.Get(4)->GetOUString();
                sal_Int32 nReplaceStrLen = aReplaceStr.getLength();
                sal_Int32 nReplaceLen;
                if( bWriteNoLenParam )
                {
                    nReplaceLen = nArgLen - nStartPos;
                }
                else
                {
                    nReplaceLen = nLen;
                    if( nReplaceLen < 0 || nReplaceLen > nArgLen - nStartPos )
                    {
                        nReplaceLen = nArgLen - nStartPos;
                    }
                }
 
                OUStringBuffer aResultStr(aArgStr);
                sal_Int32 nErase = nReplaceLen;
                aResultStr.remove( nStartPos, nErase );
                aResultStr.insert(
                    nStartPos, aReplaceStr.getStr(), std::min(nReplaceLen, nReplaceStrLen));
 
                rPar.Get(1)->PutString(aResultStr.makeStringAndClear());
            }
            else
            {
                OUString aResultStr;
                if (nStartPos > aArgStr.getLength())
                {
                    // do nothing
                }
                else if(nArgCount == 2)
                {
                    aResultStr = aArgStr.copy( nStartPos);
                }
                else
                {
                    if (nLen < 0)
                        nLen = 0;
                    if(nStartPos + nLen > aArgStr.getLength())
                    {
                        nLen = aArgStr.getLength() - nStartPos;
                    }
                    if (nLen > 0)
                        aResultStr = aArgStr.copy( nStartPos, nLen );
                }
                rPar.Get(0)->PutString(aResultStr);
            }
        }
    }
}
 
void SbRtl_Oct(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    SbxVariableRef pArg = rPar.Get(1);
    // converting value to unsigned and limit to 2 or 4 byte representation
    sal_uInt32 nVal = pArg->IsInteger() ?
        static_cast<sal_uInt16>(pArg->GetInteger()) :
        static_cast<sal_uInt32>(pArg->GetLong());
    rPar.Get(0)->PutString(OUString::number(nVal, 8));
}
 
// Replace(expression, find, replace[, start[, count[, compare]]])
 
void SbRtl_Replace(StarBASIC *, SbxArray & rPar, bool)
{
    const sal_uInt32 nArgCount = rPar.Count() - 1;
    if ( nArgCount < 3 || nArgCount > 6 )
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    sal_Int32 lStartPos = 1;
    if (nArgCount >= 4)
    {
        if (rPar.Get(4)->GetType() != SbxEMPTY)
        {
            lStartPos = rPar.Get(4)->GetLong();
        }
        if (lStartPos < 1)
        {
            return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
        }
    }
    --lStartPos; // Make it 0-based
 
    sal_Int32 lCount = -1;
    if (nArgCount >= 5)
    {
        if (rPar.Get(5)->GetType() != SbxEMPTY)
        {
            lCount = rPar.Get(5)->GetLong();
        }
        if (lCount < -1)
        {
            return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
        }
    }
 
    bool bCaseInsensitive;
    if (nArgCount == 6)
    {
        bCaseInsensitive = rPar.Get(6)->GetInteger();
    }
    else
    {
        SbiInstance* pInst = GetSbData()->pInst;
        if (pInst && pInst->IsCompatibility())
        {
            SbiRuntime* pRT = pInst->pRun;
            bCaseInsensitive = pRT && pRT->IsImageFlag(SbiImageFlags::COMPARETEXT);
        }
        else
        {
            bCaseInsensitive = true;
        }
    }
 
    const OUString aExpStr = rPar.Get(1)->GetOUString();
    OUString aFindStr = rPar.Get(2)->GetOUString();
    const OUString aReplaceStr = rPar.Get(3)->GetOUString();
 
    OUString aSrcStr(aExpStr);
    sal_Int32 nPrevPos = std::min(lStartPos, aSrcStr.getLength());
    css::uno::Sequence<sal_Int32> aOffset;
    if (bCaseInsensitive)
    {
        // tdf#132389: case-insensitive operation for non-ASCII characters
        // tdf#142487: use css::i18n::Transliteration to correctly handle ß -> ss expansion
        // tdf#132388: We can't use utl::TextSearch (css::i18n::XTextSearch), because each call to
        //             css::i18n::XTextSearch::SearchForward transliterates input string, making
        //             performance of repeated calls unacceptable
        auto xTrans = css::i18n::Transliteration::create(comphelper::getProcessComponentContext());
        xTrans->loadModule(css::i18n::TransliterationModules_IGNORE_CASE, {});
        aFindStr = xTrans->transliterate(aFindStr, 0, aFindStr.getLength(), aOffset);
        aSrcStr = xTrans->transliterate(aSrcStr, nPrevPos, aSrcStr.getLength() - nPrevPos, aOffset);
        nPrevPos = std::distance(aOffset.begin(),
                                 std::lower_bound(aOffset.begin(), aOffset.end(), nPrevPos));
    }
 
    auto getExpStrPos = [aOffset, nExpLen = aExpStr.getLength()](sal_Int32 nSrcStrPos) -> sal_Int32
    {
        assert(!aOffset.hasElements() || aOffset.getLength() >= nSrcStrPos);
        if (!aOffset.hasElements())
            return nSrcStrPos;
        return aOffset.getLength() > nSrcStrPos ? aOffset[nSrcStrPos] : nExpLen;
    };
 
    // Note: the result starts from lStartPos, removing everything to the left. See i#94895.
    OUStringBuffer sResult(aSrcStr.getLength() - nPrevPos);
    sal_Int32 nCounts = 0;
    while (lCount == -1 || lCount > nCounts)
    {
        sal_Int32 nPos = aSrcStr.indexOf(aFindStr, nPrevPos);
        if (nPos < 0)
            break;
 
        lStartPos = getExpStrPos(nPrevPos);
        sResult.append(aExpStr.getStr() + lStartPos, getExpStrPos(nPos) - lStartPos);
        sResult.append(aReplaceStr);
        nPrevPos = nPos + aFindStr.getLength();
        nCounts++;
    }
    lStartPos = getExpStrPos(nPrevPos);
    sResult.append(aExpStr.getStr() + lStartPos, aExpStr.getLength() - lStartPos);
    rPar.Get(0)->PutString(sResult.makeStringAndClear());
}
 
void SbRtl_Right(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 3)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    const OUString aStr = rPar.Get(1)->GetOUString();
    int nResultLen = rPar.Get(2)->GetLong();
    if( nResultLen < 0 )
    {
        nResultLen = 0;
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    int nStrLen = aStr.getLength();
    if ( nResultLen > nStrLen )
    {
        nResultLen = nStrLen;
    }
    OUString aResultStr = aStr.copy( nStrLen - nResultLen );
    rPar.Get(0)->PutString(aResultStr);
}
 
void SbRtl_RTL(StarBASIC * pBasic, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutObject(pBasic->getRTL().get());
}
 
void SbRtl_RTrim(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    OUString aStr(comphelper::string::stripEnd(rPar.Get(1)->GetOUString(), ' '));
    rPar.Get(0)->PutString(aStr);
}
 
void SbRtl_Sgn(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    double aDouble = rPar.Get(1)->GetDouble();
    sal_Int16 nResult = 0;
    if ( aDouble > 0 )
    {
        nResult = 1;
    }
    else if ( aDouble < 0 )
    {
        nResult = -1;
    }
    rPar.Get(0)->PutInteger(nResult);
}
 
void SbRtl_Space(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        const sal_Int32 nCount = rPar.Get(1)->GetLong();
        OUStringBuffer aBuf(nCount);
        string::padToLength(aBuf, nCount, ' ');
        rPar.Get(0)->PutString(aBuf.makeStringAndClear());
    }
}
 
void SbRtl_Sqr(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        double aDouble = rPar.Get(1)->GetDouble();
        if ( aDouble >= 0 )
        {
            rPar.Get(0)->PutDouble(sqrt(aDouble));
        }
        else
        {
            StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
        }
    }
}
 
void SbRtl_Str(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        OUString aStr;
        OUString aStrNew(u""_ustr);
        SbxVariableRef pArg = rPar.Get(1);
        pArg->Format( aStr );
 
        // Numbers start with a space
        if( pArg->IsNumericRTL() )
        {
            // replace commas by points so that it's symmetric to Val!
            aStr = aStr.replaceFirst( ",", "." );
 
            SbiInstance* pInst = GetSbData()->pInst;
            bool bCompatibility = ( pInst && pInst->IsCompatibility() );
            if( bCompatibility )
            {
                sal_Int32 nLen = aStr.getLength();
 
                const sal_Unicode* pBuf = aStr.getStr();
 
                bool bNeg = ( pBuf[0] == '-' );
                sal_Int32 iZeroSearch = 0;
                if( bNeg )
                {
                    aStrNew += "-";
                    iZeroSearch++;
                }
                else
                {
                    if( pBuf[0] != ' ' )
                    {
                        aStrNew += " ";
                    }
                }
                sal_Int32 iNext = iZeroSearch + 1;
                if( pBuf[iZeroSearch] == '0' && nLen > iNext && pBuf[iNext] == '.' )
                {
                    iZeroSearch += 1;
                }
                aStrNew += aStr.subView(iZeroSearch);
            }
            else
            {
                aStrNew = " " + aStr;
            }
        }
        else
        {
            aStrNew = aStr;
        }
        rPar.Get(0)->PutString(aStrNew);
    }
}
 
void SbRtl_StrComp(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 3)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
        rPar.Get(0)->PutEmpty();
        return;
    }
    const OUString aStr1 = rPar.Get(1)->GetOUString();
    const OUString aStr2 = rPar.Get(2)->GetOUString();
 
    SbiInstance* pInst = GetSbData()->pInst;
    bool bTextCompare;
    bool bCompatibility = ( pInst && pInst->IsCompatibility() );
    if( bCompatibility )
    {
        SbiRuntime* pRT = pInst->pRun;
        bTextCompare = pRT && pRT->IsImageFlag( SbiImageFlags::COMPARETEXT );
    }
    else
    {
        bTextCompare = true;
    }
    if (rPar.Count() == 4)
        bTextCompare = rPar.Get(3)->GetInteger();
 
    if( !bCompatibility )
    {
        bTextCompare = !bTextCompare;
    }
    sal_Int32 nRetValue = 0;
    if( bTextCompare )
    {
        ::utl::TransliterationWrapper* pTransliterationWrapper = GetSbData()->pTransliterationWrapper.get();
        if( !pTransliterationWrapper )
        {
            const uno::Reference< uno::XComponentContext >& xContext = getProcessComponentContext();
            GetSbData()->pTransliterationWrapper.reset(
                new ::utl::TransliterationWrapper( xContext,
                    TransliterationFlags::IGNORE_CASE |
                    TransliterationFlags::IGNORE_KANA |
                    TransliterationFlags::IGNORE_WIDTH ) );
            pTransliterationWrapper = GetSbData()->pTransliterationWrapper.get();
        }
 
        LanguageType eLangType = Application::GetSettings().GetLanguageTag().getLanguageType();
        pTransliterationWrapper->loadModuleIfNeeded( eLangType );
        nRetValue = pTransliterationWrapper->compareString( aStr1, aStr2 );
    }
    else
    {
        sal_Int32 aResult;
        aResult = aStr1.compareTo( aStr2 );
        if ( aResult < 0  )
        {
            nRetValue = -1;
        }
        else if ( aResult > 0)
        {
            nRetValue = 1;
        }
    }
    rPar.Get(0)->PutInteger(sal::static_int_cast<sal_Int16>(nRetValue));
}
 
void SbRtl_String(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        sal_Unicode aFiller;
        sal_Int32 lCount = rPar.Get(1)->GetLong();
        if( lCount < 0 || lCount > 0xffff )
        {
            StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
        }
        if (rPar.Get(2)->GetType() == SbxINTEGER)
        {
            aFiller = static_cast<sal_Unicode>(rPar.Get(2)->GetInteger());
        }
        else
        {
            const OUString aStr = rPar.Get(2)->GetOUString();
            aFiller = aStr[0];
        }
        OUStringBuffer aBuf(lCount);
        string::padToLength(aBuf, lCount, aFiller);
        rPar.Get(0)->PutString(aBuf.makeStringAndClear());
    }
}
 
void SbRtl_Tab(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    else
    {
        const sal_Int32 nCount = std::max(rPar.Get(1)->GetLong(), sal_Int32(0));
        OUStringBuffer aStr(nCount);
        comphelper::string::padToLength(aStr, nCount, '\t');
        rPar.Get(0)->PutString(aStr.makeStringAndClear());
    }
}
 
void SbRtl_Tan(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        SbxVariableRef pArg = rPar.Get(1);
        rPar.Get(0)->PutDouble(tan(pArg->GetDouble()));
    }
}
 
void SbRtl_UCase(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        const CharClass& rCharClass = GetCharClass();
        OUString aStr(rPar.Get(1)->GetOUString());
        aStr = rCharClass.uppercase( aStr );
        rPar.Get(0)->PutString(aStr);
    }
}
 
 
void SbRtl_Val(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        double nResult = 0.0;
        char* pEndPtr;
 
        OUString aStr(rPar.Get(1)->GetOUString());
 
        FilterWhiteSpace( aStr );
        if ( aStr.getLength() > 1 && aStr[0] == '&' )
        {
            int nRadix = 10;
            char aChar = static_cast<char>(aStr[1]);
            if ( aChar == 'h' || aChar == 'H' )
            {
                nRadix = 16;
            }
            else if ( aChar == 'o' || aChar == 'O' )
            {
                nRadix = 8;
            }
            if ( nRadix != 10 )
            {
                OString aByteStr(OUStringToOString(aStr, osl_getThreadTextEncoding()));
                sal_Int16 nlResult = static_cast<sal_Int16>(strtol( aByteStr.getStr()+2, &pEndPtr, nRadix));
                nResult = static_cast<double>(nlResult);
            }
        }
        else
        {
            rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok;
            sal_Int32 nParseEnd = 0;
            nResult = ::rtl::math::stringToDouble( aStr, '.', ',', &eStatus, &nParseEnd );
            if ( eStatus != rtl_math_ConversionStatus_Ok )
                StarBASIC::Error( ERRCODE_BASIC_MATH_OVERFLOW );
            /* TODO: we should check whether all characters were parsed here,
             * but earlier code silently ignored trailing nonsense such as "1x"
             * resulting in 1 with the side effect that any alpha-only-string
             * like "x" resulted in 0. Not changing that now (2013-03-22) as
             * user macros may rely on it. */
#if 0
            else if ( nParseEnd != aStr.getLength() )
                StarBASIC::Error( ERRCODE_BASIC_CONVERSION );
#endif
        }
 
        rPar.Get(0)->PutDouble(nResult);
    }
}
 
 
// Helper functions for date conversion
sal_Int16 implGetDateDay( double aDate )
{
    aDate = floor( aDate );
    Date aRefDate(1899'12'30);
    aRefDate.AddDays( aDate );
 
    sal_Int16 nRet = static_cast<sal_Int16>( aRefDate.GetDay() );
    return nRet;
}
 
sal_Int16 implGetDateMonth( double aDate )
{
    Date aRefDate(1899'12'30);
    sal_Int32 nDays = static_cast<sal_Int32>(aDate);
    aRefDate.AddDays( nDays );
    sal_Int16 nRet = static_cast<sal_Int16>( aRefDate.GetMonth() );
    return nRet;
}
 
css::util::Date SbxDateToUNODate( const SbxValue* const pVal )
{
    double aDate = pVal->GetDate();
 
    css::util::Date aUnoDate;
    aUnoDate.Day   = implGetDateDay  ( aDate );
    aUnoDate.Month = implGetDateMonth( aDate );
    aUnoDate.Year  = implGetDateYear ( aDate );
 
    return aUnoDate;
}
 
void SbxDateFromUNODate( SbxValue *pVal, const css::util::Date& aUnoDate)
{
    double dDate;
    if( implDateSerial( aUnoDate.Year, aUnoDate.Month, aUnoDate.Day, false, SbDateCorrection::None, dDate ) )
    {
        pVal->PutDate( dDate );
    }
}
 
// Function to convert date to UNO date (com.sun.star.util.Date)
void SbRtl_CDateToUnoDate(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
    unoToSbxValue(rPar.Get(0), Any(SbxDateToUNODate(rPar.Get(1))));
}
 
// Function to convert date from UNO date (com.sun.star.util.Date)
void SbRtl_CDateFromUnoDate(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2 || rPar.Get(1)->GetType() != SbxOBJECT)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
    Any aAny(sbxToUnoValue(rPar.Get(1), cppu::UnoType<css::util::Date>::get()));
    css::util::Date aUnoDate;
    if(aAny >>= aUnoDate)
        SbxDateFromUNODate(rPar.Get(0), aUnoDate);
    else
        SbxBase::SetError( ERRCODE_BASIC_CONVERSION );
}
 
css::util::Time SbxDateToUNOTime( const SbxValue* const pVal )
{
    double aDate = pVal->GetDate();
 
    css::util::Time aUnoTime;
    aUnoTime.Hours       = implGetHour      ( aDate );
    aUnoTime.Minutes     = implGetMinute    ( aDate );
    aUnoTime.Seconds     = implGetSecond    ( aDate );
    aUnoTime.NanoSeconds = implGetNanoSecond( aDate );
 
    return aUnoTime;
}
 
void SbxDateFromUNOTime( SbxValue *pVal, const css::util::Time& aUnoTime)
{
    pVal->PutDate(implTimeSerial(aUnoTime.Hours, aUnoTime.Minutes, aUnoTime.Seconds,
                                 nanoSecToMilliSec(aUnoTime.NanoSeconds)));
}
 
// Function to convert date to UNO time (com.sun.star.util.Time)
void SbRtl_CDateToUnoTime(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
    unoToSbxValue(rPar.Get(0), Any(SbxDateToUNOTime(rPar.Get(1))));
}
 
// Function to convert date from UNO time (com.sun.star.util.Time)
void SbRtl_CDateFromUnoTime(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2 || rPar.Get(1)->GetType() != SbxOBJECT)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
    Any aAny(sbxToUnoValue(rPar.Get(1), cppu::UnoType<css::util::Time>::get()));
    css::util::Time aUnoTime;
    if(aAny >>= aUnoTime)
        SbxDateFromUNOTime(rPar.Get(0), aUnoTime);
    else
        SbxBase::SetError( ERRCODE_BASIC_CONVERSION );
}
 
css::util::DateTime SbxDateToUNODateTime( const SbxValue* const pVal )
{
    double aDate = pVal->GetDate();
 
    css::util::DateTime aUnoDT;
    aUnoDT.Day   = implGetDateDay  ( aDate );
    aUnoDT.Month = implGetDateMonth( aDate );
    aUnoDT.Year  = implGetDateYear ( aDate );
    aUnoDT.Hours       = implGetHour      ( aDate );
    aUnoDT.Minutes     = implGetMinute    ( aDate );
    aUnoDT.Seconds     = implGetSecond    ( aDate );
    aUnoDT.NanoSeconds = implGetNanoSecond( aDate );
 
    return aUnoDT;
}
 
void SbxDateFromUNODateTime( SbxValue *pVal, const css::util::DateTime& aUnoDT)
{
    double dDate(0.0);
    if (implDateTimeSerial(aUnoDT.Year, aUnoDT.Month, aUnoDT.Day, aUnoDT.Hours, aUnoDT.Minutes,
                           aUnoDT.Seconds, nanoSecToMilliSec(aUnoDT.NanoSeconds), dDate))
    {
        pVal->PutDate( dDate );
    }
}
 
// Function to convert date to UNO date (com.sun.star.util.Date)
void SbRtl_CDateToUnoDateTime(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
    unoToSbxValue(rPar.Get(0), Any(SbxDateToUNODateTime(rPar.Get(1))));
}
 
// Function to convert date from UNO date (com.sun.star.util.Date)
void SbRtl_CDateFromUnoDateTime(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2 || rPar.Get(1)->GetType() != SbxOBJECT)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
    Any aAny(sbxToUnoValue(rPar.Get(1), cppu::UnoType<css::util::DateTime>::get()));
    css::util::DateTime aUnoDT;
    if(aAny >>= aUnoDT)
        SbxDateFromUNODateTime(rPar.Get(0), aUnoDT);
    else
        SbxBase::SetError( ERRCODE_BASIC_CONVERSION );
}
 
// Function to convert date to ISO 8601 date format YYYYMMDD
void SbRtl_CDateToIso(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() == 2)
    {
        double aDate = rPar.Get(1)->GetDate();
 
        // Date may actually even be -YYYYYMMDD
        char Buffer[11];
        sal_Int16 nYear = implGetDateYear( aDate );
        snprintf( Buffer, sizeof( Buffer ), (nYear < 0 ? "%05d%02d%02d" : "%04d%02d%02d"),
                static_cast<int>(nYear),
                static_cast<int>(implGetDateMonth( aDate )),
                static_cast<int>(implGetDateDay( aDate )) );
        OUString aRetStr = OUString::createFromAscii( Buffer );
        rPar.Get(0)->PutString(aRetStr);
    }
    else
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
}
 
// Function to convert date from ISO 8601 date format YYYYMMDD or YYYY-MM-DD
// And even YYMMDD for compatibility, sigh...
void SbRtl_CDateFromIso(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() == 2)
    {
        do
        {
            OUString aStr = rPar.Get(1)->GetOUString();
            if (aStr.isEmpty())
                break;
 
            // Valid formats are
            // YYYYMMDD    -YYYMMDD     YYYYYMMDD    -YYYYYMMDD    YYMMDD
            // YYYY-MM-DD  -YYYY-MM-DD  YYYYY-MM-DD  -YYYYY-MM-DD
 
            sal_Int32 nSign = 1;
            if (aStr[0] == '-')
            {
                nSign = -1;
                aStr = aStr.copy(1);
            }
            const sal_Int32 nLen = aStr.getLength();
 
            // Signed YYMMDD two digit year is invalid.
            if (nLen == 6 && nSign == -1)
                break;
 
            // Now valid
            // YYYYMMDD    YYYYYMMDD    YYMMDD
            // YYYY-MM-DD  YYYYY-MM-DD
            if (nLen != 6 && (nLen < 8 || 11 < nLen))
                break;
 
            bool bUseTwoDigitYear = false;
            std::u16string_view aYearStr, aMonthStr, aDayStr;
            if (nLen == 6 || nLen == 8 || nLen == 9)
            {
                // ((Y)YY)YYMMDD
                if (!comphelper::string::isdigitAsciiString(aStr))
                    break;
 
                const sal_Int32 nMonthPos = (nLen == 8 ? 4 : (nLen == 6 ? 2 : 5));
                if (nMonthPos == 2)
                    bUseTwoDigitYear = true;
                aYearStr  = aStr.subView( 0, nMonthPos );
                aMonthStr = aStr.subView( nMonthPos, 2 );
                aDayStr   = aStr.subView( nMonthPos + 2, 2 );
            }
            else
            {
                // (Y)YYYY-MM-DD
                const sal_Int32 nMonthSep = (nLen == 11 ? 5 : 4);
                if (aStr.indexOf('-') != nMonthSep)
                    break;
                if (aStr.indexOf('-', nMonthSep + 1) != nMonthSep + 3)
                    break;
 
                aYearStr  = aStr.subView( 0, nMonthSep );
                aMonthStr = aStr.subView( nMonthSep + 1, 2 );
                aDayStr   = aStr.subView( nMonthSep + 4, 2 );
                if (    !comphelper::string::isdigitAsciiString(aYearStr) ||
                        !comphelper::string::isdigitAsciiString(aMonthStr) ||
                        !comphelper::string::isdigitAsciiString(aDayStr))
                    break;
            }
 
            double dDate;
            if (!implDateSerial( static_cast<sal_Int16>(nSign * o3tl::toInt32(aYearStr)),
                        static_cast<sal_Int16>(o3tl::toInt32(aMonthStr)), static_cast<sal_Int16>(o3tl::toInt32(aDayStr)),
                        bUseTwoDigitYear, SbDateCorrection::None, dDate ))
                break;
 
            rPar.Get(0)->PutDate(dDate);
 
            return;
        }
        while (false);
 
        SbxBase::SetError( ERRCODE_BASIC_BAD_PARAMETER );
    }
    else
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
}
 
void SbRtl_DateSerial(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 4)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    sal_Int16 nYear = rPar.Get(1)->GetInteger();
    sal_Int16 nMonth = rPar.Get(2)->GetInteger();
    sal_Int16 nDay = rPar.Get(3)->GetInteger();
 
    double dDate;
    if( implDateSerial( nYear, nMonth, nDay, true, SbDateCorrection::RollOver, dDate ) )
    {
        rPar.Get(0)->PutDate(dDate);
    }
}
 
void SbRtl_TimeSerial(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 4)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    sal_Int16 nHour = rPar.Get(1)->GetInteger();
    if ( nHour == 24 )
    {
        nHour = 0;                      // because of UNO DateTimes, which go till 24 o'clock
    }
    sal_Int16 nMinute = rPar.Get(2)->GetInteger();
    sal_Int16 nSecond = rPar.Get(3)->GetInteger();
    if ((nHour < 0 || nHour > 23)   ||
        (nMinute < 0 || nMinute > 59 )  ||
        (nSecond < 0 || nSecond > 59 ))
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
    rPar.Get(0)->PutDate(implTimeSerial(nHour, nMinute, nSecond, 0)); // JSM
}
 
void SbRtl_DateValue(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        // #39629 check GetSbData()->pInst, can be called from the URL line
        std::shared_ptr<SvNumberFormatter> pFormatter;
        if( GetSbData()->pInst )
        {
            pFormatter = GetSbData()->pInst->GetNumberFormatter();
        }
        else
        {
            sal_uInt32 n;   // Dummy
            pFormatter = SbiInstance::PrepareNumberFormatter( n, n, n );
        }
 
        LanguageType eLangType = Application::GetSettings().GetLanguageTag().getLanguageType();
        sal_uInt32 nIndex = pFormatter->GetStandardIndex( eLangType);
        double fResult;
        OUString aStr(rPar.Get(1)->GetOUString());
        bool bSuccess = pFormatter->IsNumberFormat( aStr, nIndex, fResult );
        SvNumFormatType nType = pFormatter->GetType( nIndex );
 
        // DateValue("February 12, 1969") raises error if the system locale is not en_US
        // It seems that both locale number formatter and English number
        // formatter are supported in Visual Basic.
        if( !bSuccess && ( eLangType != LANGUAGE_ENGLISH_US ) )
        {
            // Try using LANGUAGE_ENGLISH_US to get the date value.
            nIndex = pFormatter->GetStandardIndex( LANGUAGE_ENGLISH_US);
            bSuccess = pFormatter->IsNumberFormat( aStr, nIndex, fResult );
            nType = pFormatter->GetType( nIndex );
        }
 
        if(bSuccess && (nType==SvNumFormatType::DATE || nType==SvNumFormatType::DATETIME))
        {
            if ( nType == SvNumFormatType::DATETIME )
            {
                // cut time
                if ( fResult  > 0.0 )
                {
                    fResult = floor( fResult );
                }
                else
                {
                    fResult = ceil( fResult );
                }
            }
            rPar.Get(0)->PutDate(fResult);
        }
        else
        {
            StarBASIC::Error( ERRCODE_BASIC_CONVERSION );
        }
    }
}
 
void SbRtl_TimeValue(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        std::shared_ptr<SvNumberFormatter> pFormatter;
        if( GetSbData()->pInst )
            pFormatter = GetSbData()->pInst->GetNumberFormatter();
        else
        {
            sal_uInt32 n;
            pFormatter = SbiInstance::PrepareNumberFormatter( n, n, n );
        }
 
        sal_uInt32 nIndex = 0;
        double fResult;
        bool bSuccess = pFormatter->IsNumberFormat(rPar.Get(1)->GetOUString(),
                                                   nIndex, fResult );
        SvNumFormatType nType = pFormatter->GetType(nIndex);
        if(bSuccess && (nType==SvNumFormatType::TIME||nType==SvNumFormatType::DATETIME))
        {
            if ( nType == SvNumFormatType::DATETIME )
            {
                // cut days
                fResult = fmod( fResult, 1 );
            }
            rPar.Get(0)->PutDate(fResult);
        }
        else
        {
            StarBASIC::Error( ERRCODE_BASIC_CONVERSION );
        }
    }
}
 
void SbRtl_Day(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        SbxVariableRef pArg = rPar.Get(1);
        double aDate = pArg->GetDate();
 
        sal_Int16 nDay = implGetDateDay( aDate );
        rPar.Get(0)->PutInteger(nDay);
    }
}
 
void SbRtl_Year(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        sal_Int16 nYear = implGetDateYear(rPar.Get(1)->GetDate());
        rPar.Get(0)->PutInteger(nYear);
    }
}
 
sal_Int16 implGetHour( double dDate )
{
    double nFrac = (dDate - floor(dDate)) * ::tools::Time::milliSecPerDay;
    sal_uInt64 nMilliSeconds = static_cast<sal_uInt64>(nFrac + 0.5);
    return static_cast<sal_Int16>((nMilliSeconds / ::tools::Time::milliSecPerHour)
                                  % ::tools::Time::hourPerDay);
}
 
void SbRtl_Hour(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        double nArg = rPar.Get(1)->GetDate();
        sal_Int16 nHour = implGetHour( nArg );
        rPar.Get(0)->PutInteger(nHour);
    }
}
 
void SbRtl_Minute(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        double nArg = rPar.Get(1)->GetDate();
        sal_Int16 nMin = implGetMinute( nArg );
        rPar.Get(0)->PutInteger(nMin);
    }
}
 
void SbRtl_Month(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        sal_Int16 nMonth = implGetDateMonth(rPar.Get(1)->GetDate());
        rPar.Get(0)->PutInteger(nMonth);
    }
}
 
sal_Int16 implGetSecond( double dDate )
{
    double nFrac = (dDate - floor(dDate)) * ::tools::Time::milliSecPerDay;
    sal_uInt64 nMilliSeconds = static_cast<sal_uInt64>(nFrac + 0.5);
    return static_cast<sal_Int16>((nMilliSeconds / ::tools::Time::milliSecPerSec)
                                  % ::tools::Time::secondPerMinute);
}
 
sal_Int32 implGetNanoSecond(double dDate)
{
    double nFrac = (dDate - floor(dDate)) * ::tools::Time::milliSecPerDay;
    sal_uInt64 nMilliSeconds = static_cast<sal_uInt64>(nFrac + 0.5);
    nMilliSeconds %= ::tools::Time::milliSecPerSec;
 
    return static_cast<sal_Int32>(nMilliSeconds * ::tools::Time ::nanoPerMilli);
}
 
void SbRtl_Second(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        double nArg = rPar.Get(1)->GetDate();
        sal_Int16 nSecond = implGetSecond( nArg );
        rPar.Get(0)->PutInteger(nSecond);
    }
}
 
double Now_Impl()
{
    // tdf#161469 - align implementation with the now function in calc, i.e., include subseconds
    DateTime aActTime(DateTime::SYSTEM);
    return static_cast<double>(GetDayDiff(aActTime))
           + implTimeSerial(aActTime.GetHour(), aActTime.GetMin(), aActTime.GetSec(),
                            nanoSecToMilliSec(aActTime.GetNanoSec()));
}
 
// Date Now()
 
void SbRtl_Now(StarBASIC*, SbxArray& rPar, bool) { rPar.Get(0)->PutDate(Now_Impl()); }
 
// Date Time()
 
void SbRtl_Time(StarBASIC *, SbxArray & rPar, bool bWrite)
{
    if ( !bWrite )
    {
        tools::Time aTime( tools::Time::SYSTEM );
        SbxVariable* pMeth = rPar.Get(0);
        OUString aRes;
        if( pMeth->IsFixed() )
        {
            // Time$: hh:mm:ss
            char buf[ 20 ];
            snprintf( buf, sizeof(buf), "%02d:%02d:%02d",
                      aTime.GetHour(), aTime.GetMin(), aTime.GetSec() );
            aRes = OUString::createFromAscii( buf );
        }
        else
        {
            // Time: system dependent
            tools::Long nSeconds=aTime.GetHour();
            nSeconds *= 3600;
            nSeconds += aTime.GetMin() * 60;
            nSeconds += aTime.GetSec();
            double nDays = static_cast<double>(nSeconds) * ( 1.0 / (24.0*3600.0) );
            const Color* pCol;
 
            std::shared_ptr<SvNumberFormatter> pFormatter;
            sal_uInt32 nIndex;
            if( GetSbData()->pInst )
            {
                pFormatter = GetSbData()->pInst->GetNumberFormatter();
                nIndex = GetSbData()->pInst->GetStdTimeIdx();
            }
            else
            {
                sal_uInt32 n;   // Dummy
                pFormatter = SbiInstance::PrepareNumberFormatter( n, nIndex, n );
            }
 
            pFormatter->GetOutputString( nDays, nIndex, aRes, &pCol );
        }
        pMeth->PutString( aRes );
    }
    else
    {
        StarBASIC::Error( ERRCODE_BASIC_NOT_IMPLEMENTED );
    }
}
 
void SbRtl_Timer(StarBASIC *, SbxArray & rPar, bool)
{
    tools::Time aTime( tools::Time::SYSTEM );
    tools::Long nSeconds = aTime.GetHour();
    nSeconds *= 3600;
    nSeconds += aTime.GetMin() * 60;
    nSeconds += aTime.GetSec();
    rPar.Get(0)->PutDate(static_cast<double>(nSeconds));
}
 
 
void SbRtl_Date(StarBASIC *, SbxArray & rPar, bool bWrite)
{
    if ( !bWrite )
    {
        Date aToday( Date::SYSTEM );
        double nDays = static_cast<double>(GetDayDiff( aToday ));
        SbxVariable* pMeth = rPar.Get(0);
        if( pMeth->IsString() )
        {
            OUString aRes;
            const Color* pCol;
 
            std::shared_ptr<SvNumberFormatter> pFormatter;
            sal_uInt32 nIndex;
            if( GetSbData()->pInst )
            {
                pFormatter = GetSbData()->pInst->GetNumberFormatter();
                nIndex = GetSbData()->pInst->GetStdDateIdx();
            }
            else
            {
                sal_uInt32 n;
                pFormatter = SbiInstance::PrepareNumberFormatter( nIndex, n, n );
            }
 
            pFormatter->GetOutputString( nDays, nIndex, aRes, &pCol );
            pMeth->PutString( aRes );
        }
        else
        {
            pMeth->PutDate( nDays );
        }
    }
    else
    {
        StarBASIC::Error( ERRCODE_BASIC_NOT_IMPLEMENTED );
    }
}
 
void SbRtl_IsArray(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    rPar.Get(0)->PutBool((rPar.Get(1)->GetType() & SbxARRAY) != 0);
}
 
void SbRtl_IsObject(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    SbxVariable* pVar = rPar.Get(1);
    bool bObject = pVar->IsObject();
    SbxBase* pObj = (bObject ? pVar->GetObject() : nullptr);
 
    if( auto pUnoClass = dynamic_cast<SbUnoClass*>( pObj) )
    {
        bObject = pUnoClass->getUnoClass().is();
    }
    rPar.Get(0)->PutBool(bObject);
}
 
void SbRtl_IsDate(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    // #46134 only string is converted, all other types result in sal_False
    SbxVariableRef xArg = rPar.Get(1);
    SbxDataType eType = xArg->GetType();
    bool bDate = false;
 
    if( eType == SbxDATE )
    {
        bDate = true;
    }
    else if( eType == SbxSTRING )
    {
        ErrCode nPrevError = SbxBase::GetError();
        SbxBase::ResetError();
 
        // force conversion of the parameter to SbxDATE
        xArg->SbxValue::GetDate();
 
        bDate = !SbxBase::IsError();
 
        SbxBase::ResetError();
        SbxBase::SetError( nPrevError );
    }
    rPar.Get(0)->PutBool(bDate);
}
 
void SbRtl_IsEmpty(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    SbxVariable* pVar = nullptr;
    if( SbiRuntime::isVBAEnabled() )
    {
        pVar = getDefaultProp(rPar.Get(1));
    }
    if ( pVar )
    {
        pVar->Broadcast( SfxHintId::BasicDataWanted );
        rPar.Get(0)->PutBool(pVar->IsEmpty());
    }
    else
    {
        rPar.Get(0)->PutBool(rPar.Get(1)->IsEmpty());
    }
}
 
void SbRtl_IsError(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    SbxVariable* pVar = rPar.Get(1);
    SbUnoObject* pObj = dynamic_cast<SbUnoObject*>( pVar  );
    if ( !pObj )
    {
        if ( SbxBase* pBaseObj = (pVar->IsObject() ? pVar->GetObject() : nullptr) )
        {
            pObj = dynamic_cast<SbUnoObject*>( pBaseObj  );
        }
    }
    uno::Reference< script::XErrorQuery > xError;
    if ( pObj )
    {
        xError.set( pObj->getUnoAny(), uno::UNO_QUERY );
    }
    if ( xError.is() )
    {
        rPar.Get(0)->PutBool(xError->hasError());
    }
    else
    {
        rPar.Get(0)->PutBool(rPar.Get(1)->IsErr());
    }
}
 
void SbRtl_IsNull(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    // #51475 because of Uno-objects return true
    // even if the pObj value is NULL
    SbxVariableRef pArg = rPar.Get(1);
    bool bNull = rPar.Get(1)->IsNull();
    if( !bNull && pArg->GetType() == SbxOBJECT )
    {
        SbxBase* pObj = pArg->GetObject();
        if( !pObj )
        {
            bNull = true;
        }
    }
    rPar.Get(0)->PutBool(bNull);
}
 
void SbRtl_IsNumeric(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    rPar.Get(0)->PutBool(rPar.Get(1)->IsNumericRTL());
}
 
 
void SbRtl_IsMissing(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    // #57915 Missing is reported by an error
    rPar.Get(0)->PutBool(rPar.Get(1)->IsErr());
}
 
// Function looks for wildcards, removes them and always returns the pure path
static OUString implSetupWildcard(const OUString& rFileParam, SbiRTLData& rRTLData)
{
    static const char cDelim1 = '/';
    static const char cDelim2 = '\\';
    static const char cWild1 = '*';
    static const char cWild2 = '?';
 
    rRTLData.moWildCard.reset();
    rRTLData.sFullNameToBeChecked.clear();
 
    OUString aFileParam = rFileParam;
    sal_Int32 nLastWild = aFileParam.lastIndexOf( cWild1 );
    if( nLastWild < 0 )
    {
        nLastWild = aFileParam.lastIndexOf( cWild2 );
    }
    bool bHasWildcards = ( nLastWild >= 0 );
 
 
    sal_Int32 nLastDelim = aFileParam.lastIndexOf( cDelim1 );
    if( nLastDelim < 0 )
    {
        nLastDelim = aFileParam.lastIndexOf( cDelim2 );
    }
    if( bHasWildcards )
    {
        // Wildcards in path?
        if( nLastDelim >= 0 && nLastDelim > nLastWild )
        {
            return aFileParam;
        }
    }
    else
    {
        OUString aPathStr = getFullPath( aFileParam );
        if( nLastDelim != aFileParam.getLength() - 1 )
        {
            rRTLData.sFullNameToBeChecked = aPathStr;
        }
        return aPathStr;
    }
 
    OUString aPureFileName;
    if( nLastDelim < 0 )
    {
        aPureFileName = aFileParam;
        aFileParam.clear();
    }
    else
    {
        aPureFileName = aFileParam.copy( nLastDelim + 1 );
        aFileParam = aFileParam.copy( 0, nLastDelim );
    }
 
    // Try again to get a valid URL/UNC-path with only the path
    OUString aPathStr = getFullPath( aFileParam );
 
    // Is there a pure file name left? Otherwise the path is
    // invalid anyway because it was not accepted by OSL before
    if (aPureFileName != "*")
    {
        rRTLData.moWildCard.emplace(aPureFileName);
    }
    return aPathStr;
}
 
static bool implCheckWildcard(std::u16string_view rName, SbiRTLData const& rRTLData)
{
    bool bMatch = true;
 
    if (rRTLData.moWildCard)
    {
        bMatch = rRTLData.moWildCard->Matches(rName);
    }
    return bMatch;
}
 
 
static bool isRootDir( std::u16string_view aDirURLStr )
{
    INetURLObject aDirURLObj( aDirURLStr );
    bool bRoot = false;
 
    // Check if it's a root directory
    sal_Int32 nCount = aDirURLObj.getSegmentCount();
 
    // No segment means Unix root directory "file:///"
    if( nCount == 0 )
    {
        bRoot = true;
    }
    // Exactly one segment needs further checking, because it
    // can be Unix "file:///foo/" -> no root
    // or Windows  "file:///c:/"  -> root
    else if( nCount == 1 )
    {
        OUString aSeg1 = aDirURLObj.getName( 0, true,
                                             INetURLObject::DecodeMechanism::WithCharset );
        if( aSeg1[1] == ':' )
        {
            bRoot = true;
        }
    }
    // More than one segments can never be root
    // so bRoot remains false
 
    return bRoot;
}
 
void SbRtl_Dir(StarBASIC *, SbxArray & rPar, bool)
{
    OUString aPath;
 
    const sal_uInt32 nParCount = rPar.Count();
    if( nParCount > 3 )
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        SbiRTLData& rRTLData = GetSbData()->pInst->GetRTLData();
 
        if( hasUno() )
        {
            const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
            if( xSFI.is() )
            {
                if ( nParCount >= 2 )
                {
                    OUString aFileParam = rPar.Get(1)->GetOUString();
 
                    OUString aFileURLStr = implSetupWildcard(aFileParam, rRTLData);
                    if (!rRTLData.sFullNameToBeChecked.isEmpty())
                    {
                        bool bExists = false;
                        try { bExists = xSFI->exists( aFileURLStr ); }
                        catch(const Exception & ) {}
 
                        OUString aNameOnlyStr;
                        if( bExists )
                        {
                            INetURLObject aFileURL( aFileURLStr );
                            aNameOnlyStr = aFileURL.getName( INetURLObject::LAST_SEGMENT,
                                                             true, INetURLObject::DecodeMechanism::WithCharset );
                        }
                        rPar.Get(0)->PutString(aNameOnlyStr);
                        return;
                    }
 
                    try
                    {
                        OUString aDirURLStr;
                        bool bFolder = xSFI->isFolder( aFileURLStr );
 
                        if( bFolder )
                        {
                            aDirURLStr = aFileURLStr;
                        }
                        else
                        {
                            rPar.Get(0)->PutString(u""_ustr);
                        }
 
                        sal_Int16 nFlags = SbAttributes::NORMAL;
                        if ( nParCount > 2 )
                        {
                            rRTLData.nDirFlags = nFlags = rPar.Get(2)->GetInteger();
                        }
                        else
                        {
                            rRTLData.nDirFlags = SbAttributes::NORMAL;
                        }
                        // Read directory
                        bool bIncludeFolders = bool(nFlags & SbAttributes::DIRECTORY);
                        rRTLData.aDirSeq = xSFI->getFolderContents(aDirURLStr, bIncludeFolders);
                        rRTLData.nCurDirPos = 0;
 
                        // #78651 Add "." and ".." directories for VB compatibility
                        if( bIncludeFolders )
                        {
                            bool bRoot = isRootDir( aDirURLStr );
 
                            // If it's no root directory we flag the need for
                            // the "." and ".." directories by the value -2
                            // for the actual position. Later for -2 will be
                            // returned "." and for -1 ".."
                            if( !bRoot )
                            {
                                rRTLData.nCurDirPos = -2;
                            }
                        }
                    }
                    catch(const Exception & )
                    {
                    }
                }
 
 
                if (rRTLData.aDirSeq.hasElements())
                {
                    bool bFolderFlag = bool(rRTLData.nDirFlags & SbAttributes::DIRECTORY);
 
                    SbiInstance* pInst = GetSbData()->pInst;
                    bool bCompatibility = ( pInst && pInst->IsCompatibility() );
                    for( ;; )
                    {
                        if (rRTLData.nCurDirPos < 0)
                        {
                            if (rRTLData.nCurDirPos == -2)
                            {
                                aPath = ".";
                            }
                            else if (rRTLData.nCurDirPos == -1)
                            {
                                aPath = "..";
                            }
                            rRTLData.nCurDirPos++;
                        }
                        else if (rRTLData.nCurDirPos >= rRTLData.aDirSeq.getLength())
                        {
                            rRTLData.aDirSeq.realloc(0);
                            aPath.clear();
                            break;
                        }
                        else
                        {
                            OUString aFile
                                = rRTLData.aDirSeq.getConstArray()[rRTLData.nCurDirPos++];
 
                            if( bCompatibility )
                            {
                                if( !bFolderFlag )
                                {
                                    bool bFolder = xSFI->isFolder( aFile );
                                    if( bFolder )
                                    {
                                        continue;
                                    }
                                }
                            }
                            else
                            {
                                // Only directories
                                if( bFolderFlag )
                                {
                                    bool bFolder = xSFI->isFolder( aFile );
                                    if( !bFolder )
                                    {
                                        continue;
                                    }
                                }
                            }
 
                            INetURLObject aURL( aFile );
                            aPath = aURL.getName( INetURLObject::LAST_SEGMENT, true,
                                                  INetURLObject::DecodeMechanism::WithCharset );
                        }
 
                        bool bMatch = implCheckWildcard(aPath, rRTLData);
                        if( !bMatch )
                        {
                            continue;
                        }
                        break;
                    }
                }
                rPar.Get(0)->PutString(aPath);
            }
        }
        else
        {
            // TODO: OSL
            if ( nParCount >= 2 )
            {
                OUString aFileParam = rPar.Get(1)->GetOUString();
 
                OUString aDirURL = implSetupWildcard(aFileParam, rRTLData);
 
                sal_Int16 nFlags = SbAttributes::NORMAL;
                if ( nParCount > 2 )
                {
                    rRTLData.nDirFlags = nFlags = rPar.Get(2)->GetInteger();
                }
                else
                {
                    rRTLData.nDirFlags = SbAttributes::NORMAL;
                }
 
                // Read directory
                bool bIncludeFolders = bool(nFlags & SbAttributes::DIRECTORY);
                rRTLData.pDir = std::make_unique<Directory>(aDirURL);
                FileBase::RC nRet = rRTLData.pDir->open();
                if( nRet != FileBase::E_None )
                {
                    rRTLData.pDir.reset();
                    rPar.Get(0)->PutString(OUString());
                    return;
                }
 
                // #86950 Add "." and ".." directories for VB compatibility
                rRTLData.nCurDirPos = 0;
                if( bIncludeFolders )
                {
                    bool bRoot = isRootDir( aDirURL );
 
                    // If it's no root directory we flag the need for
                    // the "." and ".." directories by the value -2
                    // for the actual position. Later for -2 will be
                    // returned "." and for -1 ".."
                    if( !bRoot )
                    {
                        rRTLData.nCurDirPos = -2;
                    }
                }
 
            }
 
            if (rRTLData.pDir)
            {
                bool bFolderFlag = bool(rRTLData.nDirFlags & SbAttributes::DIRECTORY);
                for( ;; )
                {
                    if (rRTLData.nCurDirPos < 0)
                    {
                        if (rRTLData.nCurDirPos == -2)
                        {
                            aPath = ".";
                        }
                        else if (rRTLData.nCurDirPos == -1)
                        {
                            aPath = "..";
                        }
                        rRTLData.nCurDirPos++;
                    }
                    else
                    {
                        DirectoryItem aItem;
                        FileBase::RC nRet = rRTLData.pDir->getNextItem(aItem);
                        if( nRet != FileBase::E_None )
                        {
                            rRTLData.pDir.reset();
                            aPath.clear();
                            break;
                        }
 
                        // Handle flags
                        FileStatus aFileStatus( osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName );
                        nRet = aItem.getFileStatus( aFileStatus );
                        if( nRet != FileBase::E_None )
                        {
                            SAL_WARN("basic", "getFileStatus failed");
                            continue;
                        }
 
                        // Only directories?
                        if( bFolderFlag )
                        {
                            FileStatus::Type aType = aFileStatus.getFileType();
                            bool bFolder = isFolder( aType );
                            if( !bFolder )
                            {
                                continue;
                            }
                        }
 
                        aPath = aFileStatus.getFileName();
                    }
 
                    bool bMatch = implCheckWildcard(aPath, rRTLData);
                    if( !bMatch )
                    {
                        continue;
                    }
                    break;
                }
            }
            rPar.Get(0)->PutString(aPath);
        }
    }
}
 
 
void SbRtl_GetAttr(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() == 2)
    {
        sal_Int16 nFlags = SbAttributes::NORMAL;
 
        // In Windows, we want to use Windows API to get the file attributes
        // for VBA interoperability.
    #if defined(_WIN32)
        if( SbiRuntime::isVBAEnabled() )
        {
            OUString aPathURL = getFullPath(rPar.Get(1)->GetOUString());
            OUString aPath;
            FileBase::getSystemPathFromFileURL( aPathURL, aPath );
            DWORD nRealFlags = GetFileAttributesW (o3tl::toW(aPath.getStr()));
            if (nRealFlags != 0xffffffff)
            {
                if (nRealFlags == FILE_ATTRIBUTE_NORMAL)
                {
                    nRealFlags = 0;
                }
                nFlags = static_cast<sal_Int16>(nRealFlags);
            }
            else
            {
                StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
            }
            rPar.Get(0)->PutInteger(nFlags);
 
            return;
        }
    #endif
 
        if( hasUno() )
        {
            const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
            if( xSFI.is() )
            {
                try
                {
                    OUString aPath = getFullPath(rPar.Get(1)->GetOUString());
                    bool bExists = false;
                    try { bExists = xSFI->exists( aPath ); }
                    catch(const Exception & ) {}
                    if( !bExists )
                    {
                        return StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
                    }
 
                    bool bReadOnly = xSFI->isReadOnly( aPath );
                    bool bHidden = xSFI->isHidden( aPath );
                    bool bDirectory = xSFI->isFolder( aPath );
                    if( bReadOnly )
                    {
                        nFlags |= SbAttributes::READONLY;
                    }
                    if( bHidden )
                    {
                        nFlags |= SbAttributes::HIDDEN;
                    }
                    if( bDirectory )
                    {
                        nFlags |= SbAttributes::DIRECTORY;
                    }
                }
                catch(const Exception & )
                {
                    StarBASIC::Error( ERRCODE_IO_GENERAL );
                }
            }
        }
        else
        {
            DirectoryItem aItem;
            (void)DirectoryItem::get(getFullPath(rPar.Get(1)->GetOUString()), aItem);
            FileStatus aFileStatus( osl_FileStatus_Mask_Attributes | osl_FileStatus_Mask_Type );
            (void)aItem.getFileStatus( aFileStatus );
            sal_uInt64 nAttributes = aFileStatus.getAttributes();
            bool bReadOnly = (nAttributes & osl_File_Attribute_ReadOnly) != 0;
 
            FileStatus::Type aType = aFileStatus.getFileType();
            bool bDirectory = isFolder( aType );
            if( bReadOnly )
            {
                nFlags |= SbAttributes::READONLY;
            }
            if( bDirectory )
            {
                nFlags |= SbAttributes::DIRECTORY;
            }
        }
        rPar.Get(0)->PutInteger(nFlags);
    }
    else
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
}
 
 
void SbRtl_FileDateTime(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        OUString aPath = rPar.Get(1)->GetOUString();
        tools::Time aTime( tools::Time::EMPTY );
        Date aDate( Date::EMPTY );
        if( hasUno() )
        {
            const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
            if( xSFI.is() )
            {
                try
                {
                    util::DateTime aUnoDT = xSFI->getDateTimeModified( aPath );
                    aTime = tools::Time( aUnoDT );
                    aDate = Date( aUnoDT );
                }
                catch(const Exception & )
                {
                    StarBASIC::Error( ERRCODE_IO_GENERAL );
                }
            }
        }
        else
        {
            bool bSuccess = false;
            do
            {
                DirectoryItem aItem;
                if (DirectoryItem::get( getFullPath( aPath ), aItem ) != FileBase::E_None)
                    break;
 
                FileStatus aFileStatus( osl_FileStatus_Mask_ModifyTime );
                if (aItem.getFileStatus( aFileStatus ) != FileBase::E_None)
                    break;
 
                TimeValue aTimeVal = aFileStatus.getModifyTime();
                oslDateTime aDT;
                if (!osl_getDateTimeFromTimeValue( &aTimeVal, &aDT ))
                    // Strictly spoken this is not an i/o error but some other failure.
                    break;
 
                aTime = tools::Time( aDT.Hours, aDT.Minutes, aDT.Seconds, aDT.NanoSeconds );
                aDate = Date( aDT.Day, aDT.Month, aDT.Year );
                bSuccess = true;
            }
            while(false);
 
            if (!bSuccess)
                StarBASIC::Error( ERRCODE_IO_GENERAL );
        }
 
        // An empty date shall not result in a formatted null-date (1899-12-30
        // or 1900-01-01) or even worse -0001-12-03 or some such due to how
        // GetDayDiff() treats things. There should be an error set in this
        // case anyway because of a missing file or other error above, but... so
        // do not even bother to use the number formatter.
        OUString aRes;
        if (aDate.IsEmpty())
        {
            aRes = "0000-00-00 00:00:00";
        }
        else
        {
            double fSerial = static_cast<double>(GetDayDiff( aDate ));
            tools::Long nSeconds = aTime.GetHour();
            nSeconds *= 3600;
            nSeconds += aTime.GetMin() * 60;
            nSeconds += aTime.GetSec();
            double nDays = static_cast<double>(nSeconds) / (24.0*3600.0);
            fSerial += nDays;
 
            const Color* pCol;
 
            std::shared_ptr<SvNumberFormatter> pFormatter;
            sal_uInt32 nIndex;
            if( GetSbData()->pInst )
            {
                pFormatter = GetSbData()->pInst->GetNumberFormatter();
                nIndex = GetSbData()->pInst->GetStdDateTimeIdx();
            }
            else
            {
                sal_uInt32 n;
                pFormatter = SbiInstance::PrepareNumberFormatter( n, n, nIndex );
            }
 
            pFormatter->GetOutputString( fSerial, nIndex, aRes, &pCol );
        }
        rPar.Get(0)->PutString(aRes);
    }
}
 
 
void SbRtl_EOF(StarBASIC *, SbxArray & rPar, bool)
{
    // No changes for UCB
    if (rPar.Count() != 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        sal_Int16 nChannel = rPar.Get(1)->GetInteger();
        SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
        SbiStream* pSbStrm = pIO->GetStream( nChannel );
        if ( !pSbStrm )
        {
            return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
        }
        bool beof;
        SvStream* pSvStrm = pSbStrm->GetStrm();
        if ( pSbStrm->IsText() )
        {
            char cBla;
            (*pSvStrm).ReadChar( cBla ); // can we read another character?
            beof = pSvStrm->eof();
            if ( !beof )
            {
                pSvStrm->SeekRel( -1 );
            }
        }
        else
        {
            beof = pSvStrm->eof();  // for binary data!
        }
        rPar.Get(0)->PutBool(beof);
    }
}
 
void SbRtl_FileAttr(StarBASIC *, SbxArray & rPar, bool)
{
    // No changes for UCB
    // #57064 Although this function doesn't operate with DirEntry, it is
    // not touched by the adjustment to virtual URLs, as it only works on
    // already opened files and the name doesn't matter there.
 
    if (rPar.Count() != 3)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        sal_Int16 nChannel = rPar.Get(1)->GetInteger();
        SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
        SbiStream* pSbStrm = pIO->GetStream( nChannel );
        if ( !pSbStrm )
        {
            return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
        }
        sal_Int16 nRet;
        if (rPar.Get(2)->GetInteger() == 1)
        {
            nRet = static_cast<sal_Int16>(pSbStrm->GetMode());
        }
        else
        {
            nRet = 0; // System file handle not supported
        }
        rPar.Get(0)->PutInteger(nRet);
    }
}
void SbRtl_Loc(StarBASIC *, SbxArray & rPar, bool)
{
    // No changes for UCB
    if (rPar.Count() != 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        sal_Int16 nChannel = rPar.Get(1)->GetInteger();
        SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
        SbiStream* pSbStrm = pIO->GetStream( nChannel );
        if ( !pSbStrm )
        {
            return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
        }
        SvStream* pSvStrm = pSbStrm->GetStrm();
        std::size_t nPos;
        if( pSbStrm->IsRandom())
        {
            short nBlockLen = pSbStrm->GetBlockLen();
            nPos = nBlockLen ? (pSvStrm->Tell() / nBlockLen) : 0;
            nPos++; // block positions starting at 1
        }
        else if ( pSbStrm->IsText() )
        {
            nPos = pSbStrm->GetLine();
        }
        else if( pSbStrm->IsBinary() )
        {
            nPos = pSvStrm->Tell();
        }
        else if ( pSbStrm->IsSeq() )
        {
            nPos = ( pSvStrm->Tell()+1 ) / 128;
        }
        else
        {
            nPos = pSvStrm->Tell();
        }
        rPar.Get(0)->PutLong(static_cast<sal_Int32>(nPos));
    }
}
 
void SbRtl_Lof(StarBASIC *, SbxArray & rPar, bool)
{
    // No changes for UCB
    if (rPar.Count() != 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        sal_Int16 nChannel = rPar.Get(1)->GetInteger();
        SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
        SbiStream* pSbStrm = pIO->GetStream( nChannel );
        if ( !pSbStrm )
        {
            return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
        }
        SvStream* pSvStrm = pSbStrm->GetStrm();
        sal_uInt64 const nLen = pSvStrm->TellEnd();
        rPar.Get(0)->PutLong(static_cast<sal_Int32>(nLen));
    }
}
 
 
void SbRtl_Seek(StarBASIC *, SbxArray & rPar, bool)
{
    // No changes for UCB
    int nArgs = static_cast<int>(rPar.Count());
    if ( nArgs < 2 || nArgs > 3 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    sal_Int16 nChannel = rPar.Get(1)->GetInteger();
    SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
    SbiStream* pSbStrm = pIO->GetStream( nChannel );
    if ( !pSbStrm )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
    }
    SvStream* pStrm = pSbStrm->GetStrm();
 
    if ( nArgs == 2 )   // Seek-Function
    {
        sal_uInt64 nPos = pStrm->Tell();
        if( pSbStrm->IsRandom() )
        {
            nPos = nPos / pSbStrm->GetBlockLen();
        }
        nPos++; // Basic counts from 1
        rPar.Get(0)->PutLong(static_cast<sal_Int32>(nPos));
    }
    else                // Seek-Statement
    {
        sal_Int32 nPos = rPar.Get(2)->GetLong();
        if ( nPos < 1 )
        {
            return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
        }
        nPos--; // Basic counts from 1, SvStreams count from 0
        pSbStrm->SetExpandOnWriteTo( 0 );
        if ( pSbStrm->IsRandom() )
        {
            nPos *= pSbStrm->GetBlockLen();
        }
        pStrm->Seek( static_cast<sal_uInt64>(nPos) );
        pSbStrm->SetExpandOnWriteTo( nPos );
    }
}
 
void SbRtl_Format(StarBASIC *, SbxArray & rPar, bool)
{
    const sal_uInt32 nArgCount = rPar.Count();
    if ( nArgCount < 2 || nArgCount > 3 )
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        OUString aResult;
        if( nArgCount == 2 )
        {
            rPar.Get(1)->Format(aResult);
        }
        else
        {
            OUString aFmt(rPar.Get(2)->GetOUString());
            rPar.Get(1)->Format(aResult, &aFmt);
        }
        rPar.Get(0)->PutString(aResult);
    }
}
 
static bool IsMissing(SbxArray& rPar, const sal_uInt32 i)
{
    const sal_uInt32 nArgCount = rPar.Count();
    if (nArgCount <= i)
        return true;
 
    SbxVariable* aPar = rPar.Get(i);
    return (aPar->GetType() == SbxERROR && SbiRuntime::IsMissing(aPar, 1));
}
 
static sal_Int16 GetOptionalIntegerParamOrDefault(SbxArray& rPar, const sal_uInt32 i,
                                                  const sal_Int16 defaultValue)
{
    return IsMissing(rPar, i) ? defaultValue : rPar.Get(i)->GetInteger();
}
 
static OUString GetOptionalOUStringParamOrDefault(SbxArray& rPar, const sal_uInt32 i,
                                                  const OUString& defaultValue)
{
    return IsMissing(rPar, i) ? defaultValue : rPar.Get(i)->GetOUString();
}
 
static void lcl_FormatNumberPercent(SbxArray& rPar, bool isPercent)
{
    const sal_uInt32 nArgCount = rPar.Count();
    if (nArgCount < 2 || nArgCount > 6)
    {
        return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
    }
 
    // The UI locale never changes -> we can use static value here
    static const LocaleDataWrapper localeData(Application::GetSettings().GetUILanguageTag());
    sal_Int16 nNumDigitsAfterDecimal = -1;
    if (nArgCount > 2 && !rPar.Get(2)->IsEmpty())
    {
        nNumDigitsAfterDecimal = rPar.Get(2)->GetInteger();
        if (nNumDigitsAfterDecimal < -1)
        {
            return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
        }
        else if (nNumDigitsAfterDecimal > 255)
            nNumDigitsAfterDecimal %= 256;
    }
    if (nNumDigitsAfterDecimal == -1)
        nNumDigitsAfterDecimal = LocaleDataWrapper::getNumDigits();
 
    bool bIncludeLeadingDigit = LocaleDataWrapper::isNumLeadingZero();
    if (nArgCount > 3 && !rPar.Get(3)->IsEmpty())
    {
        switch (rPar.Get(3)->GetInteger())
        {
            case ooo::vba::VbTriState::vbFalse:
                bIncludeLeadingDigit = false;
                break;
            case ooo::vba::VbTriState::vbTrue:
                bIncludeLeadingDigit = true;
                break;
            case ooo::vba::VbTriState::vbUseDefault:
                // do nothing;
                break;
            default:
                return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
        }
    }
 
    bool bUseParensForNegativeNumbers = false;
    if (nArgCount > 4 && !rPar.Get(4)->IsEmpty())
    {
        switch (rPar.Get(4)->GetInteger())
        {
            case ooo::vba::VbTriState::vbFalse:
            case ooo::vba::VbTriState::vbUseDefault:
                // do nothing
                break;
            case ooo::vba::VbTriState::vbTrue:
                bUseParensForNegativeNumbers = true;
                break;
            default:
                return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
        }
    }
 
    bool bGroupDigits = false;
    if (nArgCount > 5 && !rPar.Get(5)->IsEmpty())
    {
        switch (rPar.Get(5)->GetInteger())
        {
            case ooo::vba::VbTriState::vbFalse:
            case ooo::vba::VbTriState::vbUseDefault:
                // do nothing
                break;
            case ooo::vba::VbTriState::vbTrue:
                bGroupDigits = true;
                break;
            default:
                return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
        }
    }
 
    double fVal = rPar.Get(1)->GetDouble();
    if (isPercent)
        fVal *= 100;
    const bool bNegative = fVal < 0;
    if (bNegative)
        fVal = fabs(fVal); // Always work with non-negatives, to easily handle leading zero
 
    static const sal_Unicode decSep = localeData.getNumDecimalSep().toChar();
    OUStringBuffer aResult;
    rtl::math::doubleToUStringBuffer(aResult,
        fVal, rtl_math_StringFormat_F, nNumDigitsAfterDecimal, decSep,
        bGroupDigits ? localeData.getDigitGrouping().getConstArray() : nullptr,
        localeData.getNumThousandSep().toChar());
 
    if (!bIncludeLeadingDigit && aResult.getLength() > 1)
        aResult.stripStart('0');
 
    if (nNumDigitsAfterDecimal > 0)
    {
        const sal_Int32 nSepPos = aResult.indexOf(decSep);
 
        // VBA allows up to 255 digits; rtl::math::doubleToUString outputs up to 15 digits
        // for ~small numbers, so pad them as appropriate.
        if (nSepPos >= 0)
            comphelper::string::padToLength(aResult, nSepPos + nNumDigitsAfterDecimal + 1, '0');
    }
 
    if (bNegative)
    {
        if (bUseParensForNegativeNumbers)
            aResult.insert(0, '(').append(')');
        else
            aResult.insert(0, '-');
    }
    if (isPercent)
        aResult.append('%');
    rPar.Get(0)->PutString(aResult.makeStringAndClear());
}
 
// https://docs.microsoft.com/en-us/office/vba/Language/Reference/User-Interface-Help/formatnumber-function
void SbRtl_FormatNumber(StarBASIC*, SbxArray& rPar, bool)
{
    return lcl_FormatNumberPercent(rPar, false);
}
 
// https://docs.microsoft.com/en-us/office/vba/Language/Reference/User-Interface-Help/formatpercent-function
void SbRtl_FormatPercent(StarBASIC*, SbxArray& rPar, bool)
{
    return lcl_FormatNumberPercent(rPar, true);
}
 
namespace {
 
// note: BASIC does not use comphelper::random, because
// Randomize(int) must be supported and should not affect non-BASIC random use
struct RandomNumberGenerator
{
    std::mt19937 global_rng;
 
    RandomNumberGenerator()
    {
        try
        {
            std::random_device rd;
            // initialises the state of the global random number generator
            // should only be called once.
            // (note, a few std::variate_generator<> (like normal) have their
            // own state which would need a reset as well to guarantee identical
            // sequence of numbers, e.g. via myrand.distribution().reset())
            global_rng.seed(rd() ^ time(nullptr));
        }
        catch (std::runtime_error& e)
        {
            SAL_WARN("basic", "Using std::random_device failed: " << e.what());
            global_rng.seed(time(nullptr));
        }
    }
};
 
RandomNumberGenerator& theRandomNumberGenerator()
{
    static RandomNumberGenerator theGenerator;
    return theGenerator;
}
 
}
 
void SbRtl_Randomize(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() > 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    if (rPar.Count() == 2)
    {
        int nSeed = static_cast<int>(rPar.Get(1)->GetInteger());
        theRandomNumberGenerator().global_rng.seed(nSeed);
    }
    // without parameter, no need to do anything - RNG is seeded at first use
}
 
void SbRtl_Rnd(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() > 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        std::uniform_real_distribution<double> dist(0.0, 1.0);
        double const tmp(dist(theRandomNumberGenerator().global_rng));
        rPar.Get(0)->PutDouble(tmp);
    }
}
 
 
//  Syntax: Shell("Path",[ Window-Style,[ "Params", [ bSync = sal_False ]]])
//  WindowStyles (VBA compatible):
//      2 == Minimized
//      3 == Maximized
//     10 == Full-Screen (text mode applications OS/2, WIN95, WNT)
//     HACK: The WindowStyle will be passed to
//     Application::StartApp in Creator. Format: "xxxx2"
 
 
void SbRtl_Shell(StarBASIC *, SbxArray & rPar, bool)
{
    const sal_uInt32 nArgCount = rPar.Count();
    if ( nArgCount < 2 || nArgCount > 5 )
    {
        rPar.Get(0)->PutLong(0);
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        oslProcessOption nOptions = osl_Process_SEARCHPATH | osl_Process_DETACHED;
 
        OUString aCmdLine = rPar.Get(1)->GetOUString();
        // attach additional parameters - everything must be parsed anyway
        if( nArgCount >= 4 )
        {
            OUString tmp = rPar.Get(3)->GetOUString().trim();
            if (!tmp.isEmpty())
            {
                aCmdLine += " " + tmp;
            }
        }
        else if( aCmdLine.isEmpty() )
        {
            // avoid special treatment (empty list)
            aCmdLine += " ";
        }
        sal_Int32 nLen = aCmdLine.getLength();
 
        // #55735 if there are parameters, they have to be separated
        // #72471 also separate the single parameters
        std::vector<OUString> aTokenVector;
        OUString aToken;
        sal_Int32 i = 0;
        sal_Unicode c;
        while( i < nLen )
        {
            for ( ;; ++i )
            {
                c = aCmdLine[ i ];
                if ( c != ' ' && c != '\t' )
                {
                    break;
                }
            }
 
            if( c == '\"' || c == '\'' )
            {
                sal_Int32 iFoundPos = aCmdLine.indexOf( c, i + 1 );
 
                if( iFoundPos < 0 )
                {
                    aToken = aCmdLine.copy( i);
                    i = nLen;
                }
                else
                {
                    aToken = aCmdLine.copy( i + 1, (iFoundPos - i - 1) );
                    i = iFoundPos + 1;
                }
            }
            else
            {
                sal_Int32 iFoundSpacePos = aCmdLine.indexOf( ' ', i );
                sal_Int32 iFoundTabPos = aCmdLine.indexOf( '\t', i );
                sal_Int32 iFoundPos = iFoundSpacePos >= 0 ? iFoundTabPos >= 0 ? std::min( iFoundSpacePos, iFoundTabPos ) : iFoundSpacePos : -1;
 
                if( iFoundPos < 0 )
                {
                    aToken = aCmdLine.copy( i );
                    i = nLen;
                }
                else
                {
                    aToken = aCmdLine.copy( i, (iFoundPos - i) );
                    i = iFoundPos;
                }
            }
 
            // insert into the list
            aTokenVector.push_back( aToken );
        }
        // #55735 / #72471 end
 
        sal_Int16 nWinStyle = 0;
        if( nArgCount >= 3 )
        {
            nWinStyle = rPar.Get(2)->GetInteger();
            switch( nWinStyle )
            {
            case 2:
                nOptions |= osl_Process_MINIMIZED;
                break;
            case 3:
                nOptions |= osl_Process_MAXIMIZED;
                break;
            case 10:
                nOptions |= osl_Process_FULLSCREEN;
                break;
            }
 
            bool bSync = false;
            if( nArgCount >= 5 )
            {
                bSync = rPar.Get(4)->GetBool();
            }
            if( bSync )
            {
                nOptions |= osl_Process_WAIT;
            }
        }
 
        // #72471 work parameter(s) up
        std::vector<OUString>::const_iterator iter = aTokenVector.begin();
        OUString aOUStrProgURL = getFullPath( *iter );
 
        ++iter;
 
        sal_uInt16 nParamCount = sal::static_int_cast< sal_uInt16 >(aTokenVector.size() - 1 );
        std::unique_ptr<rtl_uString*[]> pParamList;
        if( nParamCount )
        {
            pParamList.reset( new rtl_uString*[nParamCount]);
            for(int iVector = 0; iter != aTokenVector.end(); ++iVector, ++iter)
            {
                const OUString& rParamStr = *iter;
                pParamList[iVector] = nullptr;
                rtl_uString_assign(&(pParamList[iVector]), rParamStr.pData);
            }
        }
 
        oslProcess pApp;
        bool bSucc = osl_executeProcess(
                    aOUStrProgURL.pData,
                    pParamList.get(),
                    nParamCount,
                    nOptions,
                    nullptr,
                    nullptr,
                    nullptr, 0,
                    &pApp ) == osl_Process_E_None;
 
        // 53521 only free process handle on success
        if (bSucc)
        {
            osl_freeProcessHandle( pApp );
        }
 
        for(int j = 0; j < nParamCount; ++j)
        {
            rtl_uString_release(pParamList[j]);
        }
 
        if( !bSucc )
        {
            StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
        }
        else
        {
            rPar.Get(0)->PutLong(0);
        }
    }
}
 
void SbRtl_VarType(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        SbxDataType eType = rPar.Get(1)->GetType();
        rPar.Get(0)->PutInteger(static_cast<sal_Int16>(eType));
    }
}
 
// Exported function
const OUString & getBasicTypeName( SbxDataType eType )
{
    static constexpr OUString pTypeNames[] =
    {
        u"Empty"_ustr,            // SbxEMPTY
        u"Null"_ustr,             // SbxNULL
        u"Integer"_ustr,          // SbxINTEGER
        u"Long"_ustr,             // SbxLONG
        u"Single"_ustr,           // SbxSINGLE
        u"Double"_ustr,           // SbxDOUBLE
        u"Currency"_ustr,         // SbxCURRENCY
        u"Date"_ustr,             // SbxDATE
        u"String"_ustr,           // SbxSTRING
        u"Object"_ustr,           // SbxOBJECT
        u"Error"_ustr,            // SbxERROR
        u"Boolean"_ustr,          // SbxBOOL
        u"Variant"_ustr,          // SbxVARIANT
        u"DataObject"_ustr,       // SbxDATAOBJECT
        u"Unknown Type"_ustr,
        u"Unknown Type"_ustr,
        u"Char"_ustr,             // SbxCHAR
        u"Byte"_ustr,             // SbxBYTE
        u"UShort"_ustr,           // SbxUSHORT
        u"ULong"_ustr,            // SbxULONG
        u"Long64"_ustr,           // SbxLONG64
        u"ULong64"_ustr,          // SbxULONG64
        u"Int"_ustr,              // SbxINT
        u"UInt"_ustr,             // SbxUINT
        u"Void"_ustr,             // SbxVOID
        u"HResult"_ustr,          // SbxHRESULT
        u"Pointer"_ustr,          // SbxPOINTER
        u"DimArray"_ustr,         // SbxDIMARRAY
        u"CArray"_ustr,           // SbxCARRAY
        u"Userdef"_ustr,          // SbxUSERDEF
        u"Lpstr"_ustr,            // SbxLPSTR
        u"Lpwstr"_ustr,           // SbxLPWSTR
        u"Unknown Type"_ustr,     // SbxCoreSTRING
        u"WString"_ustr,          // SbxWSTRING
        u"WChar"_ustr,            // SbxWCHAR
        u"Int64"_ustr,            // SbxSALINT64
        u"UInt64"_ustr,           // SbxSALUINT64
        u"Decimal"_ustr,          // SbxDECIMAL
    };
 
    size_t nPos = static_cast<size_t>(eType) & 0x0FFF;
    const size_t nTypeNameCount = std::size( pTypeNames );
    if ( nPos >= nTypeNameCount )
    {
        nPos = nTypeNameCount - 1;
    }
    return pTypeNames[nPos];
}
 
static OUString getObjectTypeName( SbxVariable* pVar )
{
    OUString sRet( u"Object"_ustr );
    if ( pVar )
    {
        SbxBase* pBaseObj = pVar->GetObject();
        if( !pBaseObj )
        {
           sRet = "Nothing";
        }
        else
        {
            SbUnoObject* pUnoObj = dynamic_cast<SbUnoObject*>( pVar  );
            if ( !pUnoObj )
            {
                pUnoObj = dynamic_cast<SbUnoObject*>( pBaseObj  );
            }
            if ( pUnoObj )
            {
                Any aObj = pUnoObj->getUnoAny();
                // For upstreaming unless we start to build oovbaapi by default
                // we need to get detect the vba-ness of the object in some
                // other way
                // note: Automation objects do not support XServiceInfo
                uno::Reference< XServiceInfo > xServInfo( aObj, uno::UNO_QUERY );
                if ( xServInfo.is() )
                {
                    // is this a VBA object ?
                    Sequence< OUString > sServices = xServInfo->getSupportedServiceNames();
                    if ( sServices.hasElements() )
                    {
                        sRet = sServices[ 0 ];
                    }
                }
                else
                {
                    uno::Reference< bridge::oleautomation::XAutomationObject > xAutoMation( aObj, uno::UNO_QUERY );
                    if ( xAutoMation.is() )
                    {
                        uno::Reference< script::XInvocation > xInv( aObj, uno::UNO_QUERY );
                        if ( xInv.is() )
                        {
                            try
                            {
                                xInv->getValue( u"$GetTypeName"_ustr ) >>= sRet;
                            }
                            catch(const Exception& )
                            {
                            }
                        }
                    }
                }
                sal_Int32 nDot = sRet.lastIndexOf( '.' );
                if ( nDot != -1 && nDot < sRet.getLength() )
                {
                    sRet = sRet.copy( nDot + 1 );
                }
            }
        }
    }
    return sRet;
}
 
void SbRtl_TypeName(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        SbxDataType eType = rPar.Get(1)->GetType();
        bool bIsArray = ( ( eType & SbxARRAY ) != 0 );
 
        OUString aRetStr;
        if ( SbiRuntime::isVBAEnabled() && eType == SbxOBJECT )
        {
            aRetStr = getObjectTypeName(rPar.Get(1));
        }
        else
        {
            aRetStr = getBasicTypeName( eType );
        }
        if( bIsArray )
        {
            aRetStr += "()";
        }
        rPar.Get(0)->PutString(aRetStr);
    }
}
 
void SbRtl_Len(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        const OUString aStr = rPar.Get(1)->GetOUString();
        rPar.Get(0)->PutLong(aStr.getLength());
    }
}
 
void SbRtl_DDEInitiate(StarBASIC *, SbxArray & rPar, bool)
{
    int nArgs = static_cast<int>(rPar.Count());
    if ( nArgs != 3 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    const OUString aApp = rPar.Get(1)->GetOUString();
    const OUString aTopic = rPar.Get(2)->GetOUString();
 
    SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
    size_t nChannel;
    ErrCode nDdeErr = pDDE->Initiate( aApp, aTopic, nChannel );
    if( nDdeErr )
    {
        StarBASIC::Error( nDdeErr );
    }
    else
    {
        rPar.Get(0)->PutInteger(static_cast<sal_Int16>(nChannel));
    }
}
 
void SbRtl_DDETerminate(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    int nArgs = static_cast<int>(rPar.Count());
    if ( nArgs != 2 )
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
        return;
    }
    size_t nChannel = rPar.Get(1)->GetInteger();
    SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
    ErrCode nDdeErr = pDDE->Terminate( nChannel );
    if( nDdeErr )
    {
        StarBASIC::Error( nDdeErr );
    }
}
 
void SbRtl_DDETerminateAll(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    int nArgs = static_cast<int>(rPar.Count());
    if ( nArgs != 1 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
    SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
    ErrCode nDdeErr = pDDE->TerminateAll();
    if( nDdeErr )
    {
        StarBASIC::Error( nDdeErr );
    }
}
 
void SbRtl_DDERequest(StarBASIC *, SbxArray & rPar, bool)
{
    int nArgs = static_cast<int>(rPar.Count());
    if ( nArgs != 3 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    size_t nChannel = rPar.Get(1)->GetInteger();
    const OUString aItem = rPar.Get(2)->GetOUString();
    SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
    OUString aResult;
    ErrCode nDdeErr = pDDE->Request( nChannel, aItem, aResult );
    if( nDdeErr )
    {
        StarBASIC::Error( nDdeErr );
    }
    else
    {
        rPar.Get(0)->PutString(aResult);
    }
}
 
void SbRtl_DDEExecute(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    int nArgs = static_cast<int>(rPar.Count());
    if ( nArgs != 3 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    size_t nChannel = rPar.Get(1)->GetInteger();
    const OUString aCommand = rPar.Get(2)->GetOUString();
    SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
    ErrCode nDdeErr = pDDE->Execute( nChannel, aCommand );
    if( nDdeErr )
    {
        StarBASIC::Error( nDdeErr );
    }
}
 
void SbRtl_DDEPoke(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    int nArgs = static_cast<int>(rPar.Count());
    if ( nArgs != 4 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    size_t nChannel = rPar.Get(1)->GetInteger();
    const OUString aItem = rPar.Get(2)->GetOUString();
    const OUString aData = rPar.Get(3)->GetOUString();
    SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
    ErrCode nDdeErr = pDDE->Poke( nChannel, aItem, aData );
    if( nDdeErr )
    {
        StarBASIC::Error( nDdeErr );
    }
}
 
 
void SbRtl_FreeFile(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 1)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
    short nChannel = 1;
    while( nChannel < CHANNELS )
    {
        SbiStream* pStrm = pIO->GetStream( nChannel );
        if( !pStrm )
        {
            rPar.Get(0)->PutInteger(nChannel);
            return;
        }
        nChannel++;
    }
    StarBASIC::Error( ERRCODE_BASIC_TOO_MANY_FILES );
}
 
void SbRtl_LBound(StarBASIC *, SbxArray & rPar, bool)
{
    const sal_uInt32 nParCount = rPar.Count();
    if ( nParCount != 3 && nParCount != 2 )
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    SbxBase* pParObj = rPar.Get(1)->GetObject();
    SbxDimArray* pArr = dynamic_cast<SbxDimArray*>( pParObj );
    if( !pArr )
        return StarBASIC::Error( ERRCODE_BASIC_MUST_HAVE_DIMS );
 
    sal_Int32 nLower, nUpper;
    short nDim = (nParCount == 3) ? static_cast<short>(rPar.Get(2)->GetInteger()) : 1;
    if (!pArr->GetDim(nDim, nLower, nUpper))
        return StarBASIC::Error( ERRCODE_BASIC_OUT_OF_RANGE );
    rPar.Get(0)->PutLong(nLower);
}
 
void SbRtl_UBound(StarBASIC *, SbxArray & rPar, bool)
{
    const sal_uInt32 nParCount = rPar.Count();
    if ( nParCount != 3 && nParCount != 2 )
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    SbxBase* pParObj = rPar.Get(1)->GetObject();
    SbxDimArray* pArr = dynamic_cast<SbxDimArray*>( pParObj );
    if( !pArr )
        return StarBASIC::Error( ERRCODE_BASIC_MUST_HAVE_DIMS );
 
    sal_Int32 nLower, nUpper;
    short nDim = (nParCount == 3) ? static_cast<short>(rPar.Get(2)->GetInteger()) : 1;
    if (!pArr->GetDim(nDim, nLower, nUpper))
        return StarBASIC::Error( ERRCODE_BASIC_OUT_OF_RANGE );
    rPar.Get(0)->PutLong(nUpper);
}
 
void SbRtl_RGB(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 4)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
 
    sal_Int32 nRed = rPar.Get(1)->GetInteger() & 0xFF;
    sal_Int32 nGreen = rPar.Get(2)->GetInteger() & 0xFF;
    sal_Int32 nBlue = rPar.Get(3)->GetInteger() & 0xFF;
    sal_Int32 nRGB;
 
    SbiInstance* pInst = GetSbData()->pInst;
    bool bCompatibility = ( pInst && pInst->IsCompatibility() );
    // See discussion in tdf#145725, here's the quotation from a link indicated in the bugtracker
    // which explains why we need to manage RGB differently according to VB compatibility
    // "In other words, the individual color components are stored in the opposite order one would expect.
    //  VB stores the red color component in the low-order byte of the long integer's low-order word,
    //  the green color in the high-order byte of the low-order word, and the blue color in the low-order byte of the high-order word"
    if( bCompatibility )
    {
        nRGB   = (nBlue << 16) | (nGreen << 8) | nRed;
    }
    else
    {
        nRGB   = (nRed << 16) | (nGreen << 8) | nBlue;
    }
    rPar.Get(0)->PutLong(nRGB);
}
 
void SbRtl_QBColor(StarBASIC *, SbxArray & rPar, bool)
{
    static const sal_Int32 pRGB[] =
    {
        0x000000,
        0x800000,
        0x008000,
        0x808000,
        0x000080,
        0x800080,
        0x008080,
        0xC0C0C0,
        0x808080,
        0xFF0000,
        0x00FF00,
        0xFFFF00,
        0x0000FF,
        0xFF00FF,
        0x00FFFF,
        0xFFFFFF,
    };
 
    if (rPar.Count() != 2)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
    sal_Int16 nCol = rPar.Get(1)->GetInteger();
    if( nCol < 0 || nCol > 15 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    sal_Int32 nRGB = pRGB[ nCol ];
    rPar.Get(0)->PutLong(nRGB);
}
 
static std::vector<sal_uInt8> byteArray2Vec(SbxArray* pArr)
{
    std::vector<sal_uInt8> result;
    if (pArr)
    {
        const sal_uInt32 nCount = pArr->Count();
        result.reserve(nCount + 1); // to avoid reallocation when padding in vbFromUnicode
        for (sal_uInt32 i = 0; i < nCount; i++)
            result.push_back(pArr->Get(i)->GetByte());
    }
    return result;
}
 
// Makes sure to get the byte array if passed, or the string converted to the bytes using
// StringToByteArray in basic/source/sbx/sbxstr.cxx
static std::vector<sal_uInt8> getByteArray(SbxValue& val)
{
    if (val.GetFullType() == SbxOBJECT)
        if (auto pObj = val.GetObject())
            if (pObj->GetType() == (SbxARRAY | SbxBYTE))
                if (auto pArr = dynamic_cast<SbxArray*>(pObj))
                    return byteArray2Vec(pArr);
 
    // Convert to string
    tools::SvRef<SbxValue> pStringValue(new SbxValue(SbxSTRING));
    *pStringValue = val;
 
    // Convert string to byte array
    tools::SvRef<SbxValue> pValue(new SbxValue(SbxOBJECT));
    pValue->PutObject(new SbxArray(SbxBYTE));
    *pValue = *pStringValue; // Does the magic of conversion of strings to byte arrays
    return byteArray2Vec(dynamic_cast<SbxArray*>(pValue->GetObject()));
}
 
// StrConv(string, conversion, LCID)
void SbRtl_StrConv(StarBASIC *, SbxArray & rPar, bool)
{
    const sal_uInt32 nArgCount = rPar.Count() - 1;
    if( nArgCount < 2 || nArgCount > 3 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
    sal_Int32 nConversion = rPar.Get(2)->GetLong();
    LanguageType nLanguage = LANGUAGE_SYSTEM;
    if (nArgCount == 3)
    {
        sal_Int32 lcid = rPar.Get(3)->GetLong();
        nLanguage = LanguageType(lcid);
    }
 
    if (nConversion == ooo::vba::VbStrConv::vbUnicode) // This mode does not combine
    {
        // Assume that the passed byte array is encoded in the defined encoding, convert to
        // UTF-16 and store as string. Passed strings are converted to byte array first.
        auto inArray = getByteArray(*rPar.Get(1));
        std::string_view s(reinterpret_cast<char*>(inArray.data()), inArray.size() / sizeof(char));
        const auto encoding = utl_getWinTextEncodingFromLangStr(LanguageTag(nLanguage).getBcp47());
        OUString aOUStr = OStringToOUString(s, encoding);
        rPar.Get(0)->PutString(aOUStr);
        return;
    }
 
    if (nConversion == ooo::vba::VbStrConv::vbFromUnicode) // This mode does not combine
    {
        // Assume that the passed byte array is UTF-16-encoded (system-endian), convert to specified
        // encoding and store as byte array. Passed strings are converted to byte array first.
        auto inArray = getByteArray(*rPar.Get(1));
        while (inArray.size() % sizeof(sal_Unicode))
            inArray.push_back('\0');
        std::u16string_view s(reinterpret_cast<sal_Unicode*>(inArray.data()),
                              inArray.size() / sizeof(sal_Unicode));
        const auto encoding = utl_getWinTextEncodingFromLangStr(LanguageTag(nLanguage).getBcp47());
        OString aOStr = OUStringToOString(s, encoding);
        const sal_Int32 lb = IsBaseIndexOne() ? 1 : 0;
        const sal_Int32 ub = lb + aOStr.getLength() - 1;
        SbxDimArray* pArray = new SbxDimArray(SbxBYTE);
        pArray->unoAddDim(lb, ub);
 
        for (sal_Int32 i = 0; i < aOStr.getLength(); ++i)
        {
            SbxVariable* pNew = new SbxVariable(SbxBYTE);
            pNew->PutByte(aOStr[i]);
            pArray->Put(pNew, i);
        }
 
        SbxVariable* retVar = rPar.Get(0);
        SbxFlagBits nFlags = retVar->GetFlags();
        retVar->ResetFlag(SbxFlagBits::Fixed);
        retVar->PutObject(pArray);
        retVar->SetFlags(nFlags);
        retVar->SetParameters(nullptr);
        return;
    }
 
    std::vector<TransliterationFlags> aTranslitSet;
    auto check = [&nConversion, &aTranslitSet](sal_Int32 conv, TransliterationFlags flag)
    {
        if ((nConversion & conv) != conv)
            return false;
 
        aTranslitSet.push_back(flag);
        nConversion &= ~conv;
        return true;
    };
 
    // Check mutually exclusive bits together
 
    if (!check(ooo::vba::VbStrConv::vbProperCase, TransliterationFlags::TITLE_CASE))
        if (!check(ooo::vba::VbStrConv::vbUpperCase, TransliterationFlags::LOWERCASE_UPPERCASE))
            check(ooo::vba::VbStrConv::vbLowerCase, TransliterationFlags::UPPERCASE_LOWERCASE);
 
    if (!check(ooo::vba::VbStrConv::vbWide, TransliterationFlags::HALFWIDTH_FULLWIDTH))
        check(ooo::vba::VbStrConv::vbNarrow, TransliterationFlags::FULLWIDTH_HALFWIDTH);
 
    if (!check(ooo::vba::VbStrConv::vbKatakana, TransliterationFlags::HIRAGANA_KATAKANA))
        check(ooo::vba::VbStrConv::vbHiragana, TransliterationFlags::KATAKANA_HIRAGANA);
 
    if (nConversion) // unknown / incorrectly combined bits
        return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
 
    OUString aStr = rPar.Get(1)->GetOUString();
    if (!aStr.isEmpty() && !aTranslitSet.empty())
    {
        const uno::Reference< uno::XComponentContext >& xContext = getProcessComponentContext();
 
        for (auto transliterationFlag : aTranslitSet)
        {
            if (transliterationFlag == TransliterationFlags::TITLE_CASE)
            {
                // TransliterationWrapper only handles the first character of the passed string
                // when handling TITLE_CASE; see Transliteration_titlecase::transliterateImpl in
                // i18npool/source/transliteration/transliteration_body.cxx
                CharClass aCharClass{ xContext, LanguageTag(nLanguage) };
                aStr = aCharClass.titlecase(aCharClass.lowercase(aStr));
            }
            else
            {
                utl::TransliterationWrapper aWrapper(xContext, transliterationFlag);
                aStr = aWrapper.transliterate(aStr, nLanguage, 0, aStr.getLength(), nullptr);
            }
        }
    }
 
    rPar.Get(0)->PutString(aStr);
}
 
 
void SbRtl_Beep(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 1)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    Sound::Beep();
}
 
void SbRtl_Load(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
 
    SbxBase* pObj = rPar.Get(1)->GetObject();
    if ( !pObj )
        return;
 
    if (SbUserFormModule* pModule = dynamic_cast<SbUserFormModule*>(pObj))
    {
        pModule->Load();
    }
    else if (SbxObject* pSbxObj = dynamic_cast<SbxObject*>(pObj))
    {
        SbxVariable* pVar = pSbxObj->Find(u"Load"_ustr, SbxClassType::Method);
        if( pVar )
        {
            pVar->GetInteger();
        }
    }
}
 
void SbRtl_Unload(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    if (rPar.Count() != 2)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
 
    SbxBase* pObj = rPar.Get(1)->GetObject();
    if ( !pObj )
        return;
 
    if (SbUserFormModule* pFormModule = dynamic_cast<SbUserFormModule*>(pObj))
    {
        pFormModule->Unload();
    }
    else if (SbxObject *pSbxObj = dynamic_cast<SbxObject*>(pObj))
    {
        SbxVariable* pVar = pSbxObj->Find(u"Unload"_ustr, SbxClassType::Method);
        if( pVar )
        {
            pVar->GetInteger();
        }
    }
}
 
void SbRtl_LoadPicture(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
    OUString aFileURL = getFullPath(rPar.Get(1)->GetOUString());
    std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream( aFileURL, StreamMode::READ ));
    if( pStream )
    {
        Bitmap aBmp;
        ReadDIB(aBmp, *pStream, true);
        BitmapEx aBitmapEx(aBmp);
        Graphic aGraphic(aBitmapEx);
 
        SbxObjectRef xRef = new SbStdPicture;
        static_cast<SbStdPicture*>(xRef.get())->SetGraphic( aGraphic );
        rPar.Get(0)->PutObject(xRef.get());
    }
}
 
void SbRtl_SavePicture(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    if (rPar.Count() != 3)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
    SbxBase* pObj = rPar.Get(1)->GetObject();
    if (SbStdPicture *pPicture = dynamic_cast<SbStdPicture*>(pObj))
    {
        SvFileStream aOStream(rPar.Get(2)->GetOUString(), StreamMode::WRITE | StreamMode::TRUNC);
        const Graphic& aGraphic = pPicture->GetGraphic();
        TypeSerializer aSerializer(aOStream);
        aSerializer.writeGraphic(aGraphic);
    }
}
 
void SbRtl_MsgBox(StarBASIC *, SbxArray & rPar, bool)
{
    const sal_uInt32 nArgCount = rPar.Count();
    if( nArgCount < 2 || nArgCount > 6 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
    // tdf#147529 - check for missing parameters
    if (IsMissing(rPar, 1))
    {
        return StarBASIC::Error(ERRCODE_BASIC_NOT_OPTIONAL);
    }
 
    // tdf#151012 - initialize optional parameters with their default values (number of buttons)
    sal_Int16 nType = GetOptionalIntegerParamOrDefault(rPar, 2, SbMB::OK);
 
    OUString aMsg = rPar.Get(1)->GetOUString();
    // tdf#151012 - initialize optional parameters with their default values (title of dialog box)
    OUString aTitle = GetOptionalOUStringParamOrDefault(rPar, 3, Application::GetDisplayName());
 
    sal_Int16 nDialogType = nType & (SbMB::ICONSTOP | SbMB::ICONQUESTION | SbMB::ICONINFORMATION);
 
    SolarMutexGuard aSolarGuard;
    weld::Widget* pParent = Application::GetDefDialogParent();
 
    VclMessageType eType = VclMessageType::Other;
 
    switch (nDialogType)
    {
        case SbMB::ICONSTOP:
            eType = VclMessageType::Error;
            break;
        case SbMB::ICONQUESTION:
            eType = VclMessageType::Question;
            break;
        case SbMB::ICONEXCLAMATION:
            eType = VclMessageType::Warning;
            break;
        case SbMB::ICONINFORMATION:
            eType = VclMessageType::Info;
            break;
    }
 
    std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent,
                eType, VclButtonsType::NONE, aMsg, GetpApp()));
 
    std::vector<std::pair<StandardButtonType, sal_Int16>> buttons;
    switch (nType & 0x0F) // delete bits 4-16
    {
        case SbMB::OK:
        default:
            buttons.emplace_back(StandardButtonType::OK, SbMB::Response::OK);
            break;
        case SbMB::OKCANCEL:
            buttons.emplace_back(StandardButtonType::OK, SbMB::Response::OK);
            buttons.emplace_back(StandardButtonType::Cancel, SbMB::Response::CANCEL);
            break;
        case SbMB::ABORTRETRYIGNORE:
            buttons.emplace_back(StandardButtonType::Abort, SbMB::Response::ABORT);
            buttons.emplace_back(StandardButtonType::Retry, SbMB::Response::RETRY);
            buttons.emplace_back(StandardButtonType::Ignore, SbMB::Response::IGNORE);
            break;
        case SbMB::YESNOCANCEL:
            buttons.emplace_back(StandardButtonType::Yes, SbMB::Response::YES);
            buttons.emplace_back(StandardButtonType::No, SbMB::Response::NO);
            buttons.emplace_back(StandardButtonType::Cancel, SbMB::Response::CANCEL);
            break;
        case SbMB::YESNO:
            buttons.emplace_back(StandardButtonType::Yes, SbMB::Response::YES);
            buttons.emplace_back(StandardButtonType::No, SbMB::Response::NO);
            break;
        case SbMB::RETRYCANCEL:
            buttons.emplace_back(StandardButtonType::Retry, SbMB::Response::RETRY);
            buttons.emplace_back(StandardButtonType::Cancel, SbMB::Response::CANCEL);
            break;
    }
 
    for (auto [buttonType, buttonResponse] : buttons)
        xBox->add_button(GetStandardText(buttonType), buttonResponse);
 
    std::size_t default_button = 0;
    if (nType & SbMB::DEFBUTTON2)
        default_button = 1;
    else if (nType & SbMB::DEFBUTTON3)
        default_button = 2;
    xBox->set_default_response(buttons[std::min(default_button, buttons.size() - 1)].second);
 
    xBox->set_title(aTitle);
    sal_Int16 nRet = xBox->run();
    rPar.Get(0)->PutInteger(nRet);
}
 
void SbRtl_SetAttr(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    if (rPar.Count() == 3)
    {
        OUString aStr = rPar.Get(1)->GetOUString();
        sal_Int16 nFlags = rPar.Get(2)->GetInteger();
 
        if( hasUno() )
        {
            const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
            if( xSFI.is() )
            {
                try
                {
                    bool bReadOnly = bool(nFlags & SbAttributes::READONLY);
                    xSFI->setReadOnly( aStr, bReadOnly );
                    bool bHidden = bool(nFlags & SbAttributes::HIDDEN);
                    xSFI->setHidden( aStr, bHidden );
                }
                catch(const Exception & )
                {
                    StarBASIC::Error( ERRCODE_IO_GENERAL );
                }
            }
        }
    }
    else
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
}
 
void SbRtl_Reset(StarBASIC *, SbxArray &, bool)
{
    SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
    if (pIO)
    {
        pIO->CloseAll();
    }
}
 
void SbRtl_DumpAllObjects(StarBASIC * pBasic, SbxArray & rPar, bool)
{
    const sal_uInt32 nArgCount = rPar.Count();
    if( nArgCount < 2 || nArgCount > 3 )
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else if( !pBasic )
    {
        StarBASIC::Error( ERRCODE_BASIC_INTERNAL_ERROR );
    }
    else
    {
        SbxObject* p = pBasic;
        while( p->GetParent() )
        {
            p = p->GetParent();
        }
        SvFileStream aStrm(rPar.Get(1)->GetOUString(),
                            StreamMode::WRITE | StreamMode::TRUNC );
        p->Dump(aStrm, rPar.Get(2)->GetBool());
        aStrm.Close();
        if( aStrm.GetError() != ERRCODE_NONE )
        {
            StarBASIC::Error( ERRCODE_BASIC_IO_ERROR );
        }
    }
}
 
 
void SbRtl_FileExists(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() == 2)
    {
        OUString aStr = rPar.Get(1)->GetOUString();
        bool bExists = false;
 
        if( hasUno() )
        {
            const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
            if( xSFI.is() )
            {
                try
                {
                    bExists = xSFI->exists( aStr );
                }
                catch(const Exception & )
                {
                    StarBASIC::Error( ERRCODE_IO_GENERAL );
                }
            }
        }
        else
        {
            DirectoryItem aItem;
            FileBase::RC nRet = DirectoryItem::get( getFullPath( aStr ), aItem );
            bExists = (nRet == FileBase::E_None);
        }
        rPar.Get(0)->PutBool(bExists);
    }
    else
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
}
 
void SbRtl_Partition(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 5)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
    sal_Int32 nNumber = rPar.Get(1)->GetLong();
    sal_Int32 nStart = rPar.Get(2)->GetLong();
    sal_Int32 nStop = rPar.Get(3)->GetLong();
    sal_Int32 nInterval = rPar.Get(4)->GetLong();
 
    if( nStart < 0 || nStop <= nStart || nInterval < 1 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
 
    // the Partition function inserts leading spaces before lowervalue and uppervalue
    // so that they both have the same number of characters as the string
    // representation of the value (Stop + 1). This ensures that if you use the output
    // of the Partition function with several values of Number, the resulting text
    // will be handled properly during any subsequent sort operation.
 
    // calculate the maximum number of characters before lowervalue and uppervalue
    OUString aBeforeStart = OUString::number( nStart - 1 );
    OUString aAfterStop = OUString::number( nStop + 1 );
    sal_Int32 nLen1 = aBeforeStart.getLength();
    sal_Int32 nLen2 = aAfterStop.getLength();
    sal_Int32 nLen = nLen1 >= nLen2 ? nLen1:nLen2;
 
    OUStringBuffer aRetStr( nLen * 2 + 1);
    OUString aLowerValue;
    OUString aUpperValue;
    if( nNumber < nStart )
    {
        aUpperValue = aBeforeStart;
    }
    else if( nNumber > nStop )
    {
        aLowerValue = aAfterStop;
    }
    else
    {
        sal_Int32 nLowerValue = nNumber;
        sal_Int32 nUpperValue = nLowerValue;
        if( nInterval > 1 )
        {
            nLowerValue = ((( nNumber - nStart ) / nInterval ) * nInterval ) + nStart;
            nUpperValue = nLowerValue + nInterval - 1;
        }
        aLowerValue = OUString::number( nLowerValue );
        aUpperValue = OUString::number( nUpperValue );
    }
 
    nLen1 = aLowerValue.getLength();
    nLen2 = aUpperValue.getLength();
 
    if( nLen > nLen1 )
    {
        // appending the leading spaces for the lowervalue
        for ( sal_Int32 i= nLen - nLen1; i > 0; --i )
        {
            aRetStr.append(" ");
        }
    }
    aRetStr.append( aLowerValue + ":");
    if( nLen > nLen2 )
    {
        // appending the leading spaces for the uppervalue
        for ( sal_Int32 i= nLen - nLen2; i > 0; --i )
        {
            aRetStr.append(" ");
        }
    }
    aRetStr.append( aUpperValue );
    rPar.Get(0)->PutString(aRetStr.makeStringAndClear());
}
 
#endif
 
sal_Int16 implGetDateYear( double aDate )
{
    Date aRefDate(1899'12'30);
    sal_Int32 nDays = static_cast<sal_Int32>(aDate);
    aRefDate.AddDays( nDays );
    sal_Int16 nRet = aRefDate.GetYear();
    return nRet;
}
 
bool implDateSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay,
        bool bUseTwoDigitYear, SbDateCorrection eCorr, double& rdRet )
{
    // XXX NOTE: For VBA years<0 are invalid and years in the range 0..29 and
    // 30..99 can not be input as they are 2-digit for 2000..2029 and
    // 1930..1999, VBA mode overrides bUseTwoDigitYear (as if that was always
    // true). For VBA years > 9999 are invalid.
    // For StarBASIC, if bUseTwoDigitYear==true then years in the range 0..99
    // can not be input as they are 2-digit for 1900..1999, years<0 are
    // accepted. If bUseTwoDigitYear==false then all years are accepted, but
    // year 0 is invalid (last day BCE -0001-12-31, first day CE 0001-01-01).
#if HAVE_FEATURE_SCRIPTING
    if ( (nYear < 0 || 9999 < nYear) && SbiRuntime::isVBAEnabled() )
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
        return false;
    }
    else if ( nYear < 30 && SbiRuntime::isVBAEnabled() )
    {
        nYear += 2000;
    }
    else
#endif
    {
        if ( 0 <= nYear && nYear < 100 &&
#if HAVE_FEATURE_SCRIPTING
                                          (bUseTwoDigitYear || SbiRuntime::isVBAEnabled())
#else
                                          bUseTwoDigitYear
#endif
           )
        {
            nYear += 1900;
        }
    }
 
    sal_Int32 nAddMonths = 0;
    sal_Int32 nAddDays = 0;
    // Always sanitize values to set date and to use for validity detection.
    if (nMonth < 1 || 12 < nMonth)
    {
        sal_Int16 nM = ((nMonth < 1) ? (12 + (nMonth % 12)) : (nMonth % 12));
        nAddMonths = nMonth - nM;
        nMonth = nM;
    }
    // Day 0 would already be normalized during Date::Normalize(), include
    // it in negative days, also to detect non-validity. The actual day of
    // month is 1+(nDay-1)
    if (nDay < 1)
    {
        nAddDays = nDay - 1;
        nDay = 1;
    }
    else if (nDay > 31)
    {
        nAddDays = nDay - 31;
        nDay = 31;
    }
 
    Date aCurDate( nDay, nMonth, nYear );
 
    /* TODO: we could enable the same rollover mechanism for StarBASIC to be
     * compatible with VBA (just with our wider supported date range), then
     * documentation would need to be adapted. As is, the DateSerial() runtime
     * function works as dumb as documented... (except that the resulting date
     * is checked for validity now and not just day<=31 and month<=12).
     * If change wanted then simply remove overriding RollOver here and adapt
     * documentation.*/
#if HAVE_FEATURE_SCRIPTING
    if (eCorr == SbDateCorrection::RollOver && !SbiRuntime::isVBAEnabled())
        eCorr = SbDateCorrection::None;
#endif
 
    if (nYear == 0 || (eCorr == SbDateCorrection::None && (nAddMonths || nAddDays || !aCurDate.IsValidDate())))
    {
#if HAVE_FEATURE_SCRIPTING
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
#endif
        return false;
    }
 
    if (eCorr != SbDateCorrection::None)
    {
        aCurDate.Normalize();
        if (nAddMonths)
            aCurDate.AddMonths( nAddMonths);
        if (nAddDays)
            aCurDate.AddDays( nAddDays);
        if (eCorr == SbDateCorrection::TruncateToMonth && aCurDate.GetMonth() != nMonth)
        {
            if (aCurDate.GetYear() == SAL_MAX_INT16 && nMonth == 12)
            {
                // Roll over and back not possible, hard max.
                aCurDate.SetMonth(12);
                aCurDate.SetDay(31);
            }
            else
            {
                aCurDate.SetMonth(nMonth);
                aCurDate.SetDay(1);
                aCurDate.AddMonths(1);
                aCurDate.AddDays(-1);
            }
        }
    }
 
    rdRet = GetDayDiff(aCurDate);
    return true;
}
 
double implTimeSerial(sal_Int16 nHours, sal_Int16 nMinutes, sal_Int16 nSeconds,
                      sal_Int32 nMilliSeconds)
{
    return (nHours * ::tools::Time::milliSecPerHour + nMinutes * ::tools::Time::milliSecPerMinute
            + nSeconds * ::tools::Time::milliSecPerSec + nMilliSeconds)
           / static_cast<double>(::tools::Time::milliSecPerDay);
}
 
bool implDateTimeSerial(sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay, sal_Int16 nHour,
                        sal_Int16 nMinute, sal_Int16 nSecond, sal_Int32 nMilliSecond, double& rdRet)
{
    double dDate;
    if(!implDateSerial(nYear, nMonth, nDay, false/*bUseTwoDigitYear*/, SbDateCorrection::None, dDate))
        return false;
    rdRet += dDate + implTimeSerial(nHour, nMinute, nSecond, nMilliSecond);
    return true;
}
 
sal_Int16 implGetMinute( double dDate )
{
    double nFrac = (dDate - floor(dDate)) * ::tools::Time::milliSecPerDay;
    sal_uInt64 nMilliSeconds = static_cast<sal_uInt64>(nFrac + 0.5);
    return static_cast<sal_Int16>((nMilliSeconds / ::tools::Time::milliSecPerMinute)
                                  % ::tools::Time::minutePerHour);
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'remove' is required to be utilized.

V530 The return value of function 'insert' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'padToLength' is required to be utilized.

V530 The return value of function 'padToLength' is required to be utilized.

V530 The return value of function 'padToLength' is required to be utilized.

V530 The return value of function 'padToLength' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'insert' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V1048 The 'bIncludeLeadingDigit' variable was assigned the same value.