Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / unotools / source / i18n / localedatawrapper.cxx
blob56524372e21fd11a04b1e8e8abe563ca7ed2ba99
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <limits>
21 #include <stdio.h>
22 #include <string>
24 #include <sal/log.hxx>
25 #include <unotools/localedatawrapper.hxx>
26 #include <unotools/digitgroupingiterator.hxx>
27 #include <comphelper/diagnose_ex.hxx>
28 #include <tools/debug.hxx>
29 #include <i18nlangtag/languagetag.hxx>
30 #include <o3tl/safeint.hxx>
32 #include <com/sun/star/i18n/KNumberFormatUsage.hpp>
33 #include <com/sun/star/i18n/KNumberFormatType.hpp>
34 #include <com/sun/star/i18n/LocaleData2.hpp>
35 #include <com/sun/star/i18n/NumberFormatIndex.hpp>
36 #include <com/sun/star/i18n/NumberFormatMapper.hpp>
38 #include <comphelper/processfactory.hxx>
39 #include <comphelper/sequence.hxx>
40 #include <rtl/ustrbuf.hxx>
41 #include <rtl/math.hxx>
42 #include <tools/date.hxx>
43 #include <tools/time.hxx>
44 #include <o3tl/string_view.hxx>
45 #include <utility>
47 const sal_uInt16 nCurrFormatDefault = 0;
49 using namespace ::com::sun::star;
50 using namespace ::com::sun::star::i18n;
51 using namespace ::com::sun::star::uno;
53 namespace
55 uno::Sequence< lang::Locale > gInstalledLocales;
56 std::vector< LanguageType > gInstalledLanguageTypes;
59 sal_uInt8 LocaleDataWrapper::nLocaleDataChecking = 0;
61 LocaleDataWrapper::LocaleDataWrapper(
62 const Reference< uno::XComponentContext > & rxContext,
63 LanguageTag aLanguageTag
66 m_xContext( rxContext ),
67 xLD( LocaleData2::create(rxContext) ),
68 maLanguageTag(std::move( aLanguageTag ))
70 loadData();
71 loadDateAcceptancePatterns({});
74 LocaleDataWrapper::LocaleDataWrapper(
75 LanguageTag aLanguageTag,
76 const std::vector<OUString> & rOverrideDateAcceptancePatterns
79 m_xContext( comphelper::getProcessComponentContext() ),
80 xLD( LocaleData2::create(m_xContext) ),
81 maLanguageTag(std::move( aLanguageTag ))
83 loadData();
84 loadDateAcceptancePatterns(rOverrideDateAcceptancePatterns);
87 LocaleDataWrapper::~LocaleDataWrapper()
91 const LanguageTag& LocaleDataWrapper::getLanguageTag() const
93 return maLanguageTag;
96 const css::lang::Locale& LocaleDataWrapper::getMyLocale() const
98 return maLanguageTag.getLocale();
101 void LocaleDataWrapper::loadData()
103 const css::lang::Locale& rMyLocale = maLanguageTag.getLocale();
106 const Sequence< Currency2 > aCurrSeq = getAllCurrencies();
107 if ( !aCurrSeq.hasElements() )
109 if (areChecksEnabled())
110 outputCheckMessage("LocaleDataWrapper::getCurrSymbolsImpl: no currency at all, using ShellsAndPebbles");
111 aCurrSymbol = "ShellsAndPebbles";
112 aCurrBankSymbol = aCurrSymbol;
113 nCurrPositiveFormat = nCurrNegativeFormat = nCurrFormatDefault;
114 nCurrDigits = 2;
116 else
118 auto pCurr = std::find_if(aCurrSeq.begin(), aCurrSeq.end(),
119 [](const Currency2& rCurr) { return rCurr.Default; });
120 if ( pCurr == aCurrSeq.end() )
122 if (areChecksEnabled())
124 outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrSymbolsImpl: no default currency" ) );
126 pCurr = aCurrSeq.begin();
128 aCurrSymbol = pCurr->Symbol;
129 aCurrBankSymbol = pCurr->BankSymbol;
130 nCurrDigits = pCurr->DecimalPlaces;
134 loadCurrencyFormats();
137 xDefaultCalendar.reset();
138 xSecondaryCalendar.reset();
139 const Sequence< Calendar2 > xCals = getAllCalendars();
140 if (xCals.getLength() > 1)
142 auto pCal = std::find_if(xCals.begin(), xCals.end(),
143 [](const Calendar2& rCal) { return !rCal.Default; });
144 if (pCal != xCals.end())
145 xSecondaryCalendar = std::make_shared<Calendar2>( *pCal);
147 auto pCal = xCals.begin();
148 if (xCals.getLength() > 1)
150 pCal = std::find_if(xCals.begin(), xCals.end(),
151 [](const Calendar2& rCal) { return rCal.Default; });
152 if (pCal == xCals.end())
153 pCal = xCals.begin();
155 xDefaultCalendar = std::make_shared<Calendar2>( *pCal);
158 loadDateOrders();
162 aDateAcceptancePatterns = xLD->getDateAcceptancePatterns( rMyLocale );
164 catch (const Exception&)
166 TOOLS_WARN_EXCEPTION( "unotools.i18n", "getDateAcceptancePatterns" );
167 aDateAcceptancePatterns = {};
171 loadDigitGrouping();
175 aReservedWords = comphelper::sequenceToContainer<std::vector<OUString>>(xLD->getReservedWord( rMyLocale ));
177 catch ( const Exception& )
179 TOOLS_WARN_EXCEPTION( "unotools.i18n", "getReservedWord" );
184 aLocaleDataItem = xLD->getLocaleItem2( rMyLocale );
186 catch (const Exception&)
188 TOOLS_WARN_EXCEPTION( "unotools.i18n", "getLocaleItem" );
189 static const css::i18n::LocaleDataItem2 aEmptyItem;
190 aLocaleDataItem = aEmptyItem;
193 aLocaleItem[LocaleItem::DATE_SEPARATOR] = aLocaleDataItem.dateSeparator;
194 aLocaleItem[LocaleItem::THOUSAND_SEPARATOR] = aLocaleDataItem.thousandSeparator;
195 aLocaleItem[LocaleItem::DECIMAL_SEPARATOR] = aLocaleDataItem.decimalSeparator;
196 aLocaleItem[LocaleItem::TIME_SEPARATOR] = aLocaleDataItem.timeSeparator;
197 aLocaleItem[LocaleItem::TIME_100SEC_SEPARATOR] = aLocaleDataItem.time100SecSeparator;
198 aLocaleItem[LocaleItem::LIST_SEPARATOR] = aLocaleDataItem.listSeparator;
199 aLocaleItem[LocaleItem::SINGLE_QUOTATION_START] = aLocaleDataItem.quotationStart;
200 aLocaleItem[LocaleItem::SINGLE_QUOTATION_END] = aLocaleDataItem.quotationEnd;
201 aLocaleItem[LocaleItem::DOUBLE_QUOTATION_START] = aLocaleDataItem.doubleQuotationStart;
202 aLocaleItem[LocaleItem::DOUBLE_QUOTATION_END] = aLocaleDataItem.doubleQuotationEnd;
203 aLocaleItem[LocaleItem::MEASUREMENT_SYSTEM] = aLocaleDataItem.measurementSystem;
204 aLocaleItem[LocaleItem::TIME_AM] = aLocaleDataItem.timeAM;
205 aLocaleItem[LocaleItem::TIME_PM] = aLocaleDataItem.timePM;
206 aLocaleItem[LocaleItem::LONG_DATE_DAY_OF_WEEK_SEPARATOR] = aLocaleDataItem.LongDateDayOfWeekSeparator;
207 aLocaleItem[LocaleItem::LONG_DATE_DAY_SEPARATOR] = aLocaleDataItem.LongDateDaySeparator;
208 aLocaleItem[LocaleItem::LONG_DATE_MONTH_SEPARATOR] = aLocaleDataItem.LongDateMonthSeparator;
209 aLocaleItem[LocaleItem::LONG_DATE_YEAR_SEPARATOR] = aLocaleDataItem.LongDateYearSeparator;
210 aLocaleItem[LocaleItem::DECIMAL_SEPARATOR_ALTERNATIVE] = aLocaleDataItem.decimalSeparatorAlternative;
213 /* FIXME-BCP47: locale data should provide a language tag instead that could be
214 * passed on. */
215 css::i18n::LanguageCountryInfo LocaleDataWrapper::getLanguageCountryInfo() const
219 return xLD->getLanguageCountryInfo( getMyLocale() );
221 catch (const Exception&)
223 TOOLS_WARN_EXCEPTION( "unotools.i18n", "getLanguageCountryInfo" );
225 return css::i18n::LanguageCountryInfo();
228 const css::i18n::LocaleDataItem2& LocaleDataWrapper::getLocaleItem() const
230 return aLocaleDataItem;
233 css::uno::Sequence< css::i18n::Currency2 > LocaleDataWrapper::getAllCurrencies() const
237 return xLD->getAllCurrencies2( getMyLocale() );
239 catch (const Exception&)
241 TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllCurrencies" );
243 return {};
246 css::uno::Sequence< css::i18n::FormatElement > LocaleDataWrapper::getAllFormats() const
250 return xLD->getAllFormats( getMyLocale() );
252 catch (const Exception&)
254 TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllFormats" );
256 return {};
259 css::i18n::ForbiddenCharacters LocaleDataWrapper::getForbiddenCharacters() const
263 return xLD->getForbiddenCharacters( getMyLocale() );
265 catch (const Exception&)
267 TOOLS_WARN_EXCEPTION( "unotools.i18n", "getForbiddenCharacters" );
269 return css::i18n::ForbiddenCharacters();
272 const css::uno::Sequence< css::lang::Locale > & LocaleDataWrapper::getAllInstalledLocaleNames() const
274 uno::Sequence< lang::Locale > &rInstalledLocales = gInstalledLocales;
276 if ( rInstalledLocales.hasElements() )
277 return rInstalledLocales;
281 rInstalledLocales = xLD->getAllInstalledLocaleNames();
283 catch ( const Exception& )
285 TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllInstalledLocaleNames" );
287 return rInstalledLocales;
290 // --- Impl and helpers ----------------------------------------------------
292 // static
293 const css::uno::Sequence< css::lang::Locale >& LocaleDataWrapper::getInstalledLocaleNames()
295 const uno::Sequence< lang::Locale > &rInstalledLocales = gInstalledLocales;
297 if ( !rInstalledLocales.hasElements() )
299 LocaleDataWrapper aLDW( ::comphelper::getProcessComponentContext(), LanguageTag( LANGUAGE_SYSTEM) );
300 aLDW.getAllInstalledLocaleNames();
302 return rInstalledLocales;
305 // static
306 const std::vector< LanguageType >& LocaleDataWrapper::getInstalledLanguageTypes()
308 std::vector< LanguageType > &rInstalledLanguageTypes = gInstalledLanguageTypes;
310 if ( !rInstalledLanguageTypes.empty() )
311 return rInstalledLanguageTypes;
313 const css::uno::Sequence< css::lang::Locale > xLoc = getInstalledLocaleNames();
314 sal_Int32 nCount = xLoc.getLength();
315 std::vector< LanguageType > xLang;
316 xLang.reserve(nCount);
317 for ( const auto& rLoc : xLoc )
319 LanguageTag aLanguageTag( rLoc );
320 OUString aDebugLocale;
321 if (areChecksEnabled())
323 aDebugLocale = aLanguageTag.getBcp47( false);
326 LanguageType eLang = aLanguageTag.getLanguageType( false);
327 if (areChecksEnabled() && eLang == LANGUAGE_DONTKNOW)
329 OUString aMsg = "ConvertIsoNamesToLanguage: unknown MS-LCID for locale\n" +
330 aDebugLocale;
331 outputCheckMessage(aMsg);
334 if ( eLang == LANGUAGE_NORWEGIAN) // no_NO, not Bokmal (nb_NO), not Nynorsk (nn_NO)
335 eLang = LANGUAGE_DONTKNOW; // don't offer "Unknown" language
336 if ( eLang != LANGUAGE_DONTKNOW )
338 LanguageTag aBackLanguageTag( eLang);
339 if ( aLanguageTag != aBackLanguageTag )
341 // In checks, exclude known problems because no MS-LCID defined
342 // and default for Language found.
343 if ( areChecksEnabled()
344 && aDebugLocale != "ar-SD" // Sudan/ar
345 && aDebugLocale != "en-CB" // Caribbean is not a country
346 // && aDebugLocale != "en-BG" // ?!? Bulgaria/en
347 // && aDebugLocale != "es-BR" // ?!? Brazil/es
350 outputCheckMessage(Concat2View(
351 "ConvertIsoNamesToLanguage/ConvertLanguageToIsoNames: ambiguous locale (MS-LCID?)\n"
352 + aDebugLocale
353 + " -> 0x"
354 + OUString::number(static_cast<sal_Int32>(static_cast<sal_uInt16>(eLang)), 16)
355 + " -> "
356 + aBackLanguageTag.getBcp47() ));
358 eLang = LANGUAGE_DONTKNOW;
361 if ( eLang != LANGUAGE_DONTKNOW )
362 xLang.push_back(eLang);
364 rInstalledLanguageTypes = xLang;
366 return rInstalledLanguageTypes;
369 const OUString& LocaleDataWrapper::getOneLocaleItem( sal_Int16 nItem ) const
371 if ( nItem >= LocaleItem::COUNT2 )
373 SAL_WARN( "unotools.i18n", "getOneLocaleItem: bounds" );
374 return aLocaleItem[0];
376 return aLocaleItem[nItem];
379 const OUString& LocaleDataWrapper::getOneReservedWord( sal_Int16 nWord ) const
381 if ( nWord < 0 || o3tl::make_unsigned(nWord) >= aReservedWords.size() )
383 SAL_WARN( "unotools.i18n", "getOneReservedWord: bounds" );
384 static const OUString EMPTY;
385 return EMPTY;
387 return aReservedWords[nWord];
390 MeasurementSystem LocaleDataWrapper::mapMeasurementStringToEnum( std::u16string_view rMS ) const
392 //! TODO: could be cached too
393 if ( o3tl::equalsIgnoreAsciiCase( rMS, u"metric" ) )
394 return MeasurementSystem::Metric;
395 //! TODO: other measurement systems? => extend enum MeasurementSystem
396 return MeasurementSystem::US;
399 bool LocaleDataWrapper::doesSecondaryCalendarUseEC( std::u16string_view rName ) const
401 if (rName.empty())
402 return false;
404 // Check language tag first to avoid loading all calendars of this locale.
405 LanguageTag aLoaded( getLoadedLanguageTag());
406 const OUString& aBcp47( aLoaded.getBcp47());
407 // So far determine only by locale, we know for a few.
408 /* TODO: check date format codes? or add to locale data? */
409 if ( aBcp47 != "ja-JP" &&
410 aBcp47 != "lo-LA" &&
411 aBcp47 != "zh-TW")
412 return false;
414 if (!xSecondaryCalendar)
415 return false;
416 if (!xSecondaryCalendar->Name.equalsIgnoreAsciiCase( rName))
417 return false;
419 return true;
422 const std::shared_ptr< css::i18n::Calendar2 >& LocaleDataWrapper::getDefaultCalendar() const
424 return xDefaultCalendar;
427 css::uno::Sequence< css::i18n::CalendarItem2 > const & LocaleDataWrapper::getDefaultCalendarDays() const
429 return getDefaultCalendar()->Days;
432 css::uno::Sequence< css::i18n::CalendarItem2 > const & LocaleDataWrapper::getDefaultCalendarMonths() const
434 return getDefaultCalendar()->Months;
437 // --- currencies -----------------------------------------------------
439 const OUString& LocaleDataWrapper::getCurrSymbol() const
441 return aCurrSymbol;
444 const OUString& LocaleDataWrapper::getCurrBankSymbol() const
446 return aCurrBankSymbol;
449 sal_uInt16 LocaleDataWrapper::getCurrPositiveFormat() const
451 return nCurrPositiveFormat;
454 sal_uInt16 LocaleDataWrapper::getCurrNegativeFormat() const
456 return nCurrNegativeFormat;
459 sal_uInt16 LocaleDataWrapper::getCurrDigits() const
461 return nCurrDigits;
464 void LocaleDataWrapper::scanCurrFormatImpl( std::u16string_view rCode,
465 sal_Int32 nStart, sal_Int32& nSign, sal_Int32& nPar,
466 sal_Int32& nNum, sal_Int32& nBlank, sal_Int32& nSym ) const
468 nSign = nPar = nNum = nBlank = nSym = -1;
469 const sal_Unicode* const pStr = rCode.data();
470 const sal_Unicode* const pStop = pStr + rCode.size();
471 const sal_Unicode* p = pStr + nStart;
472 int nInSection = 0;
473 bool bQuote = false;
474 while ( p < pStop )
476 if ( bQuote )
478 if ( *p == '"' && *(p-1) != '\\' )
479 bQuote = false;
481 else
483 switch ( *p )
485 case '"' :
486 if ( pStr == p || *(p-1) != '\\' )
487 bQuote = true;
488 break;
489 case '-' :
490 if (!nInSection && nSign == -1)
491 nSign = p - pStr;
492 break;
493 case '(' :
494 if (!nInSection && nPar == -1)
495 nPar = p - pStr;
496 break;
497 case '0' :
498 case '#' :
499 if (!nInSection && nNum == -1)
500 nNum = p - pStr;
501 break;
502 case '[' :
503 nInSection++;
504 break;
505 case ']' :
506 if ( nInSection )
508 nInSection--;
509 if (!nInSection && nBlank == -1
510 && nSym != -1 && p < pStop-1 && *(p+1) == ' ' )
511 nBlank = p - pStr + 1;
513 break;
514 case '$' :
515 if (nSym == -1 && nInSection && *(p-1) == '[')
517 nSym = p - pStr + 1;
518 if (nNum != -1 && *(p-2) == ' ')
519 nBlank = p - pStr - 2;
521 break;
522 case ';' :
523 if ( !nInSection )
524 p = pStop;
525 break;
526 default:
527 if (!nInSection && nSym == -1 && o3tl::starts_with(rCode.substr(static_cast<sal_Int32>(p - pStr)), aCurrSymbol))
528 { // currency symbol not surrounded by [$...]
529 nSym = p - pStr;
530 if (nBlank == -1 && pStr < p && *(p-1) == ' ')
531 nBlank = p - pStr - 1;
532 p += aCurrSymbol.getLength() - 1;
533 if (nBlank == -1 && p < pStop-2 && *(p+2) == ' ')
534 nBlank = p - pStr + 2;
538 p++;
542 void LocaleDataWrapper::loadCurrencyFormats()
544 css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext );
545 uno::Sequence< NumberFormatCode > aFormatSeq = xNFC->getAllFormatCode( KNumberFormatUsage::CURRENCY, maLanguageTag.getLocale() );
546 sal_Int32 nCnt = aFormatSeq.getLength();
547 if ( !nCnt )
548 { // bad luck
549 if (areChecksEnabled())
551 outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrFormatsImpl: no currency formats" ) );
553 nCurrPositiveFormat = nCurrNegativeFormat = nCurrFormatDefault;
554 return;
556 // find a negative code (medium preferred) and a default (medium preferred) (not necessarily the same)
557 NumberFormatCode const * const pFormatArr = aFormatSeq.getArray();
558 sal_Int32 nElem, nDef, nNeg, nMedium;
559 nDef = nNeg = nMedium = -1;
560 for ( nElem = 0; nElem < nCnt; nElem++ )
562 if ( pFormatArr[nElem].Type == KNumberFormatType::MEDIUM )
564 if ( pFormatArr[nElem].Default )
566 nDef = nElem;
567 nMedium = nElem;
568 if ( pFormatArr[nElem].Code.indexOf( ';' ) >= 0 )
569 nNeg = nElem;
571 else
573 if ( (nNeg == -1 || nMedium == -1) && pFormatArr[nElem].Code.indexOf( ';' ) >= 0 )
574 nNeg = nElem;
575 if ( nMedium == -1 )
576 nMedium = nElem;
579 else
581 if ( nDef == -1 && pFormatArr[nElem].Default )
582 nDef = nElem;
583 if ( nNeg == -1 && pFormatArr[nElem].Code.indexOf( ';' ) >= 0 )
584 nNeg = nElem;
588 sal_Int32 nSign, nPar, nNum, nBlank, nSym;
590 // positive format
591 nElem = (nDef >= 0 ? nDef : (nNeg >= 0 ? nNeg : 0));
592 scanCurrFormatImpl( pFormatArr[nElem].Code, 0, nSign, nPar, nNum, nBlank, nSym );
593 if (areChecksEnabled() && (nNum == -1 || nSym == -1))
595 outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrFormatsImpl: CurrPositiveFormat?" ) );
597 if (nBlank == -1)
599 if ( nSym < nNum )
600 nCurrPositiveFormat = 0; // $1
601 else
602 nCurrPositiveFormat = 1; // 1$
604 else
606 if ( nSym < nNum )
607 nCurrPositiveFormat = 2; // $ 1
608 else
609 nCurrPositiveFormat = 3; // 1 $
612 // negative format
613 if ( nNeg < 0 )
614 nCurrNegativeFormat = nCurrFormatDefault;
615 else
617 const OUString& rCode = pFormatArr[nNeg].Code;
618 sal_Int32 nDelim = rCode.indexOf(';');
619 scanCurrFormatImpl( rCode, nDelim+1, nSign, nPar, nNum, nBlank, nSym );
620 if (areChecksEnabled() && (nNum == -1 || nSym == -1 || (nPar == -1 && nSign == -1)))
622 outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrFormatsImpl: CurrNegativeFormat?" ) );
624 // NOTE: one of nPar or nSign are allowed to be -1
625 if (nBlank == -1)
627 if ( nSym < nNum )
629 if ( -1 < nPar && nPar < nSym )
630 nCurrNegativeFormat = 0; // ($1)
631 else if ( -1 < nSign && nSign < nSym )
632 nCurrNegativeFormat = 1; // -$1
633 else if ( nNum < nSign )
634 nCurrNegativeFormat = 3; // $1-
635 else
636 nCurrNegativeFormat = 2; // $-1
638 else
640 if ( -1 < nPar && nPar < nNum )
641 nCurrNegativeFormat = 4; // (1$)
642 else if ( -1 < nSign && nSign < nNum )
643 nCurrNegativeFormat = 5; // -1$
644 else if ( nSym < nSign )
645 nCurrNegativeFormat = 7; // 1$-
646 else
647 nCurrNegativeFormat = 6; // 1-$
650 else
652 if ( nSym < nNum )
654 if ( -1 < nPar && nPar < nSym )
655 nCurrNegativeFormat = 14; // ($ 1)
656 else if ( -1 < nSign && nSign < nSym )
657 nCurrNegativeFormat = 9; // -$ 1
658 else if ( nNum < nSign )
659 nCurrNegativeFormat = 12; // $ 1-
660 else
661 nCurrNegativeFormat = 11; // $ -1
663 else
665 if ( -1 < nPar && nPar < nNum )
666 nCurrNegativeFormat = 15; // (1 $)
667 else if ( -1 < nSign && nSign < nNum )
668 nCurrNegativeFormat = 8; // -1 $
669 else if ( nSym < nSign )
670 nCurrNegativeFormat = 10; // 1 $-
671 else
672 nCurrNegativeFormat = 13; // 1- $
678 // --- date -----------------------------------------------------------
680 DateOrder LocaleDataWrapper::getDateOrder() const
682 return nDateOrder;
685 LongDateOrder LocaleDataWrapper::getLongDateOrder() const
687 return nLongDateOrder;
690 LongDateOrder LocaleDataWrapper::scanDateOrderImpl( std::u16string_view rCode ) const
692 // Only some european versions were translated, the ones with different
693 // keyword combinations are:
694 // English DMY, German TMJ, Spanish DMA, French JMA, Italian GMA,
695 // Dutch DMJ, Finnish PKV
697 // default is English keywords for every other language
698 size_t nDay = rCode.find('D');
699 size_t nMonth = rCode.find('M');
700 size_t nYear = rCode.find('Y');
701 if (nDay == std::u16string_view::npos || nMonth == std::u16string_view::npos || nYear == std::u16string_view::npos)
702 { // This algorithm assumes that all three parts (DMY) are present
703 if (nMonth == std::u16string_view::npos)
704 { // only Finnish has something else than 'M' for month
705 nMonth = rCode.find('K');
706 if (nMonth != std::u16string_view::npos)
708 nDay = rCode.find('P');
709 nYear = rCode.find('V');
712 else if (nDay == std::u16string_view::npos)
713 { // We have a month 'M' if we reach this branch.
714 // Possible languages containing 'M' but no 'D':
715 // German, French, Italian
716 nDay = rCode.find('T'); // German
717 if (nDay != std::u16string_view::npos)
718 nYear = rCode.find('J');
719 else
721 nYear = rCode.find('A'); // French, Italian
722 if (nYear != std::u16string_view::npos)
724 nDay = rCode.find('J'); // French
725 if (nDay == std::u16string_view::npos)
726 nDay = rCode.find('G'); // Italian
730 else
731 { // We have a month 'M' and a day 'D'.
732 // Possible languages containing 'D' and 'M' but not 'Y':
733 // Spanish, Dutch
734 nYear = rCode.find('A'); // Spanish
735 if (nYear == std::u16string_view::npos)
736 nYear = rCode.find('J'); // Dutch
738 if (nDay == std::u16string_view::npos || nMonth == std::u16string_view::npos || nYear == std::u16string_view::npos)
740 if (areChecksEnabled())
742 outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::scanDateOrder: not all DMY present" ) );
744 if (nDay == std::u16string_view::npos)
745 nDay = rCode.size();
746 if (nMonth == std::u16string_view::npos)
747 nMonth = rCode.size();
748 if (nYear == std::u16string_view::npos)
749 nYear = rCode.size();
752 // compare with <= because each position may equal rCode.getLength()
753 if ( nDay <= nMonth && nMonth <= nYear )
754 return LongDateOrder::DMY; // also if every position equals rCode.getLength()
755 else if ( nMonth <= nDay && nDay <= nYear )
756 return LongDateOrder::MDY;
757 else if ( nYear <= nMonth && nMonth <= nDay )
758 return LongDateOrder::YMD;
759 else if ( nYear <= nDay && nDay <= nMonth )
760 return LongDateOrder::YDM;
761 else
763 if (areChecksEnabled())
765 outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::scanDateOrder: no magic applicable" ) );
767 return LongDateOrder::DMY;
771 static DateOrder getDateOrderFromLongDateOrder( LongDateOrder eLong )
773 switch (eLong)
775 case LongDateOrder::YMD:
776 return DateOrder::YMD;
777 break;
778 case LongDateOrder::DMY:
779 return DateOrder::DMY;
780 break;
781 case LongDateOrder::MDY:
782 return DateOrder::MDY;
783 break;
784 case LongDateOrder::YDM:
785 default:
786 assert(!"unhandled LongDateOrder to DateOrder");
787 return DateOrder::DMY;
791 void LocaleDataWrapper::loadDateOrders()
793 css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext );
794 uno::Sequence< NumberFormatCode > aFormatSeq = xNFC->getAllFormatCode( KNumberFormatUsage::DATE, maLanguageTag.getLocale() );
795 sal_Int32 nCnt = aFormatSeq.getLength();
796 if ( !nCnt )
797 { // bad luck
798 if (areChecksEnabled())
800 outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getDateOrdersImpl: no date formats" ) );
802 nDateOrder = DateOrder::DMY;
803 nLongDateOrder = LongDateOrder::DMY;
804 return;
806 // find the edit (21), a default (medium preferred),
807 // a medium (default preferred), and a long (default preferred)
808 NumberFormatCode const * const pFormatArr = aFormatSeq.getArray();
809 sal_Int32 nEdit, nDef, nMedium, nLong;
810 nEdit = nDef = nMedium = nLong = -1;
811 for ( sal_Int32 nElem = 0; nElem < nCnt; nElem++ )
813 if ( nEdit == -1 && pFormatArr[nElem].Index == NumberFormatIndex::DATE_SYS_DDMMYYYY )
814 nEdit = nElem;
815 if ( nDef == -1 && pFormatArr[nElem].Default )
816 nDef = nElem;
817 switch ( pFormatArr[nElem].Type )
819 case KNumberFormatType::MEDIUM :
821 if ( pFormatArr[nElem].Default )
823 nDef = nElem;
824 nMedium = nElem;
826 else if ( nMedium == -1 )
827 nMedium = nElem;
829 break;
830 case KNumberFormatType::LONG :
832 if ( pFormatArr[nElem].Default )
833 nLong = nElem;
834 else if ( nLong == -1 )
835 nLong = nElem;
837 break;
840 if ( nEdit == -1 )
842 if (areChecksEnabled())
844 outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getDateOrdersImpl: no edit" ) );
846 if ( nDef == -1 )
848 if (areChecksEnabled())
850 outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getDateOrdersImpl: no default" ) );
852 if ( nMedium != -1 )
853 nDef = nMedium;
854 else if ( nLong != -1 )
855 nDef = nLong;
856 else
857 nDef = 0;
859 nEdit = nDef;
861 LongDateOrder nDO = scanDateOrderImpl( pFormatArr[nEdit].Code );
862 if ( pFormatArr[nEdit].Type == KNumberFormatType::LONG )
863 { // normally this is not the case
864 nLongDateOrder = nDO;
865 nDateOrder = getDateOrderFromLongDateOrder(nDO);
867 else
869 // YDM should not occur in a short/medium date (i.e. no locale has
870 // that) and is nowhere handled.
871 nDateOrder = getDateOrderFromLongDateOrder(nDO);
872 if ( nLong == -1 )
873 nLongDateOrder = nDO;
874 else
875 nLongDateOrder = scanDateOrderImpl( pFormatArr[nLong].Code );
879 // --- digit grouping -------------------------------------------------
881 void LocaleDataWrapper::loadDigitGrouping()
883 /* TODO: This is a very simplified grouping setup that only serves its
884 * current purpose for Indian locales. A free-form flexible one would
885 * obtain grouping from locale data where it could be specified using, for
886 * example, codes like #,### and #,##,### that would generate the integer
887 * sequence. Needed additional API and a locale data element.
890 if (aGrouping.hasElements() && aGrouping[0])
891 return;
893 i18n::LanguageCountryInfo aLCInfo( getLanguageCountryInfo());
894 if (aLCInfo.Country.equalsIgnoreAsciiCase("IN") || // India
895 aLCInfo.Country.equalsIgnoreAsciiCase("BT") ) // Bhutan
897 aGrouping = { 3, 2, 0 };
899 else
901 aGrouping = { 3, 0, 0 };
905 const css::uno::Sequence< sal_Int32 >& LocaleDataWrapper::getDigitGrouping() const
907 return aGrouping;
910 // --- simple number formatting helpers -------------------------------
912 // The ImplAdd... methods are taken from class International and modified to
913 // suit the needs.
915 static void ImplAddUNum( OUStringBuffer& rBuf, sal_uInt64 nNumber )
917 // fill temp buffer with digits
918 sal_Unicode aTempBuf[64];
919 sal_Unicode* pTempBuf = aTempBuf;
922 *pTempBuf = static_cast<sal_Unicode>(nNumber % 10) + '0';
923 pTempBuf++;
924 nNumber /= 10;
926 while ( nNumber );
928 // copy temp buffer to buffer passed
931 pTempBuf--;
932 rBuf.append(*pTempBuf);
934 while ( pTempBuf != aTempBuf );
937 static void ImplAddUNum( OUStringBuffer& rBuf, sal_uInt64 nNumber, int nMinLen )
939 // fill temp buffer with digits
940 sal_Unicode aTempBuf[64];
941 sal_Unicode* pTempBuf = aTempBuf;
944 *pTempBuf = static_cast<sal_Unicode>(nNumber % 10) + '0';
945 pTempBuf++;
946 nNumber /= 10;
947 nMinLen--;
949 while ( nNumber );
951 // fill with zeros up to the minimal length
952 while ( nMinLen > 0 )
954 rBuf.append('0');
955 nMinLen--;
958 // copy temp buffer to real buffer
961 pTempBuf--;
962 rBuf.append(*pTempBuf);
964 while ( pTempBuf != aTempBuf );
967 static void ImplAddNum( OUStringBuffer& rBuf, sal_Int64 nNumber, int nMinLen )
969 if (nNumber < 0)
971 rBuf.append('-');
972 nNumber = -nNumber;
974 return ImplAddUNum( rBuf, nNumber, nMinLen);
977 static void ImplAdd2UNum( OUStringBuffer& rBuf, sal_uInt16 nNumber )
979 DBG_ASSERT( nNumber < 100, "ImplAdd2UNum() - Number >= 100" );
981 if ( nNumber < 10 )
983 rBuf.append('0');
984 rBuf.append(static_cast<char>(nNumber + '0'));
986 else
988 sal_uInt16 nTemp = nNumber % 10;
989 nNumber /= 10;
990 rBuf.append(static_cast<char>(nNumber + '0'));
991 rBuf.append(static_cast<char>(nTemp + '0'));
995 static void ImplAdd9UNum( OUStringBuffer& rBuf, sal_uInt32 nNumber )
997 DBG_ASSERT( nNumber < 1000000000, "ImplAdd9UNum() - Number >= 1000000000" );
999 std::ostringstream ostr;
1000 ostr.fill('0');
1001 ostr.width(9);
1002 ostr << nNumber;
1003 std::string aStr = ostr.str();
1004 rBuf.appendAscii(aStr.c_str(), aStr.size());
1007 void LocaleDataWrapper::ImplAddFormatNum( OUStringBuffer& rBuf,
1008 sal_Int64 nNumber, sal_uInt16 nDecimals, bool bUseThousandSep,
1009 bool bTrailingZeros ) const
1011 OUStringBuffer aNumBuf(64);
1012 sal_uInt16 nNumLen;
1014 // negative number
1015 sal_uInt64 abs;
1016 if ( nNumber < 0 )
1018 // Avoid overflow, map -2^63 -> 2^63 explicitly:
1019 abs = nNumber == std::numeric_limits<sal_Int64>::min()
1020 ? static_cast<sal_uInt64>(std::numeric_limits<sal_Int64>::min()) : nNumber * -1;
1021 rBuf.append('-');
1023 else
1025 abs = nNumber;
1028 // convert number
1029 ImplAddUNum( aNumBuf, abs );
1030 nNumLen = static_cast<sal_uInt16>(aNumBuf.getLength());
1032 if ( nNumLen <= nDecimals )
1034 // strip .0 in decimals?
1035 if ( !nNumber && !bTrailingZeros )
1037 rBuf.append('0');
1039 else
1041 // LeadingZero, insert 0
1042 if ( isNumLeadingZero() )
1044 rBuf.append('0');
1047 // append decimal separator
1048 rBuf.append( aLocaleDataItem.decimalSeparator );
1050 // fill with zeros
1051 sal_uInt16 i = 0;
1052 while ( i < (nDecimals-nNumLen) )
1054 rBuf.append('0');
1055 i++;
1058 // append decimals
1059 rBuf.append(aNumBuf);
1062 else
1064 const OUString& rThoSep = aLocaleDataItem.thousandSeparator;
1066 // copy number to buffer (excluding decimals)
1067 sal_uInt16 nNumLen2 = nNumLen-nDecimals;
1068 uno::Sequence< sal_Bool > aGroupPos;
1069 if (bUseThousandSep)
1070 aGroupPos = utl::DigitGroupingIterator::createForwardSequence(
1071 nNumLen2, getDigitGrouping());
1072 sal_uInt16 i = 0;
1073 for (; i < nNumLen2; ++i )
1075 rBuf.append(aNumBuf[i]);
1077 // add thousand separator?
1078 if ( bUseThousandSep && aGroupPos[i] )
1079 rBuf.append( rThoSep );
1082 // append decimals
1083 if ( nDecimals )
1085 rBuf.append( aLocaleDataItem.decimalSeparator );
1087 bool bNullEnd = true;
1088 while ( i < nNumLen )
1090 if ( aNumBuf[i] != '0' )
1091 bNullEnd = false;
1093 rBuf.append(aNumBuf[i]);
1094 i++;
1097 // strip .0 in decimals?
1098 if ( bNullEnd && !bTrailingZeros )
1099 rBuf.setLength( rBuf.getLength() - (nDecimals + 1) );
1104 // --- simple date and time formatting --------------------------------
1106 OUString LocaleDataWrapper::getDate( const Date& rDate ) const
1108 //!TODO: leading zeros et al
1109 OUStringBuffer aBuf(128);
1110 sal_uInt16 nDay = rDate.GetDay();
1111 sal_uInt16 nMonth = rDate.GetMonth();
1112 sal_Int16 nYear = rDate.GetYear();
1113 sal_uInt16 nYearLen;
1115 if ( (true) /* IsDateCentury() */ )
1116 nYearLen = 4;
1117 else
1119 nYearLen = 2;
1120 nYear %= 100;
1123 switch ( getDateOrder() )
1125 case DateOrder::DMY :
1126 ImplAdd2UNum( aBuf, nDay );
1127 aBuf.append( aLocaleDataItem.dateSeparator );
1128 ImplAdd2UNum( aBuf, nMonth );
1129 aBuf.append( aLocaleDataItem.dateSeparator );
1130 ImplAddNum( aBuf, nYear, nYearLen );
1131 break;
1132 case DateOrder::MDY :
1133 ImplAdd2UNum( aBuf, nMonth );
1134 aBuf.append( aLocaleDataItem.dateSeparator );
1135 ImplAdd2UNum( aBuf, nDay );
1136 aBuf.append( aLocaleDataItem.dateSeparator );
1137 ImplAddNum( aBuf, nYear, nYearLen );
1138 break;
1139 default:
1140 ImplAddNum( aBuf, nYear, nYearLen );
1141 aBuf.append( aLocaleDataItem.dateSeparator );
1142 ImplAdd2UNum( aBuf, nMonth );
1143 aBuf.append( aLocaleDataItem.dateSeparator );
1144 ImplAdd2UNum( aBuf, nDay );
1147 return aBuf.makeStringAndClear();
1150 OUString LocaleDataWrapper::getTime( const tools::Time& rTime, bool bSec, bool b100Sec ) const
1152 //!TODO: leading zeros et al
1153 OUStringBuffer aBuf(128);
1154 sal_uInt16 nHour = rTime.GetHour();
1156 nHour %= 24;
1158 ImplAdd2UNum( aBuf, nHour );
1159 aBuf.append( aLocaleDataItem.timeSeparator );
1160 ImplAdd2UNum( aBuf, rTime.GetMin() );
1161 if ( bSec )
1163 aBuf.append( aLocaleDataItem.timeSeparator );
1164 ImplAdd2UNum( aBuf, rTime.GetSec() );
1166 if ( b100Sec )
1168 aBuf.append( aLocaleDataItem.time100SecSeparator );
1169 ImplAdd9UNum( aBuf, rTime.GetNanoSec() );
1173 return aBuf.makeStringAndClear();
1176 OUString LocaleDataWrapper::getDuration( const tools::Time& rTime, bool bSec, bool b100Sec ) const
1178 OUStringBuffer aBuf(128);
1180 if ( rTime < tools::Time( 0 ) )
1181 aBuf.append(' ' );
1183 if ( (true) /* IsTimeLeadingZero() */ )
1184 ImplAddUNum( aBuf, rTime.GetHour(), 2 );
1185 else
1186 ImplAddUNum( aBuf, rTime.GetHour() );
1187 aBuf.append( aLocaleDataItem.timeSeparator );
1188 ImplAdd2UNum( aBuf, rTime.GetMin() );
1189 if ( bSec )
1191 aBuf.append( aLocaleDataItem.timeSeparator );
1192 ImplAdd2UNum( aBuf, rTime.GetSec() );
1194 if ( b100Sec )
1196 aBuf.append( aLocaleDataItem.time100SecSeparator );
1197 ImplAdd9UNum( aBuf, rTime.GetNanoSec() );
1201 return aBuf.makeStringAndClear();
1204 // --- simple number formatting ---------------------------------------
1206 static size_t ImplGetNumberStringLengthGuess( const css::i18n::LocaleDataItem2& rLocaleDataItem, sal_uInt16 nDecimals )
1208 // approximately 3.2 bits per digit
1209 const size_t nDig = ((sizeof(sal_Int64) * 8) / 3) + 1;
1210 // digits, separators (pessimized for insane "every digit may be grouped"), leading zero, sign
1211 size_t nGuess = ((nDecimals < nDig) ?
1212 (((nDig - nDecimals) * rLocaleDataItem.thousandSeparator.getLength()) + nDig) :
1213 nDecimals) + rLocaleDataItem.decimalSeparator.getLength() + 3;
1214 return nGuess;
1217 OUString LocaleDataWrapper::getNum( sal_Int64 nNumber, sal_uInt16 nDecimals,
1218 bool bUseThousandSep, bool bTrailingZeros ) const
1220 // check if digits and separators will fit into fixed buffer or allocate
1221 size_t nGuess = ImplGetNumberStringLengthGuess( aLocaleDataItem, nDecimals );
1222 OUStringBuffer aBuf(int(nGuess + 16));
1224 ImplAddFormatNum( aBuf, nNumber, nDecimals,
1225 bUseThousandSep, bTrailingZeros );
1227 return aBuf.makeStringAndClear();
1230 OUString LocaleDataWrapper::getCurr( sal_Int64 nNumber, sal_uInt16 nDecimals,
1231 std::u16string_view rCurrencySymbol, bool bUseThousandSep ) const
1233 sal_Unicode cZeroChar = getCurrZeroChar();
1235 // check if digits and separators will fit into fixed buffer or allocate
1236 size_t nGuess = ImplGetNumberStringLengthGuess( aLocaleDataItem, nDecimals );
1237 OUStringBuffer aNumBuf(sal_Int32(nGuess + 16));
1239 bool bNeg;
1240 if ( nNumber < 0 )
1242 bNeg = true;
1243 nNumber *= -1;
1245 else
1246 bNeg = false;
1248 // convert number
1249 ImplAddFormatNum( aNumBuf, nNumber, nDecimals,
1250 bUseThousandSep, true );
1251 const sal_Int32 nNumLen = aNumBuf.getLength();
1253 // replace zeros with zero character
1254 if ( (cZeroChar != '0') && nDecimals /* && IsNumTrailingZeros() */ )
1256 sal_uInt16 i;
1257 bool bZero = true;
1259 sal_uInt16 nNumBufIndex = nNumLen-nDecimals;
1260 i = 0;
1263 if ( aNumBuf[nNumBufIndex] != '0' )
1265 bZero = false;
1266 break;
1269 nNumBufIndex++;
1270 i++;
1272 while ( i < nDecimals );
1274 if ( bZero )
1276 nNumBufIndex = nNumLen-nDecimals;
1277 i = 0;
1280 aNumBuf[nNumBufIndex] = cZeroChar;
1281 nNumBufIndex++;
1282 i++;
1284 while ( i < nDecimals );
1288 OUString aCur;
1289 if ( !bNeg )
1291 switch( getCurrPositiveFormat() )
1293 case 0:
1294 aCur = rCurrencySymbol + aNumBuf;
1295 break;
1296 case 1:
1297 aCur = aNumBuf + rCurrencySymbol;
1298 break;
1299 case 2:
1300 aCur = OUString::Concat(rCurrencySymbol) + " " + aNumBuf;
1301 break;
1302 case 3:
1303 aCur = aNumBuf + " " + rCurrencySymbol;
1304 break;
1307 else
1309 switch( getCurrNegativeFormat() )
1311 case 0:
1312 aCur = OUString::Concat("(") + rCurrencySymbol + aNumBuf + ")";
1313 break;
1314 case 1:
1315 aCur = OUString::Concat("-") + rCurrencySymbol + aNumBuf;
1316 break;
1317 case 2:
1318 aCur = OUString::Concat(rCurrencySymbol) + "-" + aNumBuf;
1319 break;
1320 case 3:
1321 aCur = rCurrencySymbol + aNumBuf + "-";
1322 break;
1323 case 4:
1324 aCur = "(" + aNumBuf + rCurrencySymbol + ")";
1325 break;
1326 case 5:
1327 aCur = "-" + aNumBuf + rCurrencySymbol;
1328 break;
1329 case 6:
1330 aCur = aNumBuf + "-" + rCurrencySymbol;
1331 break;
1332 case 7:
1333 aCur = aNumBuf + rCurrencySymbol + "-";
1334 break;
1335 case 8:
1336 aCur = "-" + aNumBuf + " " + rCurrencySymbol;
1337 break;
1338 case 9:
1339 aCur = OUString::Concat("-") + rCurrencySymbol + " " + aNumBuf;
1340 break;
1341 case 10:
1342 aCur = aNumBuf + " " + rCurrencySymbol + "-";
1343 break;
1344 case 11:
1345 aCur = OUString::Concat(rCurrencySymbol) + " -" + aNumBuf;
1346 break;
1347 case 12:
1348 aCur = OUString::Concat(rCurrencySymbol) + " " + aNumBuf + "-";
1349 break;
1350 case 13:
1351 aCur = aNumBuf + "- " + rCurrencySymbol;
1352 break;
1353 case 14:
1354 aCur = OUString::Concat("(") + rCurrencySymbol + " " + aNumBuf + ")";
1355 break;
1356 case 15:
1357 aCur = "(" + aNumBuf + " " + rCurrencySymbol + ")";
1358 break;
1362 return aCur;
1365 // --- number parsing -------------------------------------------------
1367 double LocaleDataWrapper::stringToDouble( std::u16string_view aString, bool bUseGroupSep,
1368 rtl_math_ConversionStatus* pStatus, sal_Int32* pParseEnd ) const
1370 const sal_Unicode* pParseEndChar;
1371 double fValue = stringToDouble(aString.data(), aString.data() + aString.size(), bUseGroupSep, pStatus, &pParseEndChar);
1372 if (pParseEnd)
1373 *pParseEnd = pParseEndChar - aString.data();
1374 return fValue;
1377 double LocaleDataWrapper::stringToDouble( const sal_Unicode* pBegin, const sal_Unicode* pEnd, bool bUseGroupSep,
1378 rtl_math_ConversionStatus* pStatus, const sal_Unicode** ppParseEnd ) const
1380 const sal_Unicode cGroupSep = (bUseGroupSep ? aLocaleDataItem.thousandSeparator[0] : 0);
1381 rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok;
1382 const sal_Unicode* pParseEnd = nullptr;
1383 double fValue = rtl_math_uStringToDouble( pBegin, pEnd, aLocaleDataItem.decimalSeparator[0], cGroupSep, &eStatus, &pParseEnd);
1384 bool bTryAlt = (pParseEnd < pEnd && !aLocaleDataItem.decimalSeparatorAlternative.isEmpty() &&
1385 *pParseEnd == aLocaleDataItem.decimalSeparatorAlternative.toChar());
1386 // Try re-parsing with alternative if that was the reason to stop.
1387 if (bTryAlt)
1388 fValue = rtl_math_uStringToDouble( pBegin, pEnd, aLocaleDataItem.decimalSeparatorAlternative.toChar(), cGroupSep, &eStatus, &pParseEnd);
1389 if (pStatus)
1390 *pStatus = eStatus;
1391 if (ppParseEnd)
1392 *ppParseEnd = pParseEnd;
1393 return fValue;
1396 // --- mixed ----------------------------------------------------------
1398 LanguageTag LocaleDataWrapper::getLoadedLanguageTag() const
1400 LanguageCountryInfo aLCInfo = getLanguageCountryInfo();
1401 return LanguageTag( lang::Locale( aLCInfo.Language, aLCInfo.Country, aLCInfo.Variant ));
1404 OUString LocaleDataWrapper::appendLocaleInfo(std::u16string_view rDebugMsg) const
1406 LanguageTag aLoaded = getLoadedLanguageTag();
1407 return OUString::Concat(rDebugMsg) + "\n" + maLanguageTag.getBcp47() + " requested\n"
1408 + aLoaded.getBcp47() + " loaded";
1411 // static
1412 void LocaleDataWrapper::outputCheckMessage( std::u16string_view rMsg )
1414 outputCheckMessage(OUStringToOString(rMsg, RTL_TEXTENCODING_UTF8).getStr());
1417 // static
1418 void LocaleDataWrapper::outputCheckMessage( const char* pStr )
1420 fprintf( stderr, "\n%s\n", pStr);
1421 fflush( stderr);
1422 SAL_WARN("unotools.i18n", pStr);
1425 // static
1426 void LocaleDataWrapper::evaluateLocaleDataChecking()
1428 // Using the rtl_Instance template here wouldn't solve all threaded write
1429 // accesses, since we want to assign the result to the static member
1430 // variable and would need to dereference the pointer returned and assign
1431 // the value unguarded. This is the same pattern manually coded.
1432 sal_uInt8 nCheck = nLocaleDataChecking;
1433 if (!nCheck)
1435 ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex());
1436 nCheck = nLocaleDataChecking;
1437 if (!nCheck)
1439 #ifdef DBG_UTIL
1440 nCheck = 1;
1441 #else
1442 const char* pEnv = getenv( "OOO_ENABLE_LOCALE_DATA_CHECKS");
1443 if (pEnv && (pEnv[0] == 'Y' || pEnv[0] == 'y' || pEnv[0] == '1'))
1444 nCheck = 1;
1445 else
1446 nCheck = 2;
1447 #endif
1448 OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
1449 nLocaleDataChecking = nCheck;
1452 else {
1453 OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
1457 // --- XLocaleData3 ----------------------------------------------------------
1459 css::uno::Sequence< css::i18n::Calendar2 > LocaleDataWrapper::getAllCalendars() const
1463 return xLD->getAllCalendars2( getMyLocale() );
1465 catch (const Exception&)
1467 TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllCalendars" );
1469 return {};
1472 // --- XLocaleData4 ----------------------------------------------------------
1474 const css::uno::Sequence< OUString > & LocaleDataWrapper::getDateAcceptancePatterns() const
1476 return aDateAcceptancePatterns;
1479 // --- Override layer --------------------------------------------------------
1481 void LocaleDataWrapper::loadDateAcceptancePatterns(
1482 const std::vector<OUString> & rPatterns )
1484 if (!aDateAcceptancePatterns.hasElements() || rPatterns.empty())
1488 aDateAcceptancePatterns = xLD->getDateAcceptancePatterns( maLanguageTag.getLocale() );
1490 catch (const Exception&)
1492 TOOLS_WARN_EXCEPTION( "unotools.i18n", "setDateAcceptancePatterns" );
1494 if (rPatterns.empty())
1495 return; // just a reset
1496 if (!aDateAcceptancePatterns.hasElements())
1498 aDateAcceptancePatterns = comphelper::containerToSequence(rPatterns);
1499 return;
1503 // Earlier versions checked for presence of the full date pattern with
1504 // aDateAcceptancePatterns[0] == rPatterns[0] and prepended that if not.
1505 // This lead to confusion if the patterns were intentionally specified
1506 // without, giving entirely a different DMY order, see tdf#150288.
1507 // Not checking this and accepting the given patterns as is may result in
1508 // the user shooting themself in the foot, but we can't have both.
1509 aDateAcceptancePatterns = comphelper::containerToSequence(rPatterns);
1512 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */