    /* -*- 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 <string.h>
    #include <stdio.h>
    #include <string>

    #include <sal/log.hxx>
    #include <unotools/localedatawrapper.hxx>
    #include <unotools/calendarwrapper.hxx>
    #include <unotools/digitgroupingiterator.hxx>
    #include <tools/debug.hxx>
    #include <i18nlangtag/languagetag.hxx>

    #include <com/sun/star/i18n/KNumberFormatUsage.hpp>
    #include <com/sun/star/i18n/KNumberFormatType.hpp>
    #include <com/sun/star/i18n/LocaleData2.hpp>
    #include <com/sun/star/i18n/CalendarFieldIndex.hpp>
    #include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
    #include <com/sun/star/i18n/NumberFormatIndex.hpp>
    #include <com/sun/star/i18n/NumberFormatMapper.hpp>

    #include <comphelper/processfactory.hxx>
    #include <rtl/instance.hxx>
    #include <rtl/ustrbuf.hxx>
    #include <osl/diagnose.h>
    #include <sal/macros.h>
    #include <rtl/math.hxx>

    static const sal_uInt16 nCurrFormatInvalid = 0xffff;
    static const sal_uInt16 nCurrFormatDefault = 0;

    using namespace ::com::sun::star;
    using namespace ::com::sun::star::i18n;
    using namespace ::com::sun::star::uno;

    namespace
    {
        struct InstalledLocales
            : public rtl::Static<
                uno::Sequence< lang::Locale >, InstalledLocales >
        {};

        struct InstalledLanguageTypes
            : public rtl::Static<
                std::vector< LanguageType >, InstalledLanguageTypes >
        {};
    }

    bool LocaleDataWrapper::Locale_Compare::operator()(const css::lang::Locale& rLocale1, const css::lang::Locale& rLocale2) const
    {
        if (rLocale1.Language < rLocale2.Language)
            return true;
        else if (rLocale1.Language > rLocale2.Language)
            return false;

        if (rLocale1.Country < rLocale2.Country)
            return true;
        else if (rLocale1.Country > rLocale2.Country)
            return false;

        return rLocale1.Variant < rLocale2.Variant;
    }

    sal_uInt8 LocaleDataWrapper::nLocaleDataChecking = 0;

    LocaleDataWrapper::LocaleDataWrapper(
                const Reference< uno::XComponentContext > & rxContext,
                const LanguageTag& rLanguageTag
                )
            :
            m_xContext( rxContext ),
            xLD( LocaleData2::create(rxContext) ),
            maLanguageTag( rLanguageTag ),
            bLocaleDataItemValid( false ),
            bReservedWordValid( false ),
            bSecondaryCalendarValid( false )
    {
        invalidateData();
    }

    LocaleDataWrapper::LocaleDataWrapper(
                const LanguageTag& rLanguageTag
                )
            :
            m_xContext( comphelper::getProcessComponentContext() ),
            xLD( LocaleData2::create(m_xContext) ),
            maLanguageTag( rLanguageTag ),
            bLocaleDataItemValid( false ),
            bReservedWordValid( false ),
            bSecondaryCalendarValid( false )
    {
        invalidateData();
    }

    LocaleDataWrapper::~LocaleDataWrapper()
    {
    }

    void LocaleDataWrapper::setLanguageTag( const LanguageTag& rLanguageTag )
    {
        ::utl::ReadWriteGuard aGuard( aMutex, ReadWriteGuardMode::CriticalChange );
        maLanguageTag = rLanguageTag;
        invalidateData();
    }

    const LanguageTag& LocaleDataWrapper::getLanguageTag() const
    {
        ::utl::ReadWriteGuard aGuard( aMutex );
        return maLanguageTag;
    }

    const css::lang::Locale& LocaleDataWrapper::getMyLocale() const
    {
        ::utl::ReadWriteGuard aGuard( aMutex );
        return maLanguageTag.getLocale();
    }

    void LocaleDataWrapper::invalidateData()
    {
        aCurrSymbol.clear();
        aCurrBankSymbol.clear();
        nDateOrder = nLongDateOrder = DateOrder::Invalid;
        nCurrPositiveFormat = nCurrNegativeFormat = nCurrDigits = nCurrFormatInvalid;
        if ( bLocaleDataItemValid )
        {
            for (OUString & j : aLocaleItem)
                j.clear();
            bLocaleDataItemValid = false;
        }
        if ( bReservedWordValid )
        {
            for (OUString & j : aReservedWord)
                j.clear();
            bReservedWordValid = false;
        }
        xDefaultCalendar.reset();
        xSecondaryCalendar.reset();
        bSecondaryCalendarValid = false;
        if (aGrouping.getLength())
            aGrouping[0] = 0;
        if (aDateAcceptancePatterns.getLength())
            aDateAcceptancePatterns = Sequence<OUString>();
    }

    /* FIXME-BCP47: locale data should provide a language tag instead that could be
     * passed on. */
    css::i18n::LanguageCountryInfo LocaleDataWrapper::getLanguageCountryInfo() const
    {
        try
        {
            return xLD->getLanguageCountryInfo( getMyLocale() );
        }
        catch (const Exception& e)
        {
            SAL_WARN( "unotools.i18n", "getLanguageCountryInfo: Exception caught " << e );
        }
        return css::i18n::LanguageCountryInfo();
    }

    const css::i18n::LocaleDataItem2& LocaleDataWrapper::getLocaleItem() const
    {
        {
            ::utl::ReadWriteGuard aGuard( aMutex );
            const css::lang::Locale& rLocal = getMyLocale();
            auto itr = maDataItemCache.find(rLocal);
            if (itr != maDataItemCache.end())
                return itr->second;
        }

        try
        {
            ::utl::ReadWriteGuard aGuard( aMutex );

            const css::lang::Locale& rLocal = getMyLocale();
            css::i18n::LocaleDataItem2 aItem = xLD->getLocaleItem2( rLocal );
            auto aRet = maDataItemCache.insert(std::make_pair(rLocal, aItem));
            assert(aRet.second);
            return aRet.first->second;
        }
        catch (const Exception& e)
        {
            SAL_WARN( "unotools.i18n", "getLocaleItem: Exception caught " << e );
        }
        static css::i18n::LocaleDataItem2 aEmptyItem;
        return aEmptyItem;
    }

    css::uno::Sequence< css::i18n::Currency2 > LocaleDataWrapper::getAllCurrencies() const
    {
        try
        {
            return xLD->getAllCurrencies2( getMyLocale() );
        }
        catch (const Exception& e)
        {
            SAL_WARN( "unotools.i18n", "getAllCurrencies: Exception caught " << e );
        }
        return css::uno::Sequence< css::i18n::Currency2 >(0);
    }

    css::uno::Sequence< css::i18n::FormatElement > LocaleDataWrapper::getAllFormats() const
    {
        try
        {
            return xLD->getAllFormats( getMyLocale() );
        }
        catch (const Exception& e)
        {
            SAL_WARN( "unotools.i18n", "getAllFormats: Exception caught " << e );
        }
        return css::uno::Sequence< css::i18n::FormatElement >(0);
    }

    css::i18n::ForbiddenCharacters LocaleDataWrapper::getForbiddenCharacters() const
    {
        try
        {
            return xLD->getForbiddenCharacters( getMyLocale() );
        }
        catch (const Exception& e)
        {
            SAL_WARN( "unotools.i18n", "getForbiddenCharacters: Exception caught " << e );
        }
        return css::i18n::ForbiddenCharacters();
    }

    css::uno::Sequence< OUString > LocaleDataWrapper::getReservedWord() const
    {
        try
        {
            return xLD->getReservedWord( getMyLocale() );
        }
        catch ( const Exception& e )
        {
            SAL_WARN( "unotools.i18n", "getReservedWord: Exception caught " << e );
        }
        return css::uno::Sequence< OUString >(0);
    }

    css::uno::Sequence< css::lang::Locale > LocaleDataWrapper::getAllInstalledLocaleNames() const
    {
        uno::Sequence< lang::Locale > &rInstalledLocales = InstalledLocales::get();

        if ( rInstalledLocales.getLength() )
            return rInstalledLocales;

        try
        {
            rInstalledLocales = xLD->getAllInstalledLocaleNames();
        }
        catch ( const Exception& e )
        {
            SAL_WARN( "unotools.i18n", "getAllInstalledLocaleNames: Exception caught " << e );
        }
        return rInstalledLocales;
    }

    // --- Impl and helpers ----------------------------------------------------

    // static
    css::uno::Sequence< css::lang::Locale > LocaleDataWrapper::getInstalledLocaleNames()
    {
        const uno::Sequence< lang::Locale > &rInstalledLocales =
            InstalledLocales::get();

        if ( !rInstalledLocales.getLength() )
        {
            LocaleDataWrapper aLDW( ::comphelper::getProcessComponentContext(), LanguageTag( LANGUAGE_SYSTEM) );
            aLDW.getAllInstalledLocaleNames();
        }
        return rInstalledLocales;
    }

    // static
    std::vector< LanguageType > LocaleDataWrapper::getInstalledLanguageTypes()
    {
        std::vector< LanguageType > &rInstalledLanguageTypes =
            InstalledLanguageTypes::get();

        if ( !rInstalledLanguageTypes.empty() )
            return rInstalledLanguageTypes;

        css::uno::Sequence< css::lang::Locale > xLoc =  getInstalledLocaleNames();
        sal_Int32 nCount = xLoc.getLength();
        std::vector< LanguageType > xLang;
        xLang.reserve(nCount);
        for ( sal_Int32 i=0; i<nCount; i++ )
        {
            LanguageTag aLanguageTag( xLoc[i] );
            OUString aDebugLocale;
            if (areChecksEnabled())
            {
                aDebugLocale = aLanguageTag.getBcp47( false);
            }

            LanguageType eLang = aLanguageTag.getLanguageType( false);
            if (areChecksEnabled() && eLang == LANGUAGE_DONTKNOW)
            {
                OUStringBuffer aMsg("ConvertIsoNamesToLanguage: unknown MS-LCID for locale\n");
                aMsg.append(aDebugLocale);
                outputCheckMessage(aMsg.makeStringAndClear());
            }

            if ( eLang == LANGUAGE_NORWEGIAN)       // no_NO, not Bokmal (nb_NO), not Nynorsk (nn_NO)
                eLang = LANGUAGE_DONTKNOW;  // don't offer "Unknown" language
            if ( eLang != LANGUAGE_DONTKNOW )
            {
                LanguageTag aBackLanguageTag( eLang);
                if ( aLanguageTag != aBackLanguageTag )
                {
                    // In checks, exclude known problems because no MS-LCID defined
                    // and default for Language found.
                    if ( areChecksEnabled()
                            && aDebugLocale != "ar-SD"  // Sudan/ar
                            && aDebugLocale != "en-CB"  // Caribbean is not a country
    //                      && aDebugLocale != "en-BG"  // ?!? Bulgaria/en
    //                      && aDebugLocale != "es-BR"  // ?!? Brazil/es
                        )
                    {
                        OUStringBuffer aMsg("ConvertIsoNamesToLanguage/ConvertLanguageToIsoNames: ambiguous locale (MS-LCID?)\n");
                        aMsg.append(aDebugLocale);
                        aMsg.append("  ->  0x");
                        aMsg.append(static_cast<sal_Int32>(static_cast<sal_uInt16>(eLang)), 16);
                        aMsg.append("  ->  ");
                        aMsg.append(aBackLanguageTag.getBcp47());
                        outputCheckMessage( aMsg.makeStringAndClear() );
                    }
                    eLang = LANGUAGE_DONTKNOW;
                }
            }
            if ( eLang != LANGUAGE_DONTKNOW )
                xLang.push_back(eLang);
        }
        rInstalledLanguageTypes = xLang;

        return rInstalledLanguageTypes;
    }

    const OUString& LocaleDataWrapper::getOneLocaleItem( sal_Int16 nItem ) const
    {
        ::utl::ReadWriteGuard aGuard( aMutex );
        if ( nItem >= LocaleItem::COUNT2 )
        {
            SAL_WARN( "unotools.i18n", "getOneLocaleItem: bounds" );
            return aLocaleItem[0];
        }
        if (aLocaleItem[nItem].isEmpty())
        {   // no cached content
            aGuard.changeReadToWrite();
            const_cast<LocaleDataWrapper*>(this)->getOneLocaleItemImpl( nItem );
        }
        return aLocaleItem[nItem];
    }

    void LocaleDataWrapper::getOneLocaleItemImpl( sal_Int16 nItem )
    {
        if ( !bLocaleDataItemValid )
        {
            aLocaleDataItem = getLocaleItem();
            bLocaleDataItemValid = true;
        }
        switch ( nItem )
        {
            case LocaleItem::DATE_SEPARATOR :
                aLocaleItem[nItem] = aLocaleDataItem.dateSeparator;
            break;
            case LocaleItem::THOUSAND_SEPARATOR :
                aLocaleItem[nItem] = aLocaleDataItem.thousandSeparator;
            break;
            case LocaleItem::DECIMAL_SEPARATOR :
                aLocaleItem[nItem] = aLocaleDataItem.decimalSeparator;
            break;
            case LocaleItem::TIME_SEPARATOR :
                aLocaleItem[nItem] = aLocaleDataItem.timeSeparator;
            break;
            case LocaleItem::TIME_100SEC_SEPARATOR :
                aLocaleItem[nItem] = aLocaleDataItem.time100SecSeparator;
            break;
            case LocaleItem::LIST_SEPARATOR :
                aLocaleItem[nItem] = aLocaleDataItem.listSeparator;
            break;
            case LocaleItem::SINGLE_QUOTATION_START :
                aLocaleItem[nItem] = aLocaleDataItem.quotationStart;
            break;
            case LocaleItem::SINGLE_QUOTATION_END :
                aLocaleItem[nItem] = aLocaleDataItem.quotationEnd;
            break;
            case LocaleItem::DOUBLE_QUOTATION_START :
                aLocaleItem[nItem] = aLocaleDataItem.doubleQuotationStart;
            break;
            case LocaleItem::DOUBLE_QUOTATION_END :
                aLocaleItem[nItem] = aLocaleDataItem.doubleQuotationEnd;
            break;
            case LocaleItem::MEASUREMENT_SYSTEM :
                aLocaleItem[nItem] = aLocaleDataItem.measurementSystem;
            break;
            case LocaleItem::TIME_AM :
                aLocaleItem[nItem] = aLocaleDataItem.timeAM;
            break;
            case LocaleItem::TIME_PM :
                aLocaleItem[nItem] = aLocaleDataItem.timePM;
            break;
            case LocaleItem::LONG_DATE_DAY_OF_WEEK_SEPARATOR :
                aLocaleItem[nItem] = aLocaleDataItem.LongDateDayOfWeekSeparator;
            break;
            case LocaleItem::LONG_DATE_DAY_SEPARATOR :
                aLocaleItem[nItem] = aLocaleDataItem.LongDateDaySeparator;
            break;
            case LocaleItem::LONG_DATE_MONTH_SEPARATOR :
                aLocaleItem[nItem] = aLocaleDataItem.LongDateMonthSeparator;
            break;
            case LocaleItem::LONG_DATE_YEAR_SEPARATOR :
                aLocaleItem[nItem] = aLocaleDataItem.LongDateYearSeparator;
            break;
            case LocaleItem::DECIMAL_SEPARATOR_ALTERNATIVE :
                aLocaleItem[nItem] = aLocaleDataItem.decimalSeparatorAlternative;
            break;
            default:
                SAL_WARN( "unotools.i18n", "getOneLocaleItemImpl: which one?" );
        }
    }

    void LocaleDataWrapper::getOneReservedWordImpl( sal_Int16 nWord )
    {
        if ( !bReservedWordValid )
        {
            aReservedWordSeq = getReservedWord();
            bReservedWordValid = true;
        }
        DBG_ASSERT( nWord < aReservedWordSeq.getLength(), "getOneReservedWordImpl: which one?" );
        if ( nWord < aReservedWordSeq.getLength() )
            aReservedWord[nWord] = aReservedWordSeq[nWord];
    }

    const OUString& LocaleDataWrapper::getOneReservedWord( sal_Int16 nWord ) const
    {
        ::utl::ReadWriteGuard aGuard( aMutex );
        if ( nWord < 0 || nWord >= reservedWords::COUNT )
        {
            SAL_WARN( "unotools.i18n", "getOneReservedWord: bounds" );
            nWord = reservedWords::FALSE_WORD;
        }
        if (aReservedWord[nWord].isEmpty())
        {   // no cached content
            aGuard.changeReadToWrite();
            const_cast<LocaleDataWrapper*>(this)->getOneReservedWordImpl( nWord );
        }
        return aReservedWord[nWord];
    }

    MeasurementSystem LocaleDataWrapper::mapMeasurementStringToEnum( const OUString& rMS ) const
    {
    //! TODO: could be cached too
        if ( rMS.equalsIgnoreAsciiCase( "metric" ) )
            return MeasurementSystem::Metric;
    //! TODO: other measurement systems? => extend enum MeasurementSystem
        return MeasurementSystem::US;
    }

    void LocaleDataWrapper::getSecondaryCalendarImpl()
    {
        if (!xSecondaryCalendar && !bSecondaryCalendarValid)
        {
            Sequence< Calendar2 > xCals = getAllCalendars();
            sal_Int32 nCount = xCals.getLength();
            if (nCount > 1)
            {
                sal_Int32 nNonDef = -1;
                const Calendar2* pArr = xCals.getArray();
                for (sal_Int32 i=0; i<nCount; ++i)
                {
                    if (!pArr[i].Default)
                    {
                        nNonDef = i;
                        break;
                    }
                }
                if (nNonDef >= 0)
                    xSecondaryCalendar.reset( new Calendar2( xCals[nNonDef]));
            }
            bSecondaryCalendarValid = true;
        }
    }

    bool LocaleDataWrapper::doesSecondaryCalendarUseEC( const OUString& rName ) const
    {
        if (rName.isEmpty())
            return false;

        // Check language tag first to avoid loading all calendars of this locale.
        LanguageTag aLoaded( getLoadedLanguageTag());
        OUString aBcp47( aLoaded.getBcp47());
        // So far determine only by locale, we know for a few.
        /* TODO: check date format codes? or add to locale data? */
        if (    aBcp47 != "ja-JP" &&
                aBcp47 != "lo-LA" &&
                aBcp47 != "zh-TW")
            return false;

        ::utl::ReadWriteGuard aGuard( aMutex );

        if (!bSecondaryCalendarValid)
        {   // no cached content
            aGuard.changeReadToWrite();
            const_cast<LocaleDataWrapper*>(this)->getSecondaryCalendarImpl();
        }
        if (!xSecondaryCalendar)
            return false;
        if (!xSecondaryCalendar->Name.equalsIgnoreAsciiCase( rName))
            return false;

        return true;
    }

    void LocaleDataWrapper::getDefaultCalendarImpl()
    {
        if (!xDefaultCalendar)
        {
            Sequence< Calendar2 > xCals = getAllCalendars();
            sal_Int32 nCount = xCals.getLength();
            sal_Int32 nDef = 0;
            if (nCount > 1)
            {
                const Calendar2* pArr = xCals.getArray();
                for (sal_Int32 i=0; i<nCount; ++i)
                {
                    if (pArr[i].Default)
                    {
                        nDef = i;
                        break;
                    }
                }
            }
            xDefaultCalendar.reset( new Calendar2( xCals[nDef]));
        }
    }

    const std::shared_ptr< css::i18n::Calendar2 >& LocaleDataWrapper::getDefaultCalendar() const
    {
        ::utl::ReadWriteGuard aGuard( aMutex );
        if (!xDefaultCalendar)
        {   // no cached content
            aGuard.changeReadToWrite();
            const_cast<LocaleDataWrapper*>(this)->getDefaultCalendarImpl();
        }
        return xDefaultCalendar;
    }

    css::uno::Sequence< css::i18n::CalendarItem2 > const & LocaleDataWrapper::getDefaultCalendarDays() const
    {
        return getDefaultCalendar()->Days;
    }

    css::uno::Sequence< css::i18n::CalendarItem2 > const & LocaleDataWrapper::getDefaultCalendarMonths() const
    {
        return getDefaultCalendar()->Months;
    }

    // --- currencies -----------------------------------------------------

    const OUString& LocaleDataWrapper::getCurrSymbol() const
    {
        ::utl::ReadWriteGuard aGuard( aMutex );
        if (aCurrSymbol.isEmpty())
        {
            aGuard.changeReadToWrite();
            const_cast<LocaleDataWrapper*>(this)->getCurrSymbolsImpl();
        }
        return aCurrSymbol;
    }

    const OUString& LocaleDataWrapper::getCurrBankSymbol() const
    {
        ::utl::ReadWriteGuard aGuard( aMutex );
        if (aCurrBankSymbol.isEmpty())
        {
            aGuard.changeReadToWrite();
            const_cast<LocaleDataWrapper*>(this)->getCurrSymbolsImpl();
        }
        return aCurrBankSymbol;
    }

    sal_uInt16 LocaleDataWrapper::getCurrPositiveFormat() const
    {
        ::utl::ReadWriteGuard aGuard( aMutex );
        if ( nCurrPositiveFormat == nCurrFormatInvalid )
        {
            aGuard.changeReadToWrite();
            const_cast<LocaleDataWrapper*>(this)->getCurrFormatsImpl();
        }
        return nCurrPositiveFormat;
    }

    sal_uInt16 LocaleDataWrapper::getCurrNegativeFormat() const
    {
        ::utl::ReadWriteGuard aGuard( aMutex );
        if ( nCurrNegativeFormat == nCurrFormatInvalid )
        {
            aGuard.changeReadToWrite();
            const_cast<LocaleDataWrapper*>(this)->getCurrFormatsImpl();
        }
        return nCurrNegativeFormat;
    }

    sal_uInt16 LocaleDataWrapper::getCurrDigits() const
    {
        ::utl::ReadWriteGuard aGuard( aMutex );
        if ( nCurrDigits == nCurrFormatInvalid )
        {
            aGuard.changeReadToWrite();
            const_cast<LocaleDataWrapper*>(this)->getCurrSymbolsImpl();
        }
        return nCurrDigits;
    }

    void LocaleDataWrapper::getCurrSymbolsImpl()
    {
        Sequence< Currency2 > aCurrSeq = getAllCurrencies();
        sal_Int32 nCnt = aCurrSeq.getLength();
        Currency2 const * const pCurrArr = aCurrSeq.getArray();
        sal_Int32 nElem;
        for ( nElem = 0; nElem < nCnt; nElem++ )
        {
            if ( pCurrArr[nElem].Default )
                break;
        }
        if ( nElem >= nCnt )
        {
            if (areChecksEnabled())
            {
                outputCheckMessage( appendLocaleInfo( "LocaleDataWrapper::getCurrSymbolsImpl: no default currency" ) );
            }
            nElem = 0;
            if ( nElem >= nCnt )
            {
                if (areChecksEnabled())
                    outputCheckMessage(OUString("LocaleDataWrapper::getCurrSymbolsImpl: no currency at all, using ShellsAndPebbles"));
                aCurrSymbol = "ShellsAndPebbles";
                aCurrBankSymbol = aCurrSymbol;
                nCurrPositiveFormat = nCurrNegativeFormat = nCurrFormatDefault;
                nCurrDigits = 2;
                return;
            }
        }
        aCurrSymbol = pCurrArr[nElem].Symbol;
        aCurrBankSymbol = pCurrArr[nElem].BankSymbol;
        nCurrDigits = pCurrArr[nElem].DecimalPlaces;
    }

    void LocaleDataWrapper::scanCurrFormatImpl( const OUString& rCode,
            sal_Int32 nStart, sal_Int32& nSign, sal_Int32& nPar,
            sal_Int32& nNum, sal_Int32& nBlank, sal_Int32& nSym ) const
    {
        nSign = nPar = nNum = nBlank = nSym = -1;
        const sal_Unicode* const pStr = rCode.getStr();
        const sal_Unicode* const pStop = pStr + rCode.getLength();
        const sal_Unicode* p = pStr + nStart;
        int nInSection = 0;
        bool bQuote = false;
        while ( p < pStop )
        {
            if ( bQuote )
            {
                if ( *p == '"' && *(p-1) != '\\' )
                    bQuote = false;
            }
            else
            {
                switch ( *p )
                {
                    case '"' :
                        if ( pStr == p || *(p-1) != '\\' )
                            bQuote = true;
                    break;
                    case '-' :
                        if (!nInSection && nSign == -1)
                            nSign = p - pStr;
                    break;
                    case '(' :
                        if (!nInSection && nPar == -1)
                            nPar = p - pStr;
                    break;
                    case '0' :
                    case '#' :
                        if (!nInSection && nNum == -1)
                            nNum = p - pStr;
                    break;
                    case '[' :
                        nInSection++;
                    break;
                    case ']' :
                        if ( nInSection )
                        {
                            nInSection--;
                            if (!nInSection && nBlank == -1
                              && nSym != -1 && p < pStop-1 && *(p+1) == ' ' )
                                nBlank = p - pStr + 1;
                        }
                    break;
                    case '$' :
                        if (nSym == -1 && nInSection && *(p-1) == '[')
                        {
                            nSym = p - pStr + 1;
                            if (nNum != -1 && *(p-2) == ' ')
                                nBlank = p - pStr - 2;
                        }
                    break;
                    case ';' :
                        if ( !nInSection )
                            p = pStop;
                    break;
                    default:
                        if (!nInSection && nSym == -1 && rCode.match(aCurrSymbol, static_cast<sal_Int32>(p - pStr)))
                        {   // currency symbol not surrounded by [$...]
                            nSym = p - pStr;
                            if (nBlank == -1 && pStr < p && *(p-1) == ' ')
                                nBlank = p - pStr - 1;
                            p += aCurrSymbol.getLength() - 1;
                            if (nBlank == -1 && p < pStop-2 && *(p+2) == ' ')
                                nBlank = p - pStr + 2;
                        }
                }
            }
            p++;
        }
    }

    void LocaleDataWrapper::getCurrFormatsImpl()
    {
        css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext );
        uno::Sequence< NumberFormatCode > aFormatSeq = xNFC->getAllFormatCode( KNumberFormatUsage::CURRENCY, getMyLocale() );
        sal_Int32 nCnt = aFormatSeq.getLength();
        if ( !nCnt )
        {   // bad luck
            if (areChecksEnabled())
            {
                outputCheckMessage( appendLocaleInfo( "LocaleDataWrapper::getCurrFormatsImpl: no currency formats" ) );
            }
            nCurrPositiveFormat = nCurrNegativeFormat = nCurrFormatDefault;
            return;
        }
        // find a negative code (medium preferred) and a default (medium preferred) (not necessarily the same)
        NumberFormatCode const * const pFormatArr = aFormatSeq.getArray();
        sal_Int32 nElem, nDef, nNeg, nMedium;
        nDef = nNeg = nMedium = -1;
        for ( nElem = 0; nElem < nCnt; nElem++ )
        {
            if ( pFormatArr[nElem].Type == KNumberFormatType::MEDIUM )
            {
                if ( pFormatArr[nElem].Default )
                {
                    nDef = nElem;
                    nMedium = nElem;
                    if ( pFormatArr[nElem].Code.indexOf( ';' ) >= 0 )
                        nNeg = nElem;
                }
                else
                {
                    if ( (nNeg == -1 || nMedium == -1) && pFormatArr[nElem].Code.indexOf( ';' ) >= 0 )
                        nNeg = nElem;
                    if ( nMedium == -1 )
                        nMedium = nElem;
                }
            }
            else
            {
                if ( nDef == -1 && pFormatArr[nElem].Default )
                    nDef = nElem;
                if ( nNeg == -1 && pFormatArr[nElem].Code.indexOf( ';' ) >= 0 )
                    nNeg = nElem;
            }
        }

        // make sure it's loaded
        getCurrSymbol();

        sal_Int32 nSign, nPar, nNum, nBlank, nSym;

        // positive format
        nElem = (nDef >= 0 ? nDef : (nNeg >= 0 ? nNeg : 0));
        scanCurrFormatImpl( pFormatArr[nElem].Code, 0, nSign, nPar, nNum, nBlank, nSym );
        if (areChecksEnabled() && (nNum == -1 || nSym == -1))
        {
            outputCheckMessage( appendLocaleInfo( "LocaleDataWrapper::getCurrFormatsImpl: CurrPositiveFormat?" ) );
        }
        if (nBlank == -1)
        {
            if ( nSym < nNum )
                nCurrPositiveFormat = 0;    // $1
            else
                nCurrPositiveFormat = 1;    // 1$
        }
        else
        {
            if ( nSym < nNum )
                nCurrPositiveFormat = 2;    // $ 1
            else
                nCurrPositiveFormat = 3;    // 1 $
        }

        // negative format
        if ( nNeg < 0 )
            nCurrNegativeFormat = nCurrFormatDefault;
        else
        {
            const OUString& rCode = pFormatArr[nNeg].Code;
            sal_Int32 nDelim = rCode.indexOf(';');
            scanCurrFormatImpl( rCode, nDelim+1, nSign, nPar, nNum, nBlank, nSym );
            if (areChecksEnabled() && (nNum == -1 || nSym == -1 || (nPar == -1 && nSign == -1)))
            {
                outputCheckMessage( appendLocaleInfo( "LocaleDataWrapper::getCurrFormatsImpl: CurrNegativeFormat?" ) );
            }
            // NOTE: one of nPar or nSign are allowed to be -1
            if (nBlank == -1)
            {
                if ( nSym < nNum )
                {
                    if ( -1 < nPar && nPar < nSym )
                        nCurrNegativeFormat = 0;    // ($1)
                    else if ( -1 < nSign && nSign < nSym )
                        nCurrNegativeFormat = 1;    // -$1
                    else if ( nNum < nSign )
                        nCurrNegativeFormat = 3;    // $1-
                    else
                        nCurrNegativeFormat = 2;    // $-1
                }
                else
                {
                    if ( -1 < nPar && nPar < nNum )
                        nCurrNegativeFormat = 4;    // (1$)
                    else if ( -1 < nSign && nSign < nNum )
                        nCurrNegativeFormat = 5;    // -1$
                    else if ( nSym < nSign )
                        nCurrNegativeFormat = 7;    // 1$-
                    else
                        nCurrNegativeFormat = 6;    // 1-$
                }
            }
            else
            {
                if ( nSym < nNum )
                {
                    if ( -1 < nPar && nPar < nSym )
                        nCurrNegativeFormat = 14;   // ($ 1)
                    else if ( -1 < nSign && nSign < nSym )
                        nCurrNegativeFormat = 9;    // -$ 1
                    else if ( nNum < nSign )
                        nCurrNegativeFormat = 12;   // $ 1-
                    else
                        nCurrNegativeFormat = 11;   // $ -1
                }
                else
                {
                    if ( -1 < nPar && nPar < nNum )
                        nCurrNegativeFormat = 15;   // (1 $)
                    else if ( -1 < nSign && nSign < nNum )
                        nCurrNegativeFormat = 8;    // -1 $
                    else if ( nSym < nSign )
                        nCurrNegativeFormat = 10;   // 1 $-
                    else
                        nCurrNegativeFormat = 13;   // 1- $
                }
            }
        }
    }

    // --- date -----------------------------------------------------------

    DateOrder LocaleDataWrapper::getDateOrder() const
    {
        ::utl::ReadWriteGuard aGuard( aMutex );
        if ( nDateOrder == DateOrder::Invalid )
        {
            aGuard.changeReadToWrite();
            const_cast<LocaleDataWrapper*>(this)->getDateOrdersImpl();
        }
        return nDateOrder;
    }

    DateOrder LocaleDataWrapper::getLongDateOrder() const
    {
        ::utl::ReadWriteGuard aGuard( aMutex );
        if ( nLongDateOrder == DateOrder::Invalid )
        {
            aGuard.changeReadToWrite();
            const_cast<LocaleDataWrapper*>(this)->getDateOrdersImpl();
        }
        return nLongDateOrder;
    }

    DateOrder LocaleDataWrapper::scanDateOrderImpl( const OUString& rCode ) const
    {
        // Only some european versions were translated, the ones with different
        // keyword combinations are:
        // English DMY, German TMJ, Spanish DMA, French JMA, Italian GMA,
        // Dutch DMJ, Finnish PKV

        // default is English keywords for every other language
        sal_Int32 nDay = rCode.indexOf('D');
        sal_Int32 nMonth = rCode.indexOf('M');
        sal_Int32 nYear = rCode.indexOf('Y');
        if (nDay == -1 || nMonth == -1 || nYear == -1)
        {   // This algorithm assumes that all three parts (DMY) are present
            if (nMonth == -1)
            {   // only Finnish has something else than 'M' for month
                nMonth = rCode.indexOf('K');
                if (nMonth != -1)
                {
                    nDay = rCode.indexOf('P');
                    nYear = rCode.indexOf('V');
                }
            }
            else if (nDay == -1)
            {   // We have a month 'M' if we reach this branch.
                // Possible languages containing 'M' but no 'D':
                // German, French, Italian
                nDay = rCode.indexOf('T');         // German
                if (nDay != -1)
                    nYear = rCode.indexOf('J');
                else
                {
                    nYear = rCode.indexOf('A');    // French, Italian
                    if (nYear != -1)
                    {
                        nDay = rCode.indexOf('J'); // French
                        if (nDay == -1)
                            nDay = rCode.indexOf('G'); // Italian
                    }
                }
            }
            else
            {   // We have a month 'M' and a day 'D'.
                // Possible languages containing 'D' and 'M' but not 'Y':
                // Spanish, Dutch
                nYear = rCode.indexOf('A');        // Spanish
                if (nYear == -1)
                    nYear = rCode.indexOf('J');    // Dutch
            }
            if (nDay == -1 || nMonth == -1 || nYear == -1)
            {
                if (areChecksEnabled())
                {
                    outputCheckMessage( appendLocaleInfo( "LocaleDataWrapper::scanDateOrder: not all DMY present" ) );
                }
                if (nDay == -1)
                    nDay = rCode.getLength();
                if (nMonth == -1)
                    nMonth = rCode.getLength();
                if (nYear == -1)
                    nYear = rCode.getLength();
            }
        }
        // compare with <= because each position may equal rCode.getLength()
        if ( nDay <= nMonth && nMonth <= nYear )
            return DateOrder::DMY;     // also if every position equals rCode.getLength()
        else if ( nMonth <= nDay && nDay <= nYear )
            return DateOrder::MDY;
        else if ( nYear <= nMonth && nMonth <= nDay )
            return DateOrder::YMD;
        else
        {
            if (areChecksEnabled())
            {
                outputCheckMessage( appendLocaleInfo( "LocaleDataWrapper::scanDateOrder: no magic applicable" ) );
            }
            return DateOrder::DMY;
        }
    }

    void LocaleDataWrapper::getDateOrdersImpl()
    {
        css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext );
        uno::Sequence< NumberFormatCode > aFormatSeq = xNFC->getAllFormatCode( KNumberFormatUsage::DATE, getMyLocale() );
        sal_Int32 nCnt = aFormatSeq.getLength();
        if ( !nCnt )
        {   // bad luck
            if (areChecksEnabled())
            {
                outputCheckMessage( appendLocaleInfo( "LocaleDataWrapper::getDateOrdersImpl: no date formats" ) );
            }
            nDateOrder = nLongDateOrder = DateOrder::DMY;
            return;
        }
        // find the edit (21), a default (medium preferred),
        // a medium (default preferred), and a long (default preferred)
        NumberFormatCode const * const pFormatArr = aFormatSeq.getArray();
        sal_Int32 nElem, nEdit, nDef, nMedium, nLong;
        nEdit = nDef = nMedium = nLong = -1;
        for ( nElem = 0; nElem < nCnt; nElem++ )
        {
            if ( nEdit == -1 && pFormatArr[nElem].Index == NumberFormatIndex::DATE_SYS_DDMMYYYY )
                nEdit = nElem;
            if ( nDef == -1 && pFormatArr[nElem].Default )
                nDef = nElem;
            switch ( pFormatArr[nElem].Type )
            {
                case KNumberFormatType::MEDIUM :
                {
                    if ( pFormatArr[nElem].Default )
                    {
                        nDef = nElem;
                        nMedium = nElem;
                    }
                    else if ( nMedium == -1 )
                        nMedium = nElem;
                }
                break;
                case KNumberFormatType::LONG :
                {
                    if ( pFormatArr[nElem].Default )
                        nLong = nElem;
                    else if ( nLong == -1 )
                        nLong = nElem;
                }
                break;
            }
        }
        if ( nEdit == -1 )
        {
            if (areChecksEnabled())
            {
                outputCheckMessage( appendLocaleInfo( "LocaleDataWrapper::getDateOrdersImpl: no edit" ) );
            }
            if ( nDef == -1 )
            {
                if (areChecksEnabled())
                {
                    outputCheckMessage( appendLocaleInfo( "LocaleDataWrapper::getDateOrdersImpl: no default" ) );
                }
                if ( nMedium != -1 )
                    nDef = nMedium;
                else if ( nLong != -1 )
                    nDef = nLong;
                else
                    nDef = 0;
            }
            nEdit = nDef;
        }
        DateOrder nDF = scanDateOrderImpl( pFormatArr[nEdit].Code );
        if ( pFormatArr[nEdit].Type == KNumberFormatType::LONG )
        {   // normally this is not the case
            nLongDateOrder = nDateOrder = nDF;
        }
        else
        {
            nDateOrder = nDF;
            if ( nLong == -1 )
                nLongDateOrder = nDF;
            else
                nLongDateOrder = scanDateOrderImpl( pFormatArr[nLong].Code );
        }
    }

    // --- digit grouping -------------------------------------------------

    void LocaleDataWrapper::getDigitGroupingImpl()
    {
        /* TODO: This is a very simplified grouping setup that only serves its
         * current purpose for Indian locales. A free-form flexible one would
         * obtain grouping from locale data where it could be specified using, for
         * example, codes like #,### and #,##,### that would generate the integer
         * sequence. Needed additional API and a locale data element.
         */

        if (!aGrouping.getLength())
        {
            aGrouping.realloc(3);   // room for {3,2,0}
            aGrouping[0] = 0;       // invalidate
        }
        if (!aGrouping[0])
        {
            i18n::LanguageCountryInfo aLCInfo( getLanguageCountryInfo());
            if (aLCInfo.Country.equalsIgnoreAsciiCase("IN") || // India
                aLCInfo.Country.equalsIgnoreAsciiCase("BT") )  // Bhutan
            {
                aGrouping[0] = 3;
                aGrouping[1] = 2;
                aGrouping[2] = 0;
            }
            else
            {
                aGrouping[0] = 3;
                aGrouping[1] = 0;
            }
        }
    }

    const css::uno::Sequence< sal_Int32 > LocaleDataWrapper::getDigitGrouping() const
    {
        ::utl::ReadWriteGuard aGuard( aMutex );
        if (!aGrouping.getLength() || aGrouping[0] == 0)
        {   // no cached content
            aGuard.changeReadToWrite();
            const_cast<LocaleDataWrapper*>(this)->getDigitGroupingImpl();
        }
        return aGrouping;
    }

    // --- simple number formatting helpers -------------------------------

    // The ImplAdd... methods are taken from class International and modified to
    // suit the needs.

    static sal_Unicode* ImplAddUNum( sal_Unicode* pBuf, sal_uInt64 nNumber )
    {
        // fill temp buffer with digits
        sal_Unicode aTempBuf[64];
        sal_Unicode* pTempBuf = aTempBuf;
        do
        {
            *pTempBuf = static_cast<sal_Unicode>(nNumber % 10) + '0';
            pTempBuf++;
            nNumber /= 10;
        }
        while ( nNumber );

        // copy temp buffer to buffer passed
        do
        {
            pTempBuf--;
            *pBuf = *pTempBuf;
            pBuf++;
        }
        while ( pTempBuf != aTempBuf );

        return pBuf;
    }

    static sal_Unicode* ImplAddUNum( sal_Unicode* pBuf, sal_uInt64 nNumber, int nMinLen )
    {
        // fill temp buffer with digits
        sal_Unicode aTempBuf[64];
        sal_Unicode* pTempBuf = aTempBuf;
        do
        {
            *pTempBuf = static_cast<sal_Unicode>(nNumber % 10) + '0';
            pTempBuf++;
            nNumber /= 10;
            nMinLen--;
        }
        while ( nNumber );

        // fill with zeros up to the minimal length
        while ( nMinLen > 0 )
        {
            *pBuf = '0';
            pBuf++;
            nMinLen--;
        }

        // copy temp buffer to real buffer
        do
        {
            pTempBuf--;
            *pBuf = *pTempBuf;
            pBuf++;
        }
        while ( pTempBuf != aTempBuf );

        return pBuf;
    }

    static sal_Unicode* ImplAddNum( sal_Unicode* pBuf, sal_Int64 nNumber, int nMinLen )
    {
        if (nNumber < 0)
        {
            *pBuf++ = '-';
            nNumber = -nNumber;
        }
        return ImplAddUNum( pBuf, nNumber, nMinLen);
    }

    static sal_Unicode* ImplAdd2UNum( sal_Unicode* pBuf, sal_uInt16 nNumber, bool bLeading )
    {
        DBG_ASSERT( nNumber < 100, "ImplAdd2UNum() - Number >= 100" );

        if ( nNumber < 10 )
        {
            if ( bLeading )
            {
                *pBuf = '0';
                pBuf++;
            }
            *pBuf = nNumber + '0';
        }
        else
        {
            sal_uInt16 nTemp = nNumber % 10;
            nNumber /= 10;
            *pBuf = nNumber + '0';
            pBuf++;
            *pBuf = nTemp + '0';
        }

        pBuf++;
        return pBuf;
    }

    static sal_Unicode* ImplAdd9UNum( sal_Unicode* pBuf, sal_uInt32 nNumber )
    {
        DBG_ASSERT( nNumber < 1000000000, "ImplAdd9UNum() - Number >= 1000000000" );

        std::ostringstream ostr;
        ostr.fill('0');
        ostr.width(9);
        ostr << nNumber;
        std::string aStr = ostr.str();
        for(const char *pAB= aStr.c_str(); *pAB != '\0'; ++pAB, ++pBuf)
        {
            *pBuf = *pAB;
        }

        return pBuf;
    }

    inline sal_Unicode* ImplAddString( sal_Unicode* pBuf, const OUString& rStr )
    {
        if ( rStr.getLength() == 1 )
            *pBuf++ = rStr[0];
        else if (rStr.isEmpty())
    ;
        else
        {
            memcpy( pBuf, rStr.getStr(), rStr.getLength() * sizeof(sal_Unicode) );
            pBuf += rStr.getLength();
        }
        return pBuf;
    }

    inline sal_Unicode* ImplAddString( sal_Unicode* pBuf, sal_Unicode c )
    {
        *pBuf = c;
        pBuf++;
        return pBuf;
    }

    inline sal_Unicode* ImplAddString( sal_Unicode* pBuf, const sal_Unicode* pCopyBuf, sal_Int32 nLen )
    {
        memcpy( pBuf, pCopyBuf, nLen * sizeof(sal_Unicode) );
        return pBuf + nLen;
    }

    sal_Unicode* LocaleDataWrapper::ImplAddFormatNum( sal_Unicode* pBuf,
            sal_Int64 nNumber, sal_uInt16 nDecimals, bool bUseThousandSep,
            bool bTrailingZeros ) const
    {
        sal_Unicode aNumBuf[64];
        sal_Unicode* pNumBuf;
        sal_uInt16  nNumLen;
        sal_uInt16  i = 0;

        // negative number
        if ( nNumber < 0 )
        {
            nNumber *= -1;
            *pBuf = '-';
            pBuf++;
        }

        // convert number
        pNumBuf = ImplAddUNum( aNumBuf, static_cast<sal_uInt64>(nNumber) );
        nNumLen = static_cast<sal_uInt16>(static_cast<sal_uLong>(pNumBuf-aNumBuf));
        pNumBuf = aNumBuf;

        if ( nNumLen <= nDecimals )
        {
            // strip .0 in decimals?
            if ( !nNumber && !bTrailingZeros )
            {
                *pBuf = '0';
                pBuf++;
            }
            else
            {
                // LeadingZero, insert 0
                if ( isNumLeadingZero() )
                {
                    *pBuf = '0';
                    pBuf++;
                }

                // append decimal separator
                pBuf = ImplAddString( pBuf, getNumDecimalSep() );

                // fill with zeros
                while ( i < (nDecimals-nNumLen) )
                {
                    *pBuf = '0';
                    pBuf++;
                    i++;
                }

                // append decimals
                while ( nNumLen )
                {
                    *pBuf = *pNumBuf;
                    pBuf++;
                    pNumBuf++;
                    nNumLen--;
                }
            }
        }
        else
        {
            const OUString& rThoSep = getNumThousandSep();

            // copy number to buffer (excluding decimals)
            sal_uInt16 nNumLen2 = nNumLen-nDecimals;
            uno::Sequence< sal_Bool > aGroupPos;
            if (bUseThousandSep)
                aGroupPos = utl::DigitGroupingIterator::createForwardSequence(
                        nNumLen2, getDigitGrouping());
            for (; i < nNumLen2; ++i )
            {
                *pBuf = *pNumBuf;
                pBuf++;
                pNumBuf++;

                // add thousand separator?
                if ( bUseThousandSep && aGroupPos[i] )
                    pBuf = ImplAddString( pBuf, rThoSep );
            }

            // append decimals
            if ( nDecimals )
            {
                pBuf = ImplAddString( pBuf, getNumDecimalSep() );

                bool bNullEnd = true;
                while ( i < nNumLen )
                {
                    if ( *pNumBuf != '0' )
                        bNullEnd = false;

                    *pBuf = *pNumBuf;
                    pBuf++;
                    pNumBuf++;
                    i++;
                }

                // strip .0 in decimals?
                if ( bNullEnd && !bTrailingZeros )
                    pBuf -= nDecimals+1;
            }
        }

        return pBuf;
    }

    // --- simple date and time formatting --------------------------------

    OUString LocaleDataWrapper::getDate( const Date& rDate ) const
    {
        ::utl::ReadWriteGuard aGuard( aMutex, ReadWriteGuardMode::BlockCritical );
    //!TODO: leading zeros et al
        sal_Unicode aBuf[128];
        sal_Unicode* pBuf = aBuf;
        sal_uInt16  nDay    = rDate.GetDay();
        sal_uInt16  nMonth  = rDate.GetMonth();
        sal_Int16   nYear   = rDate.GetYear();
        sal_uInt16  nYearLen;

        if ( (true) /* IsDateCentury() */ )
            nYearLen = 4;
        else
        {
            nYearLen = 2;
            nYear %= 100;
        }

        switch ( getDateOrder() )
        {
            case DateOrder::DMY :
                pBuf = ImplAdd2UNum( pBuf, nDay, true /* IsDateDayLeadingZero() */ );
                pBuf = ImplAddString( pBuf, getDateSep() );
                pBuf = ImplAdd2UNum( pBuf, nMonth, true /* IsDateMonthLeadingZero() */ );
                pBuf = ImplAddString( pBuf, getDateSep() );
                pBuf = ImplAddNum( pBuf, nYear, nYearLen );
            break;
            case DateOrder::MDY :
                pBuf = ImplAdd2UNum( pBuf, nMonth, true /* IsDateMonthLeadingZero() */ );
                pBuf = ImplAddString( pBuf, getDateSep() );
                pBuf = ImplAdd2UNum( pBuf, nDay, true /* IsDateDayLeadingZero() */ );
                pBuf = ImplAddString( pBuf, getDateSep() );
                pBuf = ImplAddNum( pBuf, nYear, nYearLen );
            break;
            default:
                pBuf = ImplAddNum( pBuf, nYear, nYearLen );
                pBuf = ImplAddString( pBuf, getDateSep() );
                pBuf = ImplAdd2UNum( pBuf, nMonth, true /* IsDateMonthLeadingZero() */ );
                pBuf = ImplAddString( pBuf, getDateSep() );
                pBuf = ImplAdd2UNum( pBuf, nDay, true /* IsDateDayLeadingZero() */ );
        }

        return OUString(aBuf, pBuf-aBuf);
    }

    OUString LocaleDataWrapper::getTime( const tools::Time& rTime, bool bSec, bool b100Sec ) const
    {
        ::utl::ReadWriteGuard aGuard( aMutex, ReadWriteGuardMode::BlockCritical );
    //!TODO: leading zeros et al
        sal_Unicode aBuf[128];
        sal_Unicode* pBuf = aBuf;
        sal_uInt16  nHour = rTime.GetHour();

        nHour %= 24;

        pBuf = ImplAdd2UNum( pBuf, nHour, true /* IsTimeLeadingZero() */ );
        pBuf = ImplAddString( pBuf, getTimeSep() );
        pBuf = ImplAdd2UNum( pBuf, rTime.GetMin(), true );
        if ( bSec )
        {
            pBuf = ImplAddString( pBuf, getTimeSep() );
            pBuf = ImplAdd2UNum( pBuf, rTime.GetSec(), true );

            if ( b100Sec )
            {
                pBuf = ImplAddString( pBuf, getTime100SecSep() );
                pBuf = ImplAdd9UNum( pBuf, rTime.GetNanoSec() );
            }
        }

        return OUString(aBuf, pBuf - aBuf);
    }

    OUString LocaleDataWrapper::getLongDate( const Date& rDate, CalendarWrapper& rCal,
            bool bTwoDigitYear ) const
    {
        ::utl::ReadWriteGuard aGuard( aMutex, ReadWriteGuardMode::BlockCritical );
        sal_Unicode     aBuf[20];
        sal_Unicode*    pBuf;
        OUString aStr;
        sal_Int16 nVal;
        rCal.setGregorianDateTime( rDate );
        // day of week
        nVal = rCal.getValue( CalendarFieldIndex::DAY_OF_WEEK );
        aStr += rCal.getDisplayName( CalendarDisplayIndex::DAY, nVal, 1 );
        aStr += getLongDateDayOfWeekSep();
        // day of month
        nVal = rCal.getValue( CalendarFieldIndex::DAY_OF_MONTH );
        pBuf = ImplAdd2UNum( aBuf, nVal, false/*bDayOfMonthWithLeadingZero*/ );
        OUString aDay(aBuf, pBuf-aBuf);
        // month of year
        nVal = rCal.getValue( CalendarFieldIndex::MONTH );
        OUString aMonth( rCal.getDisplayName( CalendarDisplayIndex::MONTH, nVal, 1 ) );
        // year
        nVal = rCal.getValue( CalendarFieldIndex::YEAR );
        if ( bTwoDigitYear )
            pBuf = ImplAddUNum( aBuf, nVal % 100, 2 );
        else
            pBuf = ImplAddUNum( aBuf, nVal );
        OUString aYear(aBuf, pBuf-aBuf);
        // concatenate
        switch ( getLongDateOrder() )
        {
            case DateOrder::DMY :
                aStr += aDay + getLongDateDaySep() + aMonth + getLongDateMonthSep() + aYear;
            break;
            case DateOrder::MDY :
                aStr += aMonth + getLongDateMonthSep() + aDay + getLongDateDaySep() + aYear;
            break;
            default:    // YMD
                aStr += aYear + getLongDateYearSep() +  aMonth +  getLongDateMonthSep() + aDay;
        }
        return aStr;
    }

    OUString LocaleDataWrapper::getDuration( const tools::Time& rTime, bool bSec, bool b100Sec ) const
    {
        ::utl::ReadWriteGuard aGuard( aMutex, ReadWriteGuardMode::BlockCritical );
        sal_Unicode aBuf[128];
        sal_Unicode* pBuf = aBuf;

        if ( rTime < tools::Time( 0 ) )
            pBuf = ImplAddString( pBuf, ' ' );

        if ( (true) /* IsTimeLeadingZero() */ )
            pBuf = ImplAddUNum( pBuf, rTime.GetHour(), 2 );
        else
            pBuf = ImplAddUNum( pBuf, rTime.GetHour() );
        pBuf = ImplAddString( pBuf, getTimeSep() );
        pBuf = ImplAdd2UNum( pBuf, rTime.GetMin(), true );
        if ( bSec )
        {
            pBuf = ImplAddString( pBuf, getTimeSep() );
            pBuf = ImplAdd2UNum( pBuf, rTime.GetSec(), true );

            if ( b100Sec )
            {
                pBuf = ImplAddString( pBuf, getTime100SecSep() );
                pBuf = ImplAdd9UNum( pBuf, rTime.GetNanoSec() );
            }
        }

        return OUString(aBuf, pBuf-aBuf);
    }

    // --- simple number formatting ---------------------------------------

    inline size_t ImplGetNumberStringLengthGuess( const LocaleDataWrapper& rLoc, sal_uInt16 nDecimals )
    {
        // approximately 3.2 bits per digit
        const size_t nDig = ((sizeof(sal_Int64) * 8) / 3) + 1;
        // digits, separators (pessimized for insane "every digit may be grouped"), leading zero, sign
        size_t nGuess = ((nDecimals < nDig) ?
            (((nDig - nDecimals) * rLoc.getNumThousandSep().getLength()) + nDig) :
            nDecimals) + rLoc.getNumDecimalSep().getLength() + 3;
        return nGuess;
    }

    OUString LocaleDataWrapper::getNum( sal_Int64 nNumber, sal_uInt16 nDecimals,
            bool bUseThousandSep, bool bTrailingZeros ) const
    {
        ::utl::ReadWriteGuard aGuard( aMutex, ReadWriteGuardMode::BlockCritical );
        sal_Unicode aBuf[128];      // big enough for 64-bit long and crazy grouping
        // check if digits and separators will fit into fixed buffer or allocate
        size_t nGuess = ImplGetNumberStringLengthGuess( *this, nDecimals );
        sal_Unicode* const pBuffer = (nGuess < 118 ? aBuf :
            new sal_Unicode[nGuess + 16]);

        sal_Unicode* pBuf = ImplAddFormatNum( pBuffer, nNumber, nDecimals,
            bUseThousandSep, bTrailingZeros );
        OUString aStr(pBuffer, pBuf-pBuffer);

        if ( pBuffer != aBuf )
            delete [] pBuffer;
        return aStr;
    }

    OUString LocaleDataWrapper::getCurr( sal_Int64 nNumber, sal_uInt16 nDecimals,
            const OUString& rCurrencySymbol, bool bUseThousandSep ) const
    {
        ::utl::ReadWriteGuard aGuard( aMutex, ReadWriteGuardMode::BlockCritical );
        sal_Unicode aBuf[192];
        sal_Unicode aNumBuf[128];    // big enough for 64-bit long and crazy grouping
        sal_Unicode cZeroChar = getCurrZeroChar();

        // check if digits and separators will fit into fixed buffer or allocate
        size_t nGuess = ImplGetNumberStringLengthGuess( *this, nDecimals );
        sal_Unicode* const pNumBuffer = (nGuess < 118 ? aNumBuf :
            new sal_Unicode[nGuess + 16]);

        sal_Unicode* const pBuffer =
            ((size_t(rCurrencySymbol.getLength()) + nGuess + 20) < SAL_N_ELEMENTS(aBuf) ? aBuf :
            new sal_Unicode[ rCurrencySymbol.getLength() + nGuess + 20 ]);
        sal_Unicode* pBuf = pBuffer;

        bool bNeg;
        if ( nNumber < 0 )
        {
            bNeg = true;
            nNumber *= -1;
        }
        else
            bNeg = false;

        // convert number
        sal_Unicode* pEndNumBuf = ImplAddFormatNum( pNumBuffer, nNumber, nDecimals,
            bUseThousandSep, true );
        sal_Int32 nNumLen = static_cast<sal_Int32>(static_cast<sal_uLong>(pEndNumBuf-pNumBuffer));

        // replace zeros with zero character
        if ( (cZeroChar != '0') && nDecimals /* && IsNumTrailingZeros() */ )
        {
            sal_Unicode* pTempBuf;
            sal_uInt16  i;
            bool    bZero = true;

            pTempBuf = pNumBuffer+nNumLen-nDecimals;
            i = 0;
            do
            {
                if ( *pTempBuf != '0' )
                {
                    bZero = false;
                    break;
                }

                pTempBuf++;
                i++;
            }
            while ( i < nDecimals );

            if ( bZero )
            {
                pTempBuf = pNumBuffer+nNumLen-nDecimals;
                i = 0;
                do
                {
                    *pTempBuf = cZeroChar;
                    pTempBuf++;
                    i++;
                }
                while ( i < nDecimals );
            }
        }

        if ( !bNeg )
        {
            switch( getCurrPositiveFormat() )
            {
                case 0:
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    break;
                case 1:
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    break;
                case 2:
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    pBuf = ImplAddString( pBuf, ' ' );
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    break;
                case 3:
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    pBuf = ImplAddString( pBuf, ' ' );
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    break;
            }
        }
        else
        {
            switch( getCurrNegativeFormat() )
            {
                case 0:
                    pBuf = ImplAddString( pBuf, '(' );
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    pBuf = ImplAddString( pBuf, ')' );
                    break;
                case 1:
                    pBuf = ImplAddString( pBuf, '-' );
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    break;
                case 2:
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    pBuf = ImplAddString( pBuf, '-' );
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    break;
                case 3:
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    pBuf = ImplAddString( pBuf, '-' );
                    break;
                case 4:
                    pBuf = ImplAddString( pBuf, '(' );
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    pBuf = ImplAddString( pBuf, ')' );
                    break;
                case 5:
                    pBuf = ImplAddString( pBuf, '-' );
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    break;
                case 6:
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    pBuf = ImplAddString( pBuf, '-' );
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    break;
                case 7:
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    pBuf = ImplAddString( pBuf, '-' );
                    break;
                case 8:
                    pBuf = ImplAddString( pBuf, '-' );
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    pBuf = ImplAddString( pBuf, ' ' );
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    break;
                case 9:
                    pBuf = ImplAddString( pBuf, '-' );
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    pBuf = ImplAddString( pBuf, ' ' );
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    break;
                case 10:
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    pBuf = ImplAddString( pBuf, ' ' );
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    pBuf = ImplAddString( pBuf, '-' );
                    break;
                case 11:
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    pBuf = ImplAddString( pBuf, ' ' );
                    pBuf = ImplAddString( pBuf, '-' );
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    break;
                case 12:
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    pBuf = ImplAddString( pBuf, ' ' );
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    pBuf = ImplAddString( pBuf, '-' );
                    break;
                case 13:
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    pBuf = ImplAddString( pBuf, '-' );
                    pBuf = ImplAddString( pBuf, ' ' );
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    break;
                case 14:
                    pBuf = ImplAddString( pBuf, '(' );
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    pBuf = ImplAddString( pBuf, ' ' );
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    pBuf = ImplAddString( pBuf, ')' );
                    break;
                case 15:
                    pBuf = ImplAddString( pBuf, '(' );
                    pBuf = ImplAddString( pBuf, pNumBuffer, nNumLen );
                    pBuf = ImplAddString( pBuf, ' ' );
                    pBuf = ImplAddString( pBuf, rCurrencySymbol );
                    pBuf = ImplAddString( pBuf, ')' );
                    break;
            }
        }

        OUString aNumber(pBuffer, pBuf-pBuffer);

        if ( pBuffer != aBuf )
            delete [] pBuffer;
        if ( pNumBuffer != aNumBuf )
            delete [] pNumBuffer;

        return aNumber;
    }

    // --- number parsing -------------------------------------------------

    double LocaleDataWrapper::stringToDouble( const OUString& rString, bool bUseGroupSep,
            rtl_math_ConversionStatus* pStatus, sal_Int32* pParseEnd ) const
    {
        const sal_Unicode cGroupSep = (bUseGroupSep ? getNumThousandSep()[0] : 0);
        rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok;
        sal_Int32 nParseEnd = 0;
        double fValue = rtl::math::stringToDouble( rString, getNumDecimalSep()[0], cGroupSep, &eStatus, &nParseEnd);
        bool bTryAlt = (nParseEnd < rString.getLength() && !getNumDecimalSepAlt().isEmpty() &&
                rString[nParseEnd] == getNumDecimalSepAlt().toChar());
        // Try re-parsing with alternative if that was the reason to stop.
        if (bTryAlt)
            fValue = rtl::math::stringToDouble( rString, getNumDecimalSepAlt().toChar(), cGroupSep, &eStatus, &nParseEnd);
        if (pStatus)
            *pStatus = eStatus;
        if (pParseEnd)
            *pParseEnd = nParseEnd;
        return fValue;
    }

    double LocaleDataWrapper::stringToDouble( const sal_Unicode* pBegin, const sal_Unicode* pEnd, bool bUseGroupSep,
            rtl_math_ConversionStatus* pStatus, const sal_Unicode** ppParseEnd ) const
    {
        const sal_Unicode cGroupSep = (bUseGroupSep ? getNumThousandSep()[0] : 0);
        rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok;
        const sal_Unicode* pParseEnd = nullptr;
        double fValue = rtl_math_uStringToDouble( pBegin, pEnd, getNumDecimalSep()[0], cGroupSep, &eStatus, &pParseEnd);
        bool bTryAlt = (pParseEnd < pEnd && !getNumDecimalSepAlt().isEmpty() &&
                *pParseEnd == getNumDecimalSepAlt().toChar());
        // Try re-parsing with alternative if that was the reason to stop.
        if (bTryAlt)
            fValue = rtl_math_uStringToDouble( pBegin, pEnd, getNumDecimalSepAlt().toChar(), cGroupSep, &eStatus, &pParseEnd);
        if (pStatus)
            *pStatus = eStatus;
        if (ppParseEnd)
            *ppParseEnd = pParseEnd;
        return fValue;
    }

    // --- mixed ----------------------------------------------------------

    LanguageTag LocaleDataWrapper::getLoadedLanguageTag() const
    {
        LanguageCountryInfo aLCInfo = getLanguageCountryInfo();
        return LanguageTag( lang::Locale( aLCInfo.Language, aLCInfo.Country, aLCInfo.Variant ));
    }

    OUString LocaleDataWrapper::appendLocaleInfo(const OUString& rDebugMsg) const
    {
        ::utl::ReadWriteGuard aGuard( aMutex, ReadWriteGuardMode::BlockCritical );
        OUStringBuffer aDebugMsg(rDebugMsg);
        aDebugMsg.append('\n');
        aDebugMsg.append(maLanguageTag.getBcp47());
        aDebugMsg.append(" requested\n");
        LanguageTag aLoaded = getLoadedLanguageTag();
        aDebugMsg.append(aLoaded.getBcp47());
        aDebugMsg.append(" loaded");
        return aDebugMsg.makeStringAndClear();
    }

    // static
    void LocaleDataWrapper::outputCheckMessage( const OUString& rMsg )
    {
        outputCheckMessage(OUStringToOString(rMsg, RTL_TEXTENCODING_UTF8).getStr());
    }

    // static
    void LocaleDataWrapper::outputCheckMessage( const char* pStr )
    {
        fprintf( stderr, "\n%s\n", pStr);
        fflush( stderr);
        SAL_WARN("unotools.i18n", pStr);
    }

    // static
    void LocaleDataWrapper::evaluateLocaleDataChecking()
    {
        // Using the rtl_Instance template here wouldn't solve all threaded write
        // accesses, since we want to assign the result to the static member
        // variable and would need to dereference the pointer returned and assign
        // the value unguarded. This is the same pattern manually coded.
        sal_uInt8 nCheck = nLocaleDataChecking;
        if (!nCheck)
        {
            ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex());
            nCheck = nLocaleDataChecking;
            if (!nCheck)
            {
    #ifdef DBG_UTIL
                nCheck = 1;
    #else
                const char* pEnv = getenv( "OOO_ENABLE_LOCALE_DATA_CHECKS");
                if (pEnv && (pEnv[0] == 'Y' || pEnv[0] == 'y' || pEnv[0] == '1'))
                    nCheck = 1;
                else
                    nCheck = 2;
    #endif
                OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
                nLocaleDataChecking = nCheck;
            }
        }
        else {
            OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
        }
    }

    // --- XLocaleData3 ----------------------------------------------------------

    css::uno::Sequence< css::i18n::Calendar2 > LocaleDataWrapper::getAllCalendars() const
    {
        try
        {
            return xLD->getAllCalendars2( getMyLocale() );
        }
        catch (const Exception& e)
        {
            SAL_WARN( "unotools.i18n", "getAllCalendars: Exception caught " << e );
        }
        return css::uno::Sequence< css::i18n::Calendar2 >(0);
    }

    // --- XLocaleData4 ----------------------------------------------------------

    css::uno::Sequence< OUString > LocaleDataWrapper::getDateAcceptancePatterns() const
    {
        ::utl::ReadWriteGuard aGuard( aMutex );

        if (aDateAcceptancePatterns.getLength())
            return aDateAcceptancePatterns;

        aGuard.changeReadToWrite();

        try
        {
            const_cast<LocaleDataWrapper*>(this)->aDateAcceptancePatterns =
                xLD->getDateAcceptancePatterns( getMyLocale() );
            return aDateAcceptancePatterns;
        }
        catch (const Exception& e)
        {
            SAL_WARN( "unotools.i18n", "getDateAcceptancePatterns: Exception caught " << e );
        }
        return css::uno::Sequence< OUString >(0);
    }

    // --- Override layer --------------------------------------------------------

    void LocaleDataWrapper::setDateAcceptancePatterns(
            const css::uno::Sequence< OUString > & rPatterns )
    {
        ::utl::ReadWriteGuard aGuard( aMutex, ReadWriteGuardMode::Write );

        if (!aDateAcceptancePatterns.getLength() || !rPatterns.getLength())
        {
            try
            {
                aDateAcceptancePatterns = xLD->getDateAcceptancePatterns( getMyLocale() );
            }
            catch (const Exception& e)
            {
                SAL_WARN( "unotools.i18n", "setDateAcceptancePatterns: Exception caught " << e );
            }
            if (!rPatterns.getLength())
                return;     // just a reset
            if (!aDateAcceptancePatterns.getLength())
            {
                aDateAcceptancePatterns = rPatterns;
                return;
            }
        }

        // Never overwrite the locale's full date pattern! The first.
        if (aDateAcceptancePatterns[0] == rPatterns[0])
            aDateAcceptancePatterns = rPatterns;    // sane
        else
        {
            // Copy existing full date pattern and append the sequence passed.
            /* TODO: could check for duplicates and shrink target sequence */
            Sequence< OUString > aTmp( rPatterns.getLength() + 1 );
            OUString* pArray1 = aTmp.getArray();
            const OUString* pArray2 = rPatterns.getConstArray();
            pArray1[0] = aDateAcceptancePatterns[0];
            for (sal_Int32 i=0; i < rPatterns.getLength(); ++i)
                pArray1[i+1] = pArray2[i];
            aDateAcceptancePatterns = aTmp;
        }
    }

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

↑ V560 A part of conditional expression is always false: (cZeroChar != '0').