1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 .
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>
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
;
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
))
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
))
84 loadDateAcceptancePatterns(rOverrideDateAcceptancePatterns
);
87 LocaleDataWrapper::~LocaleDataWrapper()
91 const LanguageTag
& LocaleDataWrapper::getLanguageTag() const
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
;
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
);
162 aDateAcceptancePatterns
= xLD
->getDateAcceptancePatterns( rMyLocale
);
164 catch (const Exception
&)
166 TOOLS_WARN_EXCEPTION( "unotools.i18n", "getDateAcceptancePatterns" );
167 aDateAcceptancePatterns
= {};
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
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" );
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" );
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 ----------------------------------------------------
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
;
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" +
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"
354 + OUString::number(static_cast<sal_Int32
>(static_cast<sal_uInt16
>(eLang
)), 16)
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
;
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
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" &&
414 if (!xSecondaryCalendar
)
416 if (!xSecondaryCalendar
->Name
.equalsIgnoreAsciiCase( rName
))
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
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
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
;
478 if ( *p
== '"' && *(p
-1) != '\\' )
486 if ( pStr
== p
|| *(p
-1) != '\\' )
490 if (!nInSection
&& nSign
== -1)
494 if (!nInSection
&& nPar
== -1)
499 if (!nInSection
&& nNum
== -1)
509 if (!nInSection
&& nBlank
== -1
510 && nSym
!= -1 && p
< pStop
-1 && *(p
+1) == ' ' )
511 nBlank
= p
- pStr
+ 1;
515 if (nSym
== -1 && nInSection
&& *(p
-1) == '[')
518 if (nNum
!= -1 && *(p
-2) == ' ')
519 nBlank
= p
- pStr
- 2;
527 if (!nInSection
&& nSym
== -1 && o3tl::starts_with(rCode
.substr(static_cast<sal_Int32
>(p
- pStr
)), aCurrSymbol
))
528 { // currency symbol not surrounded by [$...]
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;
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();
549 if (areChecksEnabled())
551 outputCheckMessage( appendLocaleInfo( u
"LocaleDataWrapper::getCurrFormatsImpl: no currency formats" ) );
553 nCurrPositiveFormat
= nCurrNegativeFormat
= nCurrFormatDefault
;
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
)
568 if ( pFormatArr
[nElem
].Code
.indexOf( ';' ) >= 0 )
573 if ( (nNeg
== -1 || nMedium
== -1) && pFormatArr
[nElem
].Code
.indexOf( ';' ) >= 0 )
581 if ( nDef
== -1 && pFormatArr
[nElem
].Default
)
583 if ( nNeg
== -1 && pFormatArr
[nElem
].Code
.indexOf( ';' ) >= 0 )
588 sal_Int32 nSign
, nPar
, nNum
, nBlank
, nSym
;
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?" ) );
600 nCurrPositiveFormat
= 0; // $1
602 nCurrPositiveFormat
= 1; // 1$
607 nCurrPositiveFormat
= 2; // $ 1
609 nCurrPositiveFormat
= 3; // 1 $
614 nCurrNegativeFormat
= nCurrFormatDefault
;
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
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-
636 nCurrNegativeFormat
= 2; // $-1
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$-
647 nCurrNegativeFormat
= 6; // 1-$
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-
661 nCurrNegativeFormat
= 11; // $ -1
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 $-
672 nCurrNegativeFormat
= 13; // 1- $
678 // --- date -----------------------------------------------------------
680 DateOrder
LocaleDataWrapper::getDateOrder() const
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');
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
731 { // We have a month 'M' and a day 'D'.
732 // Possible languages containing 'D' and 'M' but not 'Y':
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
)
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
;
763 if (areChecksEnabled())
765 outputCheckMessage( appendLocaleInfo( u
"LocaleDataWrapper::scanDateOrder: no magic applicable" ) );
767 return LongDateOrder::DMY
;
771 static DateOrder
getDateOrderFromLongDateOrder( LongDateOrder eLong
)
775 case LongDateOrder::YMD
:
776 return DateOrder::YMD
;
778 case LongDateOrder::DMY
:
779 return DateOrder::DMY
;
781 case LongDateOrder::MDY
:
782 return DateOrder::MDY
;
784 case LongDateOrder::YDM
:
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();
798 if (areChecksEnabled())
800 outputCheckMessage( appendLocaleInfo( u
"LocaleDataWrapper::getDateOrdersImpl: no date formats" ) );
802 nDateOrder
= DateOrder::DMY
;
803 nLongDateOrder
= LongDateOrder::DMY
;
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
)
815 if ( nDef
== -1 && pFormatArr
[nElem
].Default
)
817 switch ( pFormatArr
[nElem
].Type
)
819 case KNumberFormatType::MEDIUM
:
821 if ( pFormatArr
[nElem
].Default
)
826 else if ( nMedium
== -1 )
830 case KNumberFormatType::LONG
:
832 if ( pFormatArr
[nElem
].Default
)
834 else if ( nLong
== -1 )
842 if (areChecksEnabled())
844 outputCheckMessage( appendLocaleInfo( u
"LocaleDataWrapper::getDateOrdersImpl: no edit" ) );
848 if (areChecksEnabled())
850 outputCheckMessage( appendLocaleInfo( u
"LocaleDataWrapper::getDateOrdersImpl: no default" ) );
854 else if ( nLong
!= -1 )
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
);
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
);
873 nLongDateOrder
= nDO
;
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])
893 i18n::LanguageCountryInfo
aLCInfo( getLanguageCountryInfo());
894 if (aLCInfo
.Country
.equalsIgnoreAsciiCase("IN") || // India
895 aLCInfo
.Country
.equalsIgnoreAsciiCase("BT") ) // Bhutan
897 aGrouping
= { 3, 2, 0 };
901 aGrouping
= { 3, 0, 0 };
905 const css::uno::Sequence
< sal_Int32
>& LocaleDataWrapper::getDigitGrouping() const
910 // --- simple number formatting helpers -------------------------------
912 // The ImplAdd... methods are taken from class International and modified to
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';
928 // copy temp buffer to buffer passed
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';
951 // fill with zeros up to the minimal length
952 while ( nMinLen
> 0 )
958 // copy temp buffer to real buffer
962 rBuf
.append(*pTempBuf
);
964 while ( pTempBuf
!= aTempBuf
);
967 static void ImplAddNum( OUStringBuffer
& rBuf
, sal_Int64 nNumber
, int nMinLen
)
974 return ImplAddUNum( rBuf
, nNumber
, nMinLen
);
977 static void ImplAdd2UNum( OUStringBuffer
& rBuf
, sal_uInt16 nNumber
)
979 DBG_ASSERT( nNumber
< 100, "ImplAdd2UNum() - Number >= 100" );
984 rBuf
.append(static_cast<char>(nNumber
+ '0'));
988 sal_uInt16 nTemp
= 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
;
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);
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;
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
)
1041 // LeadingZero, insert 0
1042 if ( isNumLeadingZero() )
1047 // append decimal separator
1048 rBuf
.append( aLocaleDataItem
.decimalSeparator
);
1052 while ( i
< (nDecimals
-nNumLen
) )
1059 rBuf
.append(aNumBuf
);
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());
1073 for (; i
< nNumLen2
; ++i
)
1075 rBuf
.append(aNumBuf
[i
]);
1077 // add thousand separator?
1078 if ( bUseThousandSep
&& aGroupPos
[i
] )
1079 rBuf
.append( rThoSep
);
1085 rBuf
.append( aLocaleDataItem
.decimalSeparator
);
1087 bool bNullEnd
= true;
1088 while ( i
< nNumLen
)
1090 if ( aNumBuf
[i
] != '0' )
1093 rBuf
.append(aNumBuf
[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() */ )
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
);
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
);
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();
1158 ImplAdd2UNum( aBuf
, nHour
);
1159 aBuf
.append( aLocaleDataItem
.timeSeparator
);
1160 ImplAdd2UNum( aBuf
, rTime
.GetMin() );
1163 aBuf
.append( aLocaleDataItem
.timeSeparator
);
1164 ImplAdd2UNum( aBuf
, rTime
.GetSec() );
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 ) )
1183 if ( (true) /* IsTimeLeadingZero() */ )
1184 ImplAddUNum( aBuf
, rTime
.GetHour(), 2 );
1186 ImplAddUNum( aBuf
, rTime
.GetHour() );
1187 aBuf
.append( aLocaleDataItem
.timeSeparator
);
1188 ImplAdd2UNum( aBuf
, rTime
.GetMin() );
1191 aBuf
.append( aLocaleDataItem
.timeSeparator
);
1192 ImplAdd2UNum( aBuf
, rTime
.GetSec() );
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;
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));
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() */ )
1259 sal_uInt16 nNumBufIndex
= nNumLen
-nDecimals
;
1263 if ( aNumBuf
[nNumBufIndex
] != '0' )
1272 while ( i
< nDecimals
);
1276 nNumBufIndex
= nNumLen
-nDecimals
;
1280 aNumBuf
[nNumBufIndex
] = cZeroChar
;
1284 while ( i
< nDecimals
);
1291 switch( getCurrPositiveFormat() )
1294 aCur
= rCurrencySymbol
+ aNumBuf
;
1297 aCur
= aNumBuf
+ rCurrencySymbol
;
1300 aCur
= OUString::Concat(rCurrencySymbol
) + " " + aNumBuf
;
1303 aCur
= aNumBuf
+ " " + rCurrencySymbol
;
1309 switch( getCurrNegativeFormat() )
1312 aCur
= OUString::Concat("(") + rCurrencySymbol
+ aNumBuf
+ ")";
1315 aCur
= OUString::Concat("-") + rCurrencySymbol
+ aNumBuf
;
1318 aCur
= OUString::Concat(rCurrencySymbol
) + "-" + aNumBuf
;
1321 aCur
= rCurrencySymbol
+ aNumBuf
+ "-";
1324 aCur
= "(" + aNumBuf
+ rCurrencySymbol
+ ")";
1327 aCur
= "-" + aNumBuf
+ rCurrencySymbol
;
1330 aCur
= aNumBuf
+ "-" + rCurrencySymbol
;
1333 aCur
= aNumBuf
+ rCurrencySymbol
+ "-";
1336 aCur
= "-" + aNumBuf
+ " " + rCurrencySymbol
;
1339 aCur
= OUString::Concat("-") + rCurrencySymbol
+ " " + aNumBuf
;
1342 aCur
= aNumBuf
+ " " + rCurrencySymbol
+ "-";
1345 aCur
= OUString::Concat(rCurrencySymbol
) + " -" + aNumBuf
;
1348 aCur
= OUString::Concat(rCurrencySymbol
) + " " + aNumBuf
+ "-";
1351 aCur
= aNumBuf
+ "- " + rCurrencySymbol
;
1354 aCur
= OUString::Concat("(") + rCurrencySymbol
+ " " + aNumBuf
+ ")";
1357 aCur
= "(" + aNumBuf
+ " " + rCurrencySymbol
+ ")";
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
);
1373 *pParseEnd
= pParseEndChar
- aString
.data();
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.
1388 fValue
= rtl_math_uStringToDouble( pBegin
, pEnd
, aLocaleDataItem
.decimalSeparatorAlternative
.toChar(), cGroupSep
, &eStatus
, &pParseEnd
);
1392 *ppParseEnd
= pParseEnd
;
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";
1412 void LocaleDataWrapper::outputCheckMessage( std::u16string_view rMsg
)
1414 outputCheckMessage(OUStringToOString(rMsg
, RTL_TEXTENCODING_UTF8
).getStr());
1418 void LocaleDataWrapper::outputCheckMessage( const char* pStr
)
1420 fprintf( stderr
, "\n%s\n", pStr
);
1422 SAL_WARN("unotools.i18n", pStr
);
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
;
1435 ::osl::MutexGuard
aGuard( ::osl::Mutex::getGlobalMutex());
1436 nCheck
= nLocaleDataChecking
;
1442 const char* pEnv
= getenv( "OOO_ENABLE_LOCALE_DATA_CHECKS");
1443 if (pEnv
&& (pEnv
[0] == 'Y' || pEnv
[0] == 'y' || pEnv
[0] == '1'))
1448 OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
1449 nLocaleDataChecking
= nCheck
;
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" );
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
);
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: */