/* -*- 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 <limits>
#include <stdio.h>
#include <string>
#include <sal/log.hxx>
#include <unotools/localedatawrapper.hxx>
#include <unotools/digitgroupingiterator.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <tools/debug.hxx>
#include <i18nlangtag/languagetag.hxx>
#include <o3tl/safeint.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/NumberFormatIndex.hpp>
#include <com/sun/star/i18n/NumberFormatMapper.hpp>
#include <comphelper/processfactory.hxx>
#include <comphelper/sequence.hxx>
#include <rtl/ustrbuf.hxx>
#include <rtl/math.hxx>
#include <tools/date.hxx>
#include <tools/time.hxx>
#include <tools/duration.hxx>
#include <o3tl/string_view.hxx>
#include <utility>
const sal_uInt16 nCurrFormatDefault = 0;
using namespace ::com::sun::star;
using namespace ::com::sun::star::i18n;
using namespace ::com::sun::star::uno;
namespace
{
uno::Sequence< lang::Locale > gInstalledLocales;
std::vector< LanguageType > gInstalledLanguageTypes;
}
sal_uInt8 LocaleDataWrapper::nLocaleDataChecking = 0;
LocaleDataWrapper::LocaleDataWrapper(
const Reference< uno::XComponentContext > & rxContext,
LanguageTag aLanguageTag
)
:
m_xContext( rxContext ),
xLD( LocaleData2::create(rxContext) ),
maLanguageTag(std::move( aLanguageTag ))
{
loadData();
loadDateAcceptancePatterns({});
}
LocaleDataWrapper::LocaleDataWrapper(
LanguageTag aLanguageTag,
const std::vector<OUString> & rOverrideDateAcceptancePatterns
)
:
m_xContext( comphelper::getProcessComponentContext() ),
xLD( LocaleData2::create(m_xContext) ),
maLanguageTag(std::move( aLanguageTag ))
{
loadData();
loadDateAcceptancePatterns(rOverrideDateAcceptancePatterns);
}
LocaleDataWrapper::~LocaleDataWrapper()
{
}
const LanguageTag& LocaleDataWrapper::getLanguageTag() const
{
return maLanguageTag;
}
const css::lang::Locale& LocaleDataWrapper::getMyLocale() const
{
return maLanguageTag.getLocale();
}
void LocaleDataWrapper::loadData()
{
const css::lang::Locale& rMyLocale = maLanguageTag.getLocale();
{
const Sequence< Currency2 > aCurrSeq = getAllCurrencies();
if ( !aCurrSeq.hasElements() )
{
if (areChecksEnabled())
outputCheckMessage("LocaleDataWrapper::getCurrSymbolsImpl: no currency at all, using ShellsAndPebbles");
aCurrSymbol = "ShellsAndPebbles";
aCurrBankSymbol = aCurrSymbol;
nCurrPositiveFormat = nCurrNegativeFormat = nCurrFormatDefault;
nCurrDigits = 2;
}
else
{
auto pCurr = std::find_if(aCurrSeq.begin(), aCurrSeq.end(),
[](const Currency2& rCurr) { return rCurr.Default; });
if ( pCurr == aCurrSeq.end() )
{
if (areChecksEnabled())
{
outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrSymbolsImpl: no default currency" ) );
}
pCurr = aCurrSeq.begin();
}
aCurrSymbol = pCurr->Symbol;
aCurrBankSymbol = pCurr->BankSymbol;
nCurrDigits = pCurr->DecimalPlaces;
}
}
loadCurrencyFormats();
{
xDefaultCalendar.reset();
xSecondaryCalendar.reset();
const Sequence< Calendar2 > xCals = getAllCalendars();
if (xCals.getLength() > 1)
{
auto pCal = std::find_if(xCals.begin(), xCals.end(),
[](const Calendar2& rCal) { return !rCal.Default; });
if (pCal != xCals.end())
xSecondaryCalendar = std::make_shared<Calendar2>( *pCal);
}
auto pCal = xCals.begin();
if (xCals.getLength() > 1)
{
pCal = std::find_if(xCals.begin(), xCals.end(),
[](const Calendar2& rCal) { return rCal.Default; });
if (pCal == xCals.end())
pCal = xCals.begin();
}
xDefaultCalendar = std::make_shared<Calendar2>( *pCal);
}
loadDateOrders();
try
{
aDateAcceptancePatterns = xLD->getDateAcceptancePatterns( rMyLocale );
}
catch (const Exception&)
{
TOOLS_WARN_EXCEPTION( "unotools.i18n", "getDateAcceptancePatterns" );
aDateAcceptancePatterns = {};
}
loadDigitGrouping();
try
{
aReservedWords = comphelper::sequenceToContainer<std::vector<OUString>>(xLD->getReservedWord( rMyLocale ));
}
catch ( const Exception& )
{
TOOLS_WARN_EXCEPTION( "unotools.i18n", "getReservedWord" );
}
try
{
aLocaleDataItem = xLD->getLocaleItem2( rMyLocale );
}
catch (const Exception&)
{
TOOLS_WARN_EXCEPTION( "unotools.i18n", "getLocaleItem" );
static const css::i18n::LocaleDataItem2 aEmptyItem;
aLocaleDataItem = aEmptyItem;
}
aLocaleItem[LocaleItem::DATE_SEPARATOR] = aLocaleDataItem.dateSeparator;
aLocaleItem[LocaleItem::THOUSAND_SEPARATOR] = aLocaleDataItem.thousandSeparator;
aLocaleItem[LocaleItem::DECIMAL_SEPARATOR] = aLocaleDataItem.decimalSeparator;
aLocaleItem[LocaleItem::TIME_SEPARATOR] = aLocaleDataItem.timeSeparator;
aLocaleItem[LocaleItem::TIME_100SEC_SEPARATOR] = aLocaleDataItem.time100SecSeparator;
aLocaleItem[LocaleItem::LIST_SEPARATOR] = aLocaleDataItem.listSeparator;
aLocaleItem[LocaleItem::SINGLE_QUOTATION_START] = aLocaleDataItem.quotationStart;
aLocaleItem[LocaleItem::SINGLE_QUOTATION_END] = aLocaleDataItem.quotationEnd;
aLocaleItem[LocaleItem::DOUBLE_QUOTATION_START] = aLocaleDataItem.doubleQuotationStart;
aLocaleItem[LocaleItem::DOUBLE_QUOTATION_END] = aLocaleDataItem.doubleQuotationEnd;
aLocaleItem[LocaleItem::MEASUREMENT_SYSTEM] = aLocaleDataItem.measurementSystem;
aLocaleItem[LocaleItem::TIME_AM] = aLocaleDataItem.timeAM;
aLocaleItem[LocaleItem::TIME_PM] = aLocaleDataItem.timePM;
aLocaleItem[LocaleItem::LONG_DATE_DAY_OF_WEEK_SEPARATOR] = aLocaleDataItem.LongDateDayOfWeekSeparator;
aLocaleItem[LocaleItem::LONG_DATE_DAY_SEPARATOR] = aLocaleDataItem.LongDateDaySeparator;
aLocaleItem[LocaleItem::LONG_DATE_MONTH_SEPARATOR] = aLocaleDataItem.LongDateMonthSeparator;
aLocaleItem[LocaleItem::LONG_DATE_YEAR_SEPARATOR] = aLocaleDataItem.LongDateYearSeparator;
aLocaleItem[LocaleItem::DECIMAL_SEPARATOR_ALTERNATIVE] = aLocaleDataItem.decimalSeparatorAlternative;
}
/* 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&)
{
TOOLS_WARN_EXCEPTION( "unotools.i18n", "getLanguageCountryInfo" );
}
return css::i18n::LanguageCountryInfo();
}
const css::i18n::LocaleDataItem2& LocaleDataWrapper::getLocaleItem() const
{
return aLocaleDataItem;
}
css::uno::Sequence< css::i18n::Currency2 > LocaleDataWrapper::getAllCurrencies() const
{
try
{
return xLD->getAllCurrencies2( getMyLocale() );
}
catch (const Exception&)
{
TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllCurrencies" );
}
return {};
}
css::uno::Sequence< css::i18n::FormatElement > LocaleDataWrapper::getAllFormats() const
{
try
{
return xLD->getAllFormats( getMyLocale() );
}
catch (const Exception&)
{
TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllFormats" );
}
return {};
}
css::i18n::ForbiddenCharacters LocaleDataWrapper::getForbiddenCharacters() const
{
try
{
return xLD->getForbiddenCharacters( getMyLocale() );
}
catch (const Exception&)
{
TOOLS_WARN_EXCEPTION( "unotools.i18n", "getForbiddenCharacters" );
}
return css::i18n::ForbiddenCharacters();
}
const css::uno::Sequence< css::lang::Locale > & LocaleDataWrapper::getAllInstalledLocaleNames() const
{
uno::Sequence< lang::Locale > &rInstalledLocales = gInstalledLocales;
if ( rInstalledLocales.hasElements() )
return rInstalledLocales;
try
{
rInstalledLocales = xLD->getAllInstalledLocaleNames();
}
catch ( const Exception& )
{
TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllInstalledLocaleNames" );
}
return rInstalledLocales;
}
// --- Impl and helpers ----------------------------------------------------
// static
const css::uno::Sequence< css::lang::Locale >& LocaleDataWrapper::getInstalledLocaleNames()
{
const uno::Sequence< lang::Locale > &rInstalledLocales = gInstalledLocales;
if ( !rInstalledLocales.hasElements() )
{
LocaleDataWrapper aLDW( ::comphelper::getProcessComponentContext(), LanguageTag( LANGUAGE_SYSTEM) );
aLDW.getAllInstalledLocaleNames();
}
return rInstalledLocales;
}
// static
const std::vector< LanguageType >& LocaleDataWrapper::getInstalledLanguageTypes()
{
std::vector< LanguageType > &rInstalledLanguageTypes = gInstalledLanguageTypes;
if ( !rInstalledLanguageTypes.empty() )
return rInstalledLanguageTypes;
const css::uno::Sequence< css::lang::Locale > xLoc = getInstalledLocaleNames();
sal_Int32 nCount = xLoc.getLength();
std::vector< LanguageType > xLang;
xLang.reserve(nCount);
for ( const auto& rLoc : xLoc )
{
LanguageTag aLanguageTag( rLoc );
OUString aDebugLocale;
if (areChecksEnabled())
{
aDebugLocale = aLanguageTag.getBcp47( false);
}
LanguageType eLang = aLanguageTag.getLanguageType( false);
if (areChecksEnabled() && eLang == LANGUAGE_DONTKNOW)
{
OUString aMsg = "ConvertIsoNamesToLanguage: unknown MS-LCID for locale\n" +
aDebugLocale;
outputCheckMessage(aMsg);
}
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
)
{
outputCheckMessage(Concat2View(
"ConvertIsoNamesToLanguage/ConvertLanguageToIsoNames: ambiguous locale (MS-LCID?)\n"
+ aDebugLocale
+ " -> 0x"
+ OUString::number(static_cast<sal_Int32>(static_cast<sal_uInt16>(eLang)), 16)
+ " -> "
+ aBackLanguageTag.getBcp47() ));
}
eLang = LANGUAGE_DONTKNOW;
}
}
if ( eLang != LANGUAGE_DONTKNOW )
xLang.push_back(eLang);
}
rInstalledLanguageTypes = std::move(xLang);
return rInstalledLanguageTypes;
}
const OUString& LocaleDataWrapper::getOneLocaleItem( sal_Int16 nItem ) const
{
if ( nItem >= LocaleItem::COUNT2 )
{
SAL_WARN( "unotools.i18n", "getOneLocaleItem: bounds" );
return aLocaleItem[0];
}
return aLocaleItem[nItem];
}
const OUString& LocaleDataWrapper::getOneReservedWord( sal_Int16 nWord ) const
{
if ( nWord < 0 || o3tl::make_unsigned(nWord) >= aReservedWords.size() )
{
SAL_WARN( "unotools.i18n", "getOneReservedWord: bounds" );
return EMPTY_OUSTRING;
}
return aReservedWords[nWord];
}
// static
MeasurementSystem LocaleDataWrapper::mapMeasurementStringToEnum( std::u16string_view rMS )
{
//! TODO: could be cached too
if ( o3tl::equalsIgnoreAsciiCase( rMS, u"metric" ) )
return MeasurementSystem::Metric;
//! TODO: other measurement systems? => extend enum MeasurementSystem
return MeasurementSystem::US;
}
bool LocaleDataWrapper::doesSecondaryCalendarUseEC( std::u16string_view rName ) const
{
if (rName.empty())
return false;
// Check language tag first to avoid loading all calendars of this locale.
LanguageTag aLoaded( getLoadedLanguageTag());
const 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;
if (!xSecondaryCalendar)
return false;
if (!xSecondaryCalendar->Name.equalsIgnoreAsciiCase( rName))
return false;
return true;
}
const std::shared_ptr< css::i18n::Calendar2 >& LocaleDataWrapper::getDefaultCalendar() const
{
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
{
return aCurrSymbol;
}
const OUString& LocaleDataWrapper::getCurrBankSymbol() const
{
return aCurrBankSymbol;
}
sal_uInt16 LocaleDataWrapper::getCurrPositiveFormat() const
{
return nCurrPositiveFormat;
}
sal_uInt16 LocaleDataWrapper::getCurrNegativeFormat() const
{
return nCurrNegativeFormat;
}
sal_uInt16 LocaleDataWrapper::getCurrDigits() const
{
return nCurrDigits;
}
void LocaleDataWrapper::scanCurrFormatImpl( std::u16string_view 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.data();
const sal_Unicode* const pStop = pStr + rCode.size();
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 && o3tl::starts_with(rCode.substr(static_cast<sal_Int32>(p - pStr)), aCurrSymbol))
{ // 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::loadCurrencyFormats()
{
css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext );
uno::Sequence< NumberFormatCode > aFormatSeq = xNFC->getAllFormatCode( KNumberFormatUsage::CURRENCY, maLanguageTag.getLocale() );
sal_Int32 nCnt = aFormatSeq.getLength();
if ( !nCnt )
{ // bad luck
if (areChecksEnabled())
{
outputCheckMessage( appendLocaleInfo( u"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;
}
}
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( u"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( u"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
{
return nDateOrder;
}
LongDateOrder LocaleDataWrapper::getLongDateOrder() const
{
return nLongDateOrder;
}
LongDateOrder LocaleDataWrapper::scanDateOrderImpl( std::u16string_view 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
size_t nDay = rCode.find('D');
size_t nMonth = rCode.find('M');
size_t nYear = rCode.find('Y');
if (nDay == std::u16string_view::npos || nMonth == std::u16string_view::npos || nYear == std::u16string_view::npos)
{ // This algorithm assumes that all three parts (DMY) are present
if (nMonth == std::u16string_view::npos)
{ // only Finnish has something else than 'M' for month
nMonth = rCode.find('K');
if (nMonth != std::u16string_view::npos)
{
nDay = rCode.find('P');
nYear = rCode.find('V');
}
}
else if (nDay == std::u16string_view::npos)
{ // We have a month 'M' if we reach this branch.
// Possible languages containing 'M' but no 'D':
// German, French, Italian
nDay = rCode.find('T'); // German
if (nDay != std::u16string_view::npos)
nYear = rCode.find('J');
else
{
nYear = rCode.find('A'); // French, Italian
if (nYear != std::u16string_view::npos)
{
nDay = rCode.find('J'); // French
if (nDay == std::u16string_view::npos)
nDay = rCode.find('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.find('A'); // Spanish
if (nYear == std::u16string_view::npos)
nYear = rCode.find('J'); // Dutch
}
if (nDay == std::u16string_view::npos || nMonth == std::u16string_view::npos || nYear == std::u16string_view::npos)
{
if (areChecksEnabled())
{
outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::scanDateOrder: not all DMY present" ) );
}
if (nDay == std::u16string_view::npos)
nDay = rCode.size();
if (nMonth == std::u16string_view::npos)
nMonth = rCode.size();
if (nYear == std::u16string_view::npos)
nYear = rCode.size();
}
}
// compare with <= because each position may equal rCode.getLength()
if ( nDay <= nMonth && nMonth <= nYear )
return LongDateOrder::DMY; // also if every position equals rCode.getLength()
else if ( nMonth <= nDay && nDay <= nYear )
return LongDateOrder::MDY;
else if ( nYear <= nMonth && nMonth <= nDay )
return LongDateOrder::YMD;
else if ( nYear <= nDay && nDay <= nMonth )
return LongDateOrder::YDM;
else
{
if (areChecksEnabled())
{
outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::scanDateOrder: no magic applicable" ) );
}
return LongDateOrder::DMY;
}
}
static DateOrder getDateOrderFromLongDateOrder( LongDateOrder eLong )
{
switch (eLong)
{
case LongDateOrder::YMD:
return DateOrder::YMD;
break;
case LongDateOrder::DMY:
return DateOrder::DMY;
break;
case LongDateOrder::MDY:
return DateOrder::MDY;
break;
case LongDateOrder::YDM:
default:
assert(!"unhandled LongDateOrder to DateOrder");
return DateOrder::DMY;
}
}
void LocaleDataWrapper::loadDateOrders()
{
css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext );
uno::Sequence< NumberFormatCode > aFormatSeq = xNFC->getAllFormatCode( KNumberFormatUsage::DATE, maLanguageTag.getLocale() );
sal_Int32 nCnt = aFormatSeq.getLength();
if ( !nCnt )
{ // bad luck
if (areChecksEnabled())
{
outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getDateOrdersImpl: no date formats" ) );
}
nDateOrder = DateOrder::DMY;
nLongDateOrder = LongDateOrder::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 nEdit, nDef, nMedium, nLong;
nEdit = nDef = nMedium = nLong = -1;
for ( sal_Int32 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( u"LocaleDataWrapper::getDateOrdersImpl: no edit" ) );
}
if ( nDef == -1 )
{
if (areChecksEnabled())
{
outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getDateOrdersImpl: no default" ) );
}
if ( nMedium != -1 )
nDef = nMedium;
else if ( nLong != -1 )
nDef = nLong;
else
nDef = 0;
}
nEdit = nDef;
}
LongDateOrder nDO = scanDateOrderImpl( pFormatArr[nEdit].Code );
if ( pFormatArr[nEdit].Type == KNumberFormatType::LONG )
{ // normally this is not the case
nLongDateOrder = nDO;
nDateOrder = getDateOrderFromLongDateOrder(nDO);
}
else
{
// YDM should not occur in a short/medium date (i.e. no locale has
// that) and is nowhere handled.
nDateOrder = getDateOrderFromLongDateOrder(nDO);
if ( nLong == -1 )
nLongDateOrder = nDO;
else
nLongDateOrder = scanDateOrderImpl( pFormatArr[nLong].Code );
}
}
// --- digit grouping -------------------------------------------------
void LocaleDataWrapper::loadDigitGrouping()
{
/* 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.hasElements() && aGrouping[0])
return;
i18n::LanguageCountryInfo aLCInfo( getLanguageCountryInfo());
if (aLCInfo.Country.equalsIgnoreAsciiCase("IN") || // India
aLCInfo.Country.equalsIgnoreAsciiCase("BT") ) // Bhutan
{
aGrouping = { 3, 2, 0 };
}
else
{
aGrouping = { 3, 0, 0 };
}
}
const css::uno::Sequence< sal_Int32 >& LocaleDataWrapper::getDigitGrouping() const
{
return aGrouping;
}
// --- simple number formatting helpers -------------------------------
// The ImplAdd... methods are taken from class International and modified to
// suit the needs.
static void ImplAddUNum( OUStringBuffer& rBuf, 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--;
rBuf.append(*pTempBuf);
}
while ( pTempBuf != aTempBuf );
}
static void ImplAddUNum( OUStringBuffer& rBuf, 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 )
{
rBuf.append('0');
nMinLen--;
}
// copy temp buffer to real buffer
do
{
pTempBuf--;
rBuf.append(*pTempBuf);
}
while ( pTempBuf != aTempBuf );
}
static void ImplAddNum( OUStringBuffer& rBuf, sal_Int64 nNumber, int nMinLen )
{
if (nNumber < 0)
{
rBuf.append('-');
nNumber = -nNumber;
}
return ImplAddUNum( rBuf, nNumber, nMinLen);
}
static void ImplAdd2UNum( OUStringBuffer& rBuf, sal_uInt16 nNumber )
{
DBG_ASSERT( nNumber < 100, "ImplAdd2UNum() - Number >= 100" );
if ( nNumber < 10 )
{
rBuf.append('0');
rBuf.append(static_cast<char>(nNumber + '0'));
}
else
{
sal_uInt16 nTemp = nNumber % 10;
nNumber /= 10;
rBuf.append(static_cast<char>(nNumber + '0'));
rBuf.append(static_cast<char>(nTemp + '0'));
}
}
static void ImplAdd9UNum( OUStringBuffer& rBuf, 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();
rBuf.appendAscii(aStr.c_str(), aStr.size());
}
void LocaleDataWrapper::ImplAddFormatNum( OUStringBuffer& rBuf,
sal_Int64 nNumber, sal_uInt16 nDecimals, bool bUseThousandSep,
bool bTrailingZeros ) const
{
OUStringBuffer aNumBuf(64);
sal_uInt16 nNumLen;
// negative number
sal_uInt64 abs;
if ( nNumber < 0 )
{
// Avoid overflow, map -2^63 -> 2^63 explicitly:
abs = nNumber == std::numeric_limits<sal_Int64>::min()
? static_cast<sal_uInt64>(std::numeric_limits<sal_Int64>::min()) : nNumber * -1;
rBuf.append('-');
}
else
{
abs = nNumber;
}
// convert number
ImplAddUNum( aNumBuf, abs );
nNumLen = static_cast<sal_uInt16>(aNumBuf.getLength());
if ( nNumLen <= nDecimals )
{
// strip .0 in decimals?
if ( !nNumber && !bTrailingZeros )
{
rBuf.append('0');
}
else
{
// LeadingZero, insert 0
if ( isNumLeadingZero() )
{
rBuf.append('0');
}
// append decimal separator
rBuf.append( aLocaleDataItem.decimalSeparator );
// fill with zeros
sal_uInt16 i = 0;
while ( i < (nDecimals-nNumLen) )
{
rBuf.append('0');
i++;
}
// append decimals
rBuf.append(aNumBuf);
}
}
else
{
const OUString& rThoSep = aLocaleDataItem.thousandSeparator;
// copy number to buffer (excluding decimals)
sal_uInt16 nNumLen2 = nNumLen-nDecimals;
uno::Sequence< sal_Bool > aGroupPos;
if (bUseThousandSep)
aGroupPos = utl::DigitGroupingIterator::createForwardSequence(
nNumLen2, getDigitGrouping());
sal_uInt16 i = 0;
for (; i < nNumLen2; ++i )
{
rBuf.append(aNumBuf[i]);
// add thousand separator?
if ( bUseThousandSep && aGroupPos[i] )
rBuf.append( rThoSep );
}
// append decimals
if ( nDecimals )
{
rBuf.append( aLocaleDataItem.decimalSeparator );
bool bNullEnd = true;
while ( i < nNumLen )
{
if ( aNumBuf[i] != '0' )
bNullEnd = false;
rBuf.append(aNumBuf[i]);
i++;
}
// strip .0 in decimals?
if ( bNullEnd && !bTrailingZeros )
rBuf.setLength( rBuf.getLength() - (nDecimals + 1) );
}
}
}
// --- simple date and time formatting --------------------------------
OUString LocaleDataWrapper::getDate( const Date& rDate ) const
{
//!TODO: leading zeros et al
OUStringBuffer aBuf(128);
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 :
ImplAdd2UNum( aBuf, nDay );
aBuf.append( aLocaleDataItem.dateSeparator );
ImplAdd2UNum( aBuf, nMonth );
aBuf.append( aLocaleDataItem.dateSeparator );
ImplAddNum( aBuf, nYear, nYearLen );
break;
case DateOrder::MDY :
ImplAdd2UNum( aBuf, nMonth );
aBuf.append( aLocaleDataItem.dateSeparator );
ImplAdd2UNum( aBuf, nDay );
aBuf.append( aLocaleDataItem.dateSeparator );
ImplAddNum( aBuf, nYear, nYearLen );
break;
default:
ImplAddNum( aBuf, nYear, nYearLen );
aBuf.append( aLocaleDataItem.dateSeparator );
ImplAdd2UNum( aBuf, nMonth );
aBuf.append( aLocaleDataItem.dateSeparator );
ImplAdd2UNum( aBuf, nDay );
}
return aBuf.makeStringAndClear();
}
OUString LocaleDataWrapper::getTime( const tools::Time& rTime, bool bSec, bool b100Sec ) const
{
//!TODO: leading zeros et al
OUStringBuffer aBuf(128);
sal_uInt16 nHour = rTime.GetHour();
nHour %= 24;
ImplAdd2UNum( aBuf, nHour );
aBuf.append( aLocaleDataItem.timeSeparator );
ImplAdd2UNum( aBuf, rTime.GetMin() );
if ( bSec )
{
aBuf.append( aLocaleDataItem.timeSeparator );
ImplAdd2UNum( aBuf, rTime.GetSec() );
if ( b100Sec )
{
aBuf.append( aLocaleDataItem.time100SecSeparator );
ImplAdd9UNum( aBuf, rTime.GetNanoSec() );
}
}
return aBuf.makeStringAndClear();
}
OUString LocaleDataWrapper::getDuration( const tools::Duration& rDuration, bool bSec, bool b100Sec ) const
{
OUStringBuffer aBuf(128);
if ( rDuration.IsNegative() )
aBuf.append(' ');
sal_Int64 nHours = static_cast<sal_Int64>(rDuration.GetDays()) * 24 +
(rDuration.IsNegative() ?
-static_cast<sal_Int64>(rDuration.GetTime().GetHour()) :
rDuration.GetTime().GetHour());
if ( (true) /* IsTimeLeadingZero() */ )
ImplAddNum( aBuf, nHours, 2 );
else
ImplAddNum( aBuf, nHours, 1 );
aBuf.append( aLocaleDataItem.timeSeparator );
ImplAdd2UNum( aBuf, rDuration.GetTime().GetMin() );
if ( bSec )
{
aBuf.append( aLocaleDataItem.timeSeparator );
ImplAdd2UNum( aBuf, rDuration.GetTime().GetSec() );
if ( b100Sec )
{
aBuf.append( aLocaleDataItem.time100SecSeparator );
ImplAdd9UNum( aBuf, rDuration.GetTime().GetNanoSec() );
}
}
return aBuf.makeStringAndClear();
}
// --- simple number formatting ---------------------------------------
static size_t ImplGetNumberStringLengthGuess( const css::i18n::LocaleDataItem2& rLocaleDataItem, 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) * rLocaleDataItem.thousandSeparator.getLength()) + nDig) :
nDecimals) + rLocaleDataItem.decimalSeparator.getLength() + 3;
return nGuess;
}
OUString LocaleDataWrapper::getNum( sal_Int64 nNumber, sal_uInt16 nDecimals,
bool bUseThousandSep, bool bTrailingZeros ) const
{
// check if digits and separators will fit into fixed buffer or allocate
size_t nGuess = ImplGetNumberStringLengthGuess( aLocaleDataItem, nDecimals );
OUStringBuffer aBuf(int(nGuess + 16));
ImplAddFormatNum( aBuf, nNumber, nDecimals,
bUseThousandSep, bTrailingZeros );
return aBuf.makeStringAndClear();
}
OUString LocaleDataWrapper::getCurr( sal_Int64 nNumber, sal_uInt16 nDecimals,
std::u16string_view rCurrencySymbol, bool bUseThousandSep ) const
{
sal_Unicode cZeroChar = getCurrZeroChar();
// check if digits and separators will fit into fixed buffer or allocate
size_t nGuess = ImplGetNumberStringLengthGuess( aLocaleDataItem, nDecimals );
OUStringBuffer aNumBuf(sal_Int32(nGuess + 16));
bool bNeg;
if ( nNumber < 0 )
{
bNeg = true;
nNumber *= -1;
}
else
bNeg = false;
// convert number
ImplAddFormatNum( aNumBuf, nNumber, nDecimals,
bUseThousandSep, true );
const sal_Int32 nNumLen = aNumBuf.getLength();
// replace zeros with zero character
if ( (cZeroChar != '0') && nDecimals /* && IsNumTrailingZeros() */ )
{
sal_uInt16 i;
bool bZero = true;
sal_uInt16 nNumBufIndex = nNumLen-nDecimals;
i = 0;
do
{
if ( aNumBuf[nNumBufIndex] != '0' )
{
bZero = false;
break;
}
nNumBufIndex++;
i++;
}
while ( i < nDecimals );
if ( bZero )
{
nNumBufIndex = nNumLen-nDecimals;
i = 0;
do
{
aNumBuf[nNumBufIndex] = cZeroChar;
nNumBufIndex++;
i++;
}
while ( i < nDecimals );
}
}
OUString aCur;
if ( !bNeg )
{
switch( getCurrPositiveFormat() )
{
case 0:
aCur = rCurrencySymbol + aNumBuf;
break;
case 1:
aCur = aNumBuf + rCurrencySymbol;
break;
case 2:
aCur = OUString::Concat(rCurrencySymbol) + " " + aNumBuf;
break;
case 3:
aCur = aNumBuf + " " + rCurrencySymbol;
break;
}
}
else
{
switch( getCurrNegativeFormat() )
{
case 0:
aCur = OUString::Concat("(") + rCurrencySymbol + aNumBuf + ")";
break;
case 1:
aCur = OUString::Concat("-") + rCurrencySymbol + aNumBuf;
break;
case 2:
aCur = OUString::Concat(rCurrencySymbol) + "-" + aNumBuf;
break;
case 3:
aCur = rCurrencySymbol + aNumBuf + "-";
break;
case 4:
aCur = "(" + aNumBuf + rCurrencySymbol + ")";
break;
case 5:
aCur = "-" + aNumBuf + rCurrencySymbol;
break;
case 6:
aCur = aNumBuf + "-" + rCurrencySymbol;
break;
case 7:
aCur = aNumBuf + rCurrencySymbol + "-";
break;
case 8:
aCur = "-" + aNumBuf + " " + rCurrencySymbol;
break;
case 9:
aCur = OUString::Concat("-") + rCurrencySymbol + " " + aNumBuf;
break;
case 10:
aCur = aNumBuf + " " + rCurrencySymbol + "-";
break;
case 11:
aCur = OUString::Concat(rCurrencySymbol) + " -" + aNumBuf;
break;
case 12:
aCur = OUString::Concat(rCurrencySymbol) + " " + aNumBuf + "-";
break;
case 13:
aCur = aNumBuf + "- " + rCurrencySymbol;
break;
case 14:
aCur = OUString::Concat("(") + rCurrencySymbol + " " + aNumBuf + ")";
break;
case 15:
aCur = "(" + aNumBuf + " " + rCurrencySymbol + ")";
break;
}
}
return aCur;
}
// --- number parsing -------------------------------------------------
double LocaleDataWrapper::stringToDouble( std::u16string_view aString, bool bUseGroupSep,
rtl_math_ConversionStatus* pStatus, sal_Int32* pParseEnd ) const
{
const sal_Unicode* pParseEndChar;
double fValue = stringToDouble(aString.data(), aString.data() + aString.size(), bUseGroupSep, pStatus, &pParseEndChar);
if (pParseEnd)
*pParseEnd = pParseEndChar - aString.data();
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 ? aLocaleDataItem.thousandSeparator[0] : 0);
rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok;
const sal_Unicode* pParseEnd = nullptr;
double fValue = rtl_math_uStringToDouble( pBegin, pEnd, aLocaleDataItem.decimalSeparator[0], cGroupSep, &eStatus, &pParseEnd);
bool bTryAlt = (pParseEnd < pEnd && !aLocaleDataItem.decimalSeparatorAlternative.isEmpty() &&
*pParseEnd == aLocaleDataItem.decimalSeparatorAlternative.toChar());
// Try re-parsing with alternative if that was the reason to stop.
if (bTryAlt)
fValue = rtl_math_uStringToDouble( pBegin, pEnd, aLocaleDataItem.decimalSeparatorAlternative.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(std::u16string_view rDebugMsg) const
{
LanguageTag aLoaded = getLoadedLanguageTag();
return OUString::Concat(rDebugMsg) + "\n" + maLanguageTag.getBcp47() + " requested\n"
+ aLoaded.getBcp47() + " loaded";
}
// static
void LocaleDataWrapper::outputCheckMessage( std::u16string_view 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&)
{
TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllCalendars" );
}
return {};
}
// --- XLocaleData4 ----------------------------------------------------------
const css::uno::Sequence< OUString > & LocaleDataWrapper::getDateAcceptancePatterns() const
{
return aDateAcceptancePatterns;
}
// --- Override layer --------------------------------------------------------
void LocaleDataWrapper::loadDateAcceptancePatterns(
const std::vector<OUString> & rPatterns )
{
if (!aDateAcceptancePatterns.hasElements() || rPatterns.empty())
{
try
{
aDateAcceptancePatterns = xLD->getDateAcceptancePatterns( maLanguageTag.getLocale() );
}
catch (const Exception&)
{
TOOLS_WARN_EXCEPTION( "unotools.i18n", "setDateAcceptancePatterns" );
}
if (rPatterns.empty())
return; // just a reset
if (!aDateAcceptancePatterns.hasElements())
{
aDateAcceptancePatterns = comphelper::containerToSequence(rPatterns);
return;
}
}
// Earlier versions checked for presence of the full date pattern with
// aDateAcceptancePatterns[0] == rPatterns[0] and prepended that if not.
// This lead to confusion if the patterns were intentionally specified
// without, giving entirely a different DMY order, see tdf#150288.
// Not checking this and accepting the given patterns as is may result in
// the user shooting themself in the foot, but we can't have both.
aDateAcceptancePatterns = comphelper::containerToSequence(rPatterns);
}
/* 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 'append' 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 'append' 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 'append' is required to be utilized.
↑ V530 The return value of function 'appendAscii' 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 'append' 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 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V547 Expression '!"unhandled LongDateOrder to DateOrder"' is always false.
↑ V560 A part of conditional expression is always false: (cZeroChar != '0').