summaryrefslogtreecommitdiffstats
path: root/src/corelib/text/qlocale.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/text/qlocale.cpp')
-rw-r--r--src/corelib/text/qlocale.cpp560
1 files changed, 362 insertions, 198 deletions
diff --git a/src/corelib/text/qlocale.cpp b/src/corelib/text/qlocale.cpp
index f8c98c4c16..86ab072b73 100644
--- a/src/corelib/text/qlocale.cpp
+++ b/src/corelib/text/qlocale.cpp
@@ -54,6 +54,8 @@ QT_WARNING_DISABLE_GCC("-Wfree-nonheap-object") // false positive tracking
QT_BEGIN_NAMESPACE
+constexpr int QLocale::DefaultTwoDigitBaseYear;
+
QT_IMPL_METATYPE_EXTERN_TAGGED(QList<Qt::DayOfWeek>, QList_Qt__DayOfWeek)
#ifndef QT_NO_SYSTEMLOCALE
QT_IMPL_METATYPE_EXTERN_TAGGED(QSystemLocale::CurrencyToStringArgument,
@@ -109,18 +111,18 @@ QLocale::Language QLocalePrivate::codeToLanguage(QStringView code,
auto searchCode = [codeBuf](auto f) {
return std::find_if(languageCodeList.begin(), languageCodeList.end(),
- [=](const LanguageCodeEntry &i) { return f(i) == codeBuf; });
+ [=](LanguageCodeEntry i) { return f(i) == codeBuf; });
};
if (codeTypes.testFlag(QLocale::ISO639Part1) && uc3 == 0) {
- auto i = searchCode([](const LanguageCodeEntry &i) { return i.part1; });
+ auto i = searchCode([](LanguageCodeEntry i) { return i.part1; });
if (i != languageCodeList.end())
return QLocale::Language(std::distance(languageCodeList.begin(), i));
}
if (uc3 != 0) {
if (codeTypes.testFlag(QLocale::ISO639Part2B)) {
- auto i = searchCode([](const LanguageCodeEntry &i) { return i.part2B; });
+ auto i = searchCode([](LanguageCodeEntry i) { return i.part2B; });
if (i != languageCodeList.end())
return QLocale::Language(std::distance(languageCodeList.begin(), i));
}
@@ -129,13 +131,13 @@ QLocale::Language QLocalePrivate::codeToLanguage(QStringView code,
// This is asserted in iso639_3.LanguageCodeData.
if (codeTypes.testFlag(QLocale::ISO639Part2T)
&& !codeTypes.testFlag(QLocale::ISO639Part3)) {
- auto i = searchCode([](const LanguageCodeEntry &i) { return i.part2T; });
+ auto i = searchCode([](LanguageCodeEntry i) { return i.part2T; });
if (i != languageCodeList.end())
return QLocale::Language(std::distance(languageCodeList.begin(), i));
}
if (codeTypes.testFlag(QLocale::ISO639Part3)) {
- auto i = searchCode([](const LanguageCodeEntry &i) { return i.part3; });
+ auto i = searchCode([](LanguageCodeEntry i) { return i.part3; });
if (i != languageCodeList.end())
return QLocale::Language(std::distance(languageCodeList.begin(), i));
}
@@ -250,7 +252,7 @@ struct LikelyPair
QLocaleId value = QLocaleId { 0, 0, 0 };
};
-bool operator<(const LikelyPair &lhs, const LikelyPair &rhs)
+bool operator<(LikelyPair lhs, LikelyPair rhs)
{
// Must match the comparison LocaleDataWriter.likelySubtags() uses when
// sorting, see qtbase/util/locale_database.qlocalexml2cpp.py
@@ -463,7 +465,7 @@ QByteArray QLocalePrivate::bcp47Name(char separator) const
return m_data->id().withLikelySubtagsRemoved().name(separator);
}
-static qsizetype findLocaleIndexById(const QLocaleId &localeId)
+static qsizetype findLocaleIndexById(QLocaleId localeId)
{
qsizetype idx = locale_index[localeId.language_id];
// If there are no locales for specified language (so we we've got the
@@ -682,6 +684,7 @@ qsizetype qt_repeatCount(QStringView s)
}
Q_CONSTINIT static const QLocaleData *default_data = nullptr;
+Q_CONSTINIT QBasicAtomicInt QLocalePrivate::s_generation = Q_BASIC_ATOMIC_INITIALIZER(0);
static QLocalePrivate *c_private()
{
@@ -704,7 +707,7 @@ static QLocalePrivate *c_private()
system locale. This is only intended as a way to let a platform plugin
install its own system locale, overriding what might otherwise be provided
for its class of platform (as Android does, differing from Linux), and to
- let tests transiently over-ride the system or plugin-supplied one. As such,
+ let tests transiently override the system or plugin-supplied one. As such,
there should not be diverse threads creating and destroying QSystemLocale
instances concurrently, so no attempt is made at thread-safety in managing
the stack.
@@ -780,28 +783,49 @@ static void updateSystemPrivate()
systemLocaleData.m_script_id = res.toInt();
// Should we replace Any values based on likely sub-tags ?
+
+ // If system locale is default locale, update the default collator's generation:
+ if (default_data == &systemLocaleData)
+ QLocalePrivate::s_generation.fetchAndAddRelaxed(1);
}
#endif // !QT_NO_SYSTEMLOCALE
-static const QLocaleData *systemData()
+static const QLocaleData *systemData(qsizetype *sysIndex = nullptr)
{
#ifndef QT_NO_SYSTEMLOCALE
/*
Copy over the information from the fallback locale and modify.
- This modifies (cross-thread) global state, so take care to only call it in
- one thread.
+ If sysIndex is passed, it should be the m_index of the system locale's
+ QLocalePrivate, which we'll update if it needs it.
+
+ This modifies (cross-thread) global state, so is mutex-protected.
*/
{
+ Q_CONSTINIT static QLocaleId sysId;
+ bool updated = false;
+
Q_CONSTINIT static QBasicMutex systemDataMutex;
systemDataMutex.lock();
- if (systemLocaleData.m_language_id == 0)
+ if (systemLocaleData.m_language_id == 0) {
updateSystemPrivate();
+ updated = true;
+ }
+ // Initialization of system private has *sysIndex == -1 to hit this.
+ if (sysIndex && (updated || *sysIndex < 0)) {
+ const QLocaleId nowId = systemLocaleData.id();
+ if (sysId != nowId || *sysIndex < 0) {
+ // This look-up may be expensive:
+ *sysIndex = QLocaleData::findLocaleIndex(nowId);
+ sysId = nowId;
+ }
+ }
systemDataMutex.unlock();
}
return &systemLocaleData;
#else
+ Q_UNUSED(sysIndex);
return locale_data;
#endif
}
@@ -853,7 +877,6 @@ QDataStream &operator>>(QDataStream &ds, QLocale &l)
static constexpr qsizetype locale_data_size = q20::ssize(locale_data) - 1; // trailing guard
-Q_CONSTINIT QBasicAtomicInt QLocalePrivate::s_generation = Q_BASIC_ATOMIC_INITIALIZER(0);
Q_GLOBAL_STATIC(QSharedDataPointer<QLocalePrivate>, defaultLocalePrivate,
new QLocalePrivate(defaultData(), defaultIndex()))
@@ -980,6 +1003,21 @@ QLocale::QLocale(QLocalePrivate &dd)
: d(&dd)
{}
+/*!
+ \variable QLocale::DefaultTwoDigitBaseYear
+ \since 6.7
+
+ \brief The default start year of the century within which a format taking
+ a two-digit year will select. The value of the constant is \c {1900}.
+
+ Some locales use, particularly for ShortFormat, only the last two digits of
+ the year. Proir to 6.7 the year 1900 was always used as a base year for
+ such cases. Now various QLocale and QDate functions have the overloads that
+ allow callers to specify the base year, and this constant is used as its
+ default value.
+
+ \sa toDate(), toDateTime(), QDate::fromString(), QDateTime::fromString()
+*/
/*!
\since 6.3
@@ -1049,7 +1087,7 @@ QLocale::QLocale()
*/
QLocale::QLocale(Language language, Territory territory)
- : d(findLocalePrivate(language, QLocale::AnyScript, territory))
+ : d(findLocalePrivate(language, AnyScript, territory))
{
}
@@ -1176,10 +1214,10 @@ QString QLocale::quoteString(QStringView str, QuotationStyle style) const
#ifndef QT_NO_SYSTEMLOCALE
if (d->m_data == &systemLocaleData) {
QVariant res;
- if (style == QLocale::AlternateQuotation)
+ if (style == AlternateQuotation)
res = systemLocale()->query(QSystemLocale::StringToAlternateQuotation,
QVariant::fromValue(str));
- if (res.isNull() || style == QLocale::StandardQuotation)
+ if (res.isNull() || style == StandardQuotation)
res = systemLocale()->query(QSystemLocale::StringToStandardQuotation,
QVariant::fromValue(str));
if (!res.isNull())
@@ -1188,7 +1226,7 @@ QString QLocale::quoteString(QStringView str, QuotationStyle style) const
#endif
QLocaleData::DataRange start, end;
- if (style == QLocale::StandardQuotation) {
+ if (style == StandardQuotation) {
start = d->m_data->quoteStart();
end = d->m_data->quoteEnd();
} else {
@@ -1320,13 +1358,47 @@ QLocale::Country QLocale::country() const
#endif
/*!
+ \since 6.7
+ \enum QLocale::TagSeparator
+
+ Indicate how to combine the parts that make up a locale identifier.
+
+ A locale identifier may be made up of several tags, indicating language,
+ script and territory (plus, potentially, other details), joined together to
+ form the identifier. Various standards and conventional forms use either a
+ dash (the Unicode HYPHEN-MINUS, U+002D) or an underscore (LOW LINE, U+005F).
+ Different clients of QLocale may thus need one or the other.
+
+ \value Dash Use \c{'-'}, the dash or hyphen character.
+ \value Underscore Use \c{'_'}, the underscore character.
+
+ \note Although dash and underscore are the only separators used in public
+ standards (as at 2023), it is possible to cast any \l
+ {https://en.cppreference.com/w/cpp/language/ascii} {ASCII} character to this
+ type if a non-standard ASCII separator is needed. Casting a non-ASCII
+ character (with decimal value above 127) is not supported: such values are
+ reserved for future use as enum members if some public standard ever uses a
+ non-ASCII separator. It is, of course, possible to use QString::replace() to
+ replace the separator used by a function taking a parameter of this type
+ with an arbitrary Unicode character or string.
+*/
+
+Q_DECL_COLD_FUNCTION static void badSeparatorWarning(const char *method, char sep)
+{
+ qWarning("QLocale::%s(): Using non-ASCII separator '%c' (%02x) is unsupported",
+ method, sep, uint(uchar(sep)));
+}
+
+/*!
\brief The short name of this locale.
Returns the language and territory of this locale as a string of the form
"language_territory", where language is a lowercase, two-letter ISO 639
language code, and territory is an uppercase, two- or three-letter ISO 3166
territory code. If the locale has no specified territory, only the language
- name is returned.
+ name is returned. Since Qt 6.7 an optional \a separator parameter can be
+ supplied to override the default underscore character separating the two
+ tags.
Even if the QLocale object was constructed with an explicit script, name()
will not contain it for compatibility reasons. Use \l bcp47Name() instead if
@@ -1337,8 +1409,13 @@ QLocale::Country QLocale::country() const
\sa QLocale(), language(), script(), territory(), bcp47Name(), uiLanguages()
*/
-QString QLocale::name() const
+QString QLocale::name(TagSeparator separator) const
{
+ const char sep = char(separator);
+ if (uchar(sep) > 0x7f) {
+ badSeparatorWarning("name", sep);
+ return {};
+ }
const auto code = d->languageCode();
QLatin1StringView view{code.data()};
@@ -1350,7 +1427,7 @@ QString QLocale::name() const
if (c == AnyTerritory)
return view;
- return view + u'_' + d->territoryCode();
+ return view + QLatin1Char(sep) + d->territoryCode();
}
template <typename T> static inline
@@ -1359,12 +1436,16 @@ T toIntegral_helper(const QLocalePrivate *d, QStringView str, bool *ok)
constexpr bool isUnsigned = std::is_unsigned_v<T>;
using Int64 = typename std::conditional_t<isUnsigned, quint64, qint64>;
- Int64 val = 0;
+ QSimpleParsedNumber<Int64> r{};
if constexpr (isUnsigned)
- val = d->m_data->stringToUnsLongLong(str, 10, ok, d->m_numberOptions);
+ r = d->m_data->stringToUnsLongLong(str, 10, d->m_numberOptions);
else
- val = d->m_data->stringToLongLong(str, 10, ok, d->m_numberOptions);
+ r = d->m_data->stringToLongLong(str, 10, d->m_numberOptions);
+ if (ok)
+ *ok = r.ok();
+
+ Int64 val = r.result;
if (T(val) != val) {
if (ok != nullptr)
*ok = false;
@@ -1390,13 +1471,22 @@ T toIntegral_helper(const QLocalePrivate *d, QStringView str, bool *ok)
locale name of the QLocale data; this need not be the language the
user-interface should be in.
- This function tries to conform the locale name to BCP47.
+ This function tries to conform the locale name to the IETF Best Common
+ Practice 47, defined by RFC 5646. Since Qt 6.7, it supports an optional \a
+ separator parameter which can be used to override the BCP47-specified use of
+ a hyphen to separate the tags. For use in IETF-defined protocols, however,
+ the default, QLocale::TagSeparator::Dash, should be retained.
\sa name(), language(), territory(), script(), uiLanguages()
*/
-QString QLocale::bcp47Name() const
+QString QLocale::bcp47Name(TagSeparator separator) const
{
- return QString::fromLatin1(d->bcp47Name());
+ const char sep = char(separator);
+ if (uchar(sep) > 0x7f) {
+ badSeparatorWarning("bcp47Name", sep);
+ return {};
+ }
+ return QString::fromLatin1(d->bcp47Name(sep));
}
/*!
@@ -1541,9 +1631,9 @@ QLocale::Script QLocale::codeToScript(QStringView scriptCode) noexcept
QString QLocale::languageToString(Language language)
{
- if (language > QLocale::LastLanguage)
+ if (language > LastLanguage)
return "Unknown"_L1;
- return QLatin1StringView(language_name_list + language_name_index[language]);
+ return QString::fromUtf8(language_name_list + language_name_index[language]);
}
/*!
@@ -1553,11 +1643,11 @@ QString QLocale::languageToString(Language language)
\sa languageToString(), scriptToString(), territory(), bcp47Name()
*/
-QString QLocale::territoryToString(QLocale::Territory territory)
+QString QLocale::territoryToString(Territory territory)
{
- if (territory > QLocale::LastTerritory)
+ if (territory > LastTerritory)
return "Unknown"_L1;
- return QLatin1StringView(territory_name_list + territory_name_index[territory]);
+ return QString::fromUtf8(territory_name_list + territory_name_index[territory]);
}
#if QT_DEPRECATED_SINCE(6, 6)
@@ -1581,11 +1671,11 @@ QString QLocale::countryToString(Country country)
\sa languageToString(), territoryToString(), script(), bcp47Name()
*/
-QString QLocale::scriptToString(QLocale::Script script)
+QString QLocale::scriptToString(Script script)
{
- if (script > QLocale::LastScript)
+ if (script > LastScript)
return "Unknown"_L1;
- return QLatin1StringView(script_name_list + script_name_index[script]);
+ return QString::fromUtf8(script_name_list + script_name_index[script]);
}
/*!
@@ -2348,6 +2438,16 @@ QTime QLocale::toTime(const QString &string, FormatType format) const
Parses \a string and returns the date it represents. The format of the date
string is chosen according to the \a format parameter (see dateFormat()).
+//! [base-year-for-short]
+ Some locales use, particularly for ShortFormat, only the last two digits of
+ the year. In such a case, the 100 years starting at \a baseYear are the
+ candidates first considered. Prior to 6.7 there was no \a baseYear parameter
+ and 1900 was always used. This is the default for \a baseYear, selecting a
+ year from then to 1999. In some cases, other fields may lead to the next or
+ previous century being selected, to get a result consistent with all fields
+ given. See \l QDate::fromString() for details.
+//! [base-year-for-short]
+
\note Month and day names, where used, must be given in the locale's
language.
@@ -2355,18 +2455,18 @@ QTime QLocale::toTime(const QString &string, FormatType format) const
\sa dateFormat(), toTime(), toDateTime(), QDate::fromString()
*/
-QDate QLocale::toDate(const QString &string, FormatType format) const
+QDate QLocale::toDate(const QString &string, FormatType format, int baseYear) const
{
- return toDate(string, dateFormat(format));
+ return toDate(string, dateFormat(format), baseYear);
}
/*!
\since 5.14
\overload
*/
-QDate QLocale::toDate(const QString &string, FormatType format, QCalendar cal) const
+QDate QLocale::toDate(const QString &string, FormatType format, QCalendar cal, int baseYear) const
{
- return toDate(string, dateFormat(format), cal);
+ return toDate(string, dateFormat(format), cal, baseYear);
}
/*!
@@ -2378,6 +2478,8 @@ QDate QLocale::toDate(const QString &string, FormatType format, QCalendar cal) c
date string is chosen according to the \a format parameter (see
dateFormat()).
+ \include qlocale.cpp base-year-for-short
+
\note Month and day names, where used, must be given in the locale's
language. Any am/pm indicators used must match \l amText() or \l pmText(),
ignoring case.
@@ -2386,18 +2488,19 @@ QDate QLocale::toDate(const QString &string, FormatType format, QCalendar cal) c
\sa dateTimeFormat(), toTime(), toDate(), QDateTime::fromString()
*/
-QDateTime QLocale::toDateTime(const QString &string, FormatType format) const
+QDateTime QLocale::toDateTime(const QString &string, FormatType format, int baseYear) const
{
- return toDateTime(string, dateTimeFormat(format));
+ return toDateTime(string, dateTimeFormat(format), baseYear);
}
/*!
\since 5.14
\overload
*/
-QDateTime QLocale::toDateTime(const QString &string, FormatType format, QCalendar cal) const
+QDateTime QLocale::toDateTime(const QString &string, FormatType format, QCalendar cal,
+ int baseYear) const
{
- return toDateTime(string, dateTimeFormat(format), cal);
+ return toDateTime(string, dateTimeFormat(format), cal, baseYear);
}
/*!
@@ -2438,6 +2541,16 @@ QTime QLocale::toTime(const QString &string, const QString &format) const
Parses \a string and returns the date it represents. See QDate::fromString()
for the interpretation of \a format.
+//! [base-year-for-two-digit]
+ When \a format only specifies the last two digits of a year, the 100 years
+ starting at \a baseYear are the candidates first considered. Prior to 6.7
+ there was no \a baseYear parameter and 1900 was always used. This is the
+ default for \a baseYear, selecting a year from then to 1999. In some cases,
+ other fields may lead to the next or previous century being selected, to get
+ a result consistent with all fields given. See \l QDate::fromString() for
+ details.
+//! [base-year-for-two-digit]
+
\note Month and day names, where used, must be given in the locale's
language.
@@ -2445,26 +2558,27 @@ QTime QLocale::toTime(const QString &string, const QString &format) const
\sa dateFormat(), toTime(), toDateTime(), QDate::fromString()
*/
-QDate QLocale::toDate(const QString &string, const QString &format) const
+QDate QLocale::toDate(const QString &string, const QString &format, int baseYear) const
{
- return toDate(string, format, QCalendar());
+ return toDate(string, format, QCalendar(), baseYear);
}
/*!
\since 5.14
\overload
*/
-QDate QLocale::toDate(const QString &string, const QString &format, QCalendar cal) const
+QDate QLocale::toDate(const QString &string, const QString &format, QCalendar cal, int baseYear) const
{
QDate date;
#if QT_CONFIG(datetimeparser)
QDateTimeParser dt(QMetaType::QDate, QDateTimeParser::FromString, cal);
dt.setDefaultLocale(*this);
if (dt.parseFormat(format))
- dt.fromString(string, &date, nullptr);
+ dt.fromString(string, &date, nullptr, baseYear);
#else
Q_UNUSED(string);
Q_UNUSED(format);
+ Q_UNUSED(baseYear);
Q_UNUSED(cal);
#endif
return date;
@@ -2478,6 +2592,8 @@ QDate QLocale::toDate(const QString &string, const QString &format, QCalendar ca
Parses \a string and returns the date-time it represents. See
QDateTime::fromString() for the interpretation of \a format.
+ \include qlocale.cpp base-year-for-two-digit
+
\note Month and day names, where used, must be given in the locale's
language. Any am/pm indicators used must match \l amText() or \l pmText(),
ignoring case.
@@ -2491,27 +2607,31 @@ QDate QLocale::toDate(const QString &string, const QString &format, QCalendar ca
\sa dateTimeFormat(), toTime(), toDate(), QDateTime::fromString()
*/
-QDateTime QLocale::toDateTime(const QString &string, const QString &format) const
+QDateTime QLocale::toDateTime(const QString &string, const QString &format, int baseYear) const
{
- return toDateTime(string, format, QCalendar());
+ return toDateTime(string, format, QCalendar(), baseYear);
}
/*!
\since 5.14
\overload
*/
-QDateTime QLocale::toDateTime(const QString &string, const QString &format, QCalendar cal) const
+QDateTime QLocale::toDateTime(const QString &string, const QString &format, QCalendar cal,
+ int baseYear) const
{
#if QT_CONFIG(datetimeparser)
QDateTime datetime;
QDateTimeParser dt(QMetaType::QDateTime, QDateTimeParser::FromString, cal);
dt.setDefaultLocale(*this);
- if (dt.parseFormat(format) && (dt.fromString(string, &datetime) || !datetime.isValid()))
+ if (dt.parseFormat(format) && (dt.fromString(string, &datetime, baseYear)
+ || !datetime.isValid())) {
return datetime;
+ }
#else
Q_UNUSED(string);
Q_UNUSED(format);
+ Q_UNUSED(baseYear);
Q_UNUSED(cal);
#endif
return QDateTime();
@@ -2741,8 +2861,19 @@ QString QLocale::toString(double f, char format, int precision) const
QLocale QLocale::system()
{
- QT_PREPEND_NAMESPACE(systemData)(); // Ensure system data is up to date.
- static QLocalePrivate locale(systemData(), defaultIndex(), DefaultNumberOptions, 1);
+ constexpr auto sysData = []() {
+ // Same return as systemData(), but leave the setup to the actual call to it.
+#ifdef QT_NO_SYSTEMLOCALE
+ return locale_data;
+#else
+ return &systemLocaleData;
+#endif
+ };
+ Q_CONSTINIT static QLocalePrivate locale(sysData(), -1, DefaultNumberOptions, 1);
+ // Calling systemData() ensures system data is up to date; we also need it
+ // to ensure that locale's index stays up to date:
+ systemData(&locale.m_index);
+ Q_ASSERT(locale.m_index >= 0 && locale.m_index < locale_data_size);
return QLocale(locale);
}
@@ -2759,15 +2890,14 @@ QLocale QLocale::system()
QList<QLocale> locales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript,
QLocale::Russia);
*/
-QList<QLocale> QLocale::matchingLocales(QLocale::Language language, QLocale::Script script,
- QLocale::Territory territory)
+QList<QLocale> QLocale::matchingLocales(Language language, Script script, Territory territory)
{
const QLocaleId filter { language, script, territory };
if (!filter.isValid())
return QList<QLocale>();
- if (language == QLocale::C)
- return QList<QLocale>() << QLocale(QLocale::C);
+ if (language == C)
+ return QList<QLocale>{QLocale(C)};
QList<QLocale> result;
if (filter.matchesAll())
@@ -2790,7 +2920,7 @@ QList<QLocale> QLocale::matchingLocales(QLocale::Language language, QLocale::Scr
if (filter.acceptLanguage(syslocaledata->m_language_id)) {
const QLocaleId id = syslocaledata->id();
if (filter.acceptScriptTerritory(id))
- result.append(QLocale::system());
+ result.append(system());
}
return result;
@@ -2810,7 +2940,7 @@ QList<QLocale> QLocale::matchingLocales(QLocale::Language language, QLocale::Scr
QList<QLocale::Country> QLocale::countriesForLanguage(Language language)
{
const auto locales = matchingLocales(language, AnyScript, AnyCountry);
- QList<QLocale::Country> result;
+ QList<Country> result;
result.reserve(locales.size());
for (const auto &locale : locales)
result.append(locale.territory());
@@ -2888,6 +3018,14 @@ QString QLocale::standaloneDayName(int day, FormatType type) const
// Calendar look-up of month and day names:
+// Only used in assertions
+[[maybe_unused]] static bool sameLocale(const QLocaleData *locale, const QCalendarLocale &calendar)
+{
+ return locale->m_language_id == calendar.m_language_id
+ && locale->m_script_id == calendar.m_script_id
+ && locale->m_territory_id == calendar.m_territory_id;
+}
+
/*!
\internal
*/
@@ -2996,12 +3134,13 @@ QString QCalendarBackend::monthName(const QLocale &locale, int month, int,
QLocale::FormatType format) const
{
Q_ASSERT(month >= 1 && month <= maximumMonthsInYear());
- return rawMonthName(localeMonthIndexData()[locale.d->m_index],
- localeMonthData(), month, format);
+ const QCalendarLocale &monthly = localeMonthIndexData()[locale.d->m_index];
+ Q_ASSERT(sameLocale(locale.d->m_data, monthly));
+ return rawMonthName(monthly, localeMonthData(), month, format);
}
-QString QGregorianCalendar::monthName(const QLocale &locale, int month, int year,
- QLocale::FormatType format) const
+QString QRomanCalendar::monthName(const QLocale &locale, int month, int year,
+ QLocale::FormatType format) const
{
#ifndef QT_NO_SYSTEMLOCALE
if (locale.d->m_data == &systemLocaleData) {
@@ -3031,12 +3170,13 @@ QString QCalendarBackend::standaloneMonthName(const QLocale &locale, int month,
QLocale::FormatType format) const
{
Q_ASSERT(month >= 1 && month <= maximumMonthsInYear());
- return rawStandaloneMonthName(localeMonthIndexData()[locale.d->m_index],
- localeMonthData(), month, format);
+ const QCalendarLocale &monthly = localeMonthIndexData()[locale.d->m_index];
+ Q_ASSERT(sameLocale(locale.d->m_data, monthly));
+ return rawStandaloneMonthName(monthly, localeMonthData(), month, format);
}
-QString QGregorianCalendar::standaloneMonthName(const QLocale &locale, int month, int year,
- QLocale::FormatType format) const
+QString QRomanCalendar::standaloneMonthName(const QLocale &locale, int month, int year,
+ QLocale::FormatType format) const
{
#ifndef QT_NO_SYSTEMLOCALE
if (locale.d->m_data == &systemLocaleData) {
@@ -3204,34 +3344,34 @@ QLocale::MeasurementSystem QLocale::measurementSystem() const
Qt::LayoutDirection QLocale::textDirection() const
{
switch (script()) {
- case QLocale::AdlamScript:
- case QLocale::ArabicScript:
- case QLocale::AvestanScript:
- case QLocale::CypriotScript:
- case QLocale::HatranScript:
- case QLocale::HebrewScript:
- case QLocale::ImperialAramaicScript:
- case QLocale::InscriptionalPahlaviScript:
- case QLocale::InscriptionalParthianScript:
- case QLocale::KharoshthiScript:
- case QLocale::LydianScript:
- case QLocale::MandaeanScript:
- case QLocale::ManichaeanScript:
- case QLocale::MendeKikakuiScript:
- case QLocale::MeroiticCursiveScript:
- case QLocale::MeroiticScript:
- case QLocale::NabataeanScript:
- case QLocale::NkoScript:
- case QLocale::OldHungarianScript:
- case QLocale::OldNorthArabianScript:
- case QLocale::OldSouthArabianScript:
- case QLocale::OrkhonScript:
- case QLocale::PalmyreneScript:
- case QLocale::PhoenicianScript:
- case QLocale::PsalterPahlaviScript:
- case QLocale::SamaritanScript:
- case QLocale::SyriacScript:
- case QLocale::ThaanaScript:
+ case AdlamScript:
+ case ArabicScript:
+ case AvestanScript:
+ case CypriotScript:
+ case HatranScript:
+ case HebrewScript:
+ case ImperialAramaicScript:
+ case InscriptionalPahlaviScript:
+ case InscriptionalParthianScript:
+ case KharoshthiScript:
+ case LydianScript:
+ case MandaeanScript:
+ case ManichaeanScript:
+ case MendeKikakuiScript:
+ case MeroiticCursiveScript:
+ case MeroiticScript:
+ case NabataeanScript:
+ case NkoScript:
+ case OldHungarianScript:
+ case OldNorthArabianScript:
+ case OldSouthArabianScript:
+ case OrkhonScript:
+ case PalmyreneScript:
+ case PhoenicianScript:
+ case PsalterPahlaviScript:
+ case SamaritanScript:
+ case SyriacScript:
+ case ThaanaScript:
return Qt::RightToLeft;
default:
break;
@@ -3384,7 +3524,9 @@ QString QCalendarBackend::dateTimeToString(QStringView format, const QDateTime &
}
const QChar c = format.at(i);
- qsizetype repeat = qt_repeatCount(format.mid(i));
+ qsizetype rep = qt_repeatCount(format.mid(i));
+ Q_ASSERT(rep < std::numeric_limits<int>::max());
+ int repeat = int(rep);
bool used = false;
if (formatDate) {
switch (c.unicode()) {
@@ -3497,29 +3639,49 @@ QString QCalendarBackend::dateTimeToString(QStringView format, const QDateTime &
break;
case 't': {
+ enum AbbrType { Long, Offset, Short };
+ const auto tzAbbr = [locale](const QDateTime &when, AbbrType type) {
+#if QT_CONFIG(timezone)
+ if (type != Short || locale != QLocale::system()) {
+ QTimeZone::NameType mode =
+ type == Short ? QTimeZone::ShortName
+ : type == Long ? QTimeZone::LongName : QTimeZone::OffsetName;
+ return when.timeRepresentation().displayName(when, mode, locale);
+ } // else: prefer QDateTime's abbreviation, for backwards-compatibility.
+#endif // else, make do with non-localized abbreviation:
+ if (type != Offset)
+ return when.timeZoneAbbreviation();
+ // For Offset, we can coerce to a UTC-based zone's abbreviation:
+ return when.toOffsetFromUtc(when.offsetFromUtc()).timeZoneAbbreviation();
+ };
used = true;
repeat = qMin(repeat, 4);
// If we don't have a date-time, use the current system time:
const QDateTime when = formatDate ? datetime : QDateTime::currentDateTime();
QString text;
switch (repeat) {
-#if QT_CONFIG(timezone)
case 4:
- text = when.timeZone().displayName(when, QTimeZone::LongName);
+ text = tzAbbr(when, Long);
break;
-#endif // timezone
- case 3:
- case 2:
- text = when.toOffsetFromUtc(when.offsetFromUtc()).timeZoneAbbreviation();
- // If the offset is UTC that'll be a Qt::UTC, otherwise Qt::OffsetFromUTC.
- Q_ASSERT(text.startsWith("UTC"_L1));
- // The Qt::UTC case omits the zero offset, which we want:
- text = text.size() == 3 ? u"+00:00"_s : text.sliced(3);
- if (repeat == 2) // +hhmm format, rather than +hh:mm format
+ case 3: // ±hh:mm
+ case 2: // ±hhmm (we'll remove the ':' at the end)
+ text = tzAbbr(when, Offset);
+ Q_ASSERT(text.startsWith("UTC"_L1)); // Need to strip this.
+ // The Qt::UTC case omits the zero offset:
+ text = (text.size() == 3
+ ? u"+00:00"_s
+ : (text.size() <= 6
+ // Whole-hour offsets may lack the zero minutes:
+ ? QStringView{text}.sliced(3) + ":00"_L1
+ : std::move(text).sliced(3)));
+ if (repeat == 2)
text = text.remove(u':');
break;
default:
- text = when.timeZoneAbbreviation();
+ text = tzAbbr(when, Short);
+ // UTC-offset zones only include minutes if non-zero.
+ if (text.startsWith("UTC"_L1) && text.size() == 6)
+ text += ":00"_L1;
break;
}
if (!text.isEmpty())
@@ -3558,7 +3720,7 @@ QString QLocaleData::doubleToString(double d, int precision, DoubleForm form,
qsizetype bufSize = 1;
if (precision == QLocale::FloatingPointShortest)
bufSize += std::numeric_limits<double>::max_digits10;
- else if (form == DFDecimal && qIsFinite(d))
+ else if (form == DFDecimal && qt_is_finite(d))
bufSize += wholePartSpace(qAbs(d)) + precision;
else // Add extra digit due to different interpretations of precision.
bufSize += qMax(2, precision) + 1; // Must also be big enough for "nan" or "inf"
@@ -3637,7 +3799,7 @@ QString QLocaleData::doubleToString(double d, int precision, DoubleForm form,
int bias = 2 + minExponentDigits;
// Decimal form may get grouping separators inserted:
if (groupDigits && decpt >= m_grouping_top + m_grouping_least)
- bias -= (decpt - m_grouping_top - m_grouping_least) / m_grouping_higher + 1;
+ bias -= (decpt - m_grouping_least) / m_grouping_higher + 1;
// X = decpt - 1 needs two digits if decpt > 10:
if (decpt > 10 && minExponentDigits == 1)
++bias;
@@ -3726,7 +3888,7 @@ QString QLocaleData::decimalForm(QString &&digits, int decpt, int precision,
qsizetype i = decpt - m_grouping_least;
if (i >= m_grouping_top) {
digits.insert(i * digitWidth, group);
- while ((i -= m_grouping_higher) >= m_grouping_top)
+ while ((i -= m_grouping_higher) > 0)
digits.insert(i * digitWidth, group);
}
}
@@ -3834,7 +3996,7 @@ QString QLocaleData::applyIntegerFormatting(QString &&numStr, bool negative, int
if (i >= m_grouping_top) {
numStr.insert(i * digitWidth, group);
++usedWidth;
- while ((i -= m_grouping_higher) >= m_grouping_top) {
+ while ((i -= m_grouping_higher) > 0) {
numStr.insert(i * digitWidth, group);
++usedWidth;
}
@@ -4063,7 +4225,8 @@ char NumericTokenizer::nextToken()
// writing Cyrillic may well use that; and Ukrainians might well use E.
// All other Cyrillic locales (officially) use plain ASCII E.
if (m_guide.exponentCyrillic // Only true in scientific float mode.
- && (tail.startsWith(u"\u0415") || tail.startsWith(u"E"))) {
+ && (tail.startsWith(u"\u0415", Qt::CaseInsensitive)
+ || tail.startsWith(u"E", Qt::CaseInsensitive))) {
++m_index;
return 'e';
}
@@ -4152,7 +4315,7 @@ bool QLocaleData::numberToCLocale(QStringView s, QLocale::NumberOptions number_o
if (last_separator_idx == -1) {
// Check distance from the beginning of the digits:
if (start_of_digits_idx == -1 || m_grouping_top > digitsInGroup
- || digitsInGroup >= m_grouping_higher + m_grouping_top) {
+ || digitsInGroup >= m_grouping_least + m_grouping_top) {
return false;
}
} else {
@@ -4198,11 +4361,12 @@ bool QLocaleData::numberToCLocale(QStringView s, QLocale::NumberOptions number_o
return true;
}
-bool QLocaleData::validateChars(QStringView str, NumberMode numMode, QByteArray *buff,
- int decDigits, QLocale::NumberOptions number_options) const
+ParsingResult
+QLocaleData::validateChars(QStringView str, NumberMode numMode, int decDigits,
+ QLocale::NumberOptions number_options) const
{
- buff->clear();
- buff->reserve(str.size());
+ ParsingResult result;
+ result.buff.reserve(str.size());
enum { Whole, Fractional, Exponent } state = Whole;
const bool scientific = numMode == DoubleScientificMode;
@@ -4220,14 +4384,14 @@ bool QLocaleData::validateChars(QStringView str, NumberMode numMode, QByteArray
case Fractional:
// If a double has too many digits in its fractional part it is Invalid.
if (decDigits-- == 0)
- return false;
+ return {};
break;
case Exponent:
if (!isAsciiDigit(last)) {
// This is the first digit in the exponent (there may have beena '+'
// or '-' in before). If it's a zero, the exponent is zero-padded.
if (c == '0' && (number_options & QLocale::RejectLeadingZeroInExponent))
- return false;
+ return {};
}
break;
}
@@ -4238,7 +4402,7 @@ bool QLocaleData::validateChars(QStringView str, NumberMode numMode, QByteArray
// If an integer has a decimal point, it is Invalid.
// A double can only have one, at the end of its whole-number part.
if (numMode == IntegerMode || state != Whole)
- return false;
+ return {};
// Even when decDigits is 0, we do allow the decimal point to be
// present - just as long as no digits follow it.
@@ -4249,14 +4413,14 @@ bool QLocaleData::validateChars(QStringView str, NumberMode numMode, QByteArray
case '-':
// A sign can only appear at the start or after the e of scientific:
if (last != '\0' && !(scientific && last == 'e'))
- return false;
+ return {};
break;
case ',':
// Grouping is only allowed after a digit in the whole-number portion:
if ((number_options & QLocale::RejectGroupSeparator) || state != Whole
|| !isAsciiDigit(last)) {
- return false;
+ return {};
}
// We could check grouping sizes are correct, but fixup()s are
// probably better off correcting any misplacement instead.
@@ -4265,7 +4429,7 @@ bool QLocaleData::validateChars(QStringView str, NumberMode numMode, QByteArray
case 'e':
// Only one e is allowed and only in scientific:
if (!scientific || state == Exponent)
- return false;
+ return {};
state = Exponent;
break;
@@ -4275,16 +4439,23 @@ bool QLocaleData::validateChars(QStringView str, NumberMode numMode, QByteArray
// validators don't accept those values.
// For anything else, tokens.nextToken() must have returned 0.
Q_ASSERT(!c || c == 'a' || c == 'f' || c == 'i' || c == 'n');
- return false;
+ return {};
}
}
last = c;
if (c != ',') // Skip grouping
- buff->append(c);
+ result.buff.append(c);
}
- return true;
+ result.state = ParsingResult::Acceptable;
+
+ // Intermediate if it ends with any character that requires a digit after
+ // it to be valid e.g. group separator, sign, or exponent
+ if (last == ',' || last == '-' || last == '+' || last == 'e')
+ result.state = ParsingResult::Intermediate;
+
+ return result;
}
double QLocaleData::stringToDouble(QStringView str, bool *ok,
@@ -4302,83 +4473,60 @@ double QLocaleData::stringToDouble(QStringView str, bool *ok,
return r.result;
}
-qlonglong QLocaleData::stringToLongLong(QStringView str, int base, bool *ok,
- QLocale::NumberOptions number_options) const
+QSimpleParsedNumber<qint64>
+QLocaleData::stringToLongLong(QStringView str, int base,
+ QLocale::NumberOptions number_options) const
{
CharBuff buff;
- if (!numberToCLocale(str, number_options, IntegerMode, &buff)) {
- if (ok != nullptr)
- *ok = false;
- return 0;
- }
+ if (!numberToCLocale(str, number_options, IntegerMode, &buff))
+ return {};
- return bytearrayToLongLong(QByteArrayView(buff.constData(), buff.size()), base, ok);
+ return bytearrayToLongLong(QByteArrayView(buff), base);
}
-qulonglong QLocaleData::stringToUnsLongLong(QStringView str, int base, bool *ok,
- QLocale::NumberOptions number_options) const
+QSimpleParsedNumber<quint64>
+QLocaleData::stringToUnsLongLong(QStringView str, int base,
+ QLocale::NumberOptions number_options) const
{
CharBuff buff;
- if (!numberToCLocale(str, number_options, IntegerMode, &buff)) {
- if (ok != nullptr)
- *ok = false;
- return 0;
- }
+ if (!numberToCLocale(str, number_options, IntegerMode, &buff))
+ return {};
- return bytearrayToUnsLongLong(QByteArrayView(buff.constData(), buff.size()), base, ok);
+ return bytearrayToUnsLongLong(QByteArrayView(buff), base);
}
-qlonglong QLocaleData::bytearrayToLongLong(QByteArrayView num, int base, bool *ok)
+static bool checkParsed(QByteArrayView num, qsizetype used)
{
- const qsizetype len = num.size();
- auto [l, used] = qstrntoll(num.data(), len, base);
- if (used <= 0) {
- if (ok != nullptr)
- *ok = false;
- return 0;
- }
+ if (used <= 0)
+ return false;
+ const qsizetype len = num.size();
if (used < len && num[used] != '\0') {
while (used < len && ascii_isspace(num[used]))
++used;
}
- if (used < len && num[used] != '\0') {
+ if (used < len && num[used] != '\0')
// we stopped at a non-digit character after converting some digits
- if (ok != nullptr)
- *ok = false;
- return 0;
- }
+ return false;
- if (ok != nullptr)
- *ok = true;
- return l;
+ return true;
}
-qulonglong QLocaleData::bytearrayToUnsLongLong(QByteArrayView num, int base, bool *ok)
+QSimpleParsedNumber<qint64> QLocaleData::bytearrayToLongLong(QByteArrayView num, int base)
{
- const qsizetype len = num.size();
- auto [l, used] = qstrntoull(num.data(), len, base);
- if (used <= 0) {
- if (ok != nullptr)
- *ok = false;
- return 0;
- }
-
- if (used < len && num[used] != '\0') {
- while (used < len && ascii_isspace(num[used]))
- ++used;
- }
-
- if (used < len && num[used] != '\0') {
- if (ok != nullptr)
- *ok = false;
- return 0;
- }
+ auto r = qstrntoll(num.data(), num.size(), base);
+ if (!checkParsed(num, r.used))
+ return {};
+ return r;
+}
- if (ok != nullptr)
- *ok = true;
- return l;
+QSimpleParsedNumber<quint64> QLocaleData::bytearrayToUnsLongLong(QByteArrayView num, int base)
+{
+ auto r = qstrntoull(num.data(), num.size(), base);
+ if (!checkParsed(num, r.used))
+ return {};
+ return r;
}
/*!
@@ -4397,7 +4545,7 @@ qulonglong QLocaleData::bytearrayToUnsLongLong(QByteArrayView num, int base, boo
\since 4.8
Returns a currency symbol according to the \a format.
*/
-QString QLocale::currencySymbol(QLocale::CurrencySymbolFormat format) const
+QString QLocale::currencySymbol(CurrencySymbolFormat format) const
{
#ifndef QT_NO_SYSTEMLOCALE
if (d->m_data == &systemLocaleData) {
@@ -4448,7 +4596,7 @@ QString QLocale::toCurrencyString(qlonglong value, const QString &symbol) const
QString str = toString(value);
QString sym = symbol.isNull() ? currencySymbol() : symbol;
if (sym.isEmpty())
- sym = currencySymbol(QLocale::CurrencyIsoCode);
+ sym = currencySymbol(CurrencyIsoCode);
return range.viewData(currency_format_data).arg(str, sym);
}
@@ -4470,7 +4618,7 @@ QString QLocale::toCurrencyString(qulonglong value, const QString &symbol) const
QString str = toString(value);
QString sym = symbol.isNull() ? currencySymbol() : symbol;
if (sym.isEmpty())
- sym = currencySymbol(QLocale::CurrencyIsoCode);
+ sym = currencySymbol(CurrencyIsoCode);
return d->m_data->currencyFormat().getData(currency_format_data).arg(str, sym);
}
@@ -4503,7 +4651,7 @@ QString QLocale::toCurrencyString(double value, const QString &symbol, int preci
QString str = toString(value, 'f', precision == -1 ? d->m_data->m_currency_digits : precision);
QString sym = symbol.isNull() ? currencySymbol() : symbol;
if (sym.isEmpty())
- sym = currencySymbol(QLocale::CurrencyIsoCode);
+ sym = currencySymbol(CurrencyIsoCode);
return range.viewData(currency_format_data).arg(str, sym);
}
@@ -4579,21 +4727,32 @@ QString QLocale::formattedDataSize(qint64 bytes, int precision, DataSizeFormats
\since 4.8
\brief List of locale names for use in selecting translations
- Each entry in the returned list is the dash-joined name of a locale,
- suitable to the user's preferences for what to translate the UI into. For
- example, if the user has configured their system to use English as used in
- the USA, the list would be "en-Latn-US", "en-US", "en". The order of entries
- is the order in which to check for translations; earlier items in the list
- are to be preferred over later ones.
+ Each entry in the returned list is the name of a locale suitable to the
+ user's preferences for what to translate the UI into. Where a name in the
+ list is composed of several tags, they are joined as indicated by \a
+ separator. Prior to Qt 6.7 a dash was used as separator.
+
+ For example, using the default separator QLocale::TagSeparator::Dash, if the
+ user has configured their system to use English as used in the USA, the list
+ would be "en-Latn-US", "en-US", "en". The order of entries is the order in
+ which to check for translations; earlier items in the list are to be
+ preferred over later ones. If your translation files use underscores, rather
+ than dashes, to separate locale tags, pass QLocale::TagSeparator::Underscore
+ as \a separator.
Most likely you do not need to use this function directly, but just pass the
QLocale object to the QTranslator::load() function.
\sa QTranslator, bcp47Name()
*/
-QStringList QLocale::uiLanguages() const
+QStringList QLocale::uiLanguages(TagSeparator separator) const
{
+ const char sep = char(separator);
QStringList uiLanguages;
+ if (uchar(sep) > 0x7f) {
+ badSeparatorWarning("uiLanguages", sep);
+ return uiLanguages;
+ }
QList<QLocaleId> localeIds;
#ifdef QT_NO_SYSTEMLOCALE
constexpr bool isSystem = false;
@@ -4601,6 +4760,11 @@ QStringList QLocale::uiLanguages() const
const bool isSystem = d->m_data == &systemLocaleData;
if (isSystem) {
uiLanguages = systemLocale()->query(QSystemLocale::UILanguages).toStringList();
+ if (separator != TagSeparator::Dash) {
+ // Map from default separator, Dash, used by backends:
+ const QChar join = QLatin1Char(sep);
+ uiLanguages = uiLanguages.replaceInStrings(u"-", QStringView(&join, 1));
+ }
// ... but we need to include likely-adjusted forms of each of those, too.
// For now, collect up locale Ids representing the entries, for later processing:
for (const auto &entry : std::as_const(uiLanguages))
@@ -4612,7 +4776,7 @@ QStringList QLocale::uiLanguages() const
// first. (Known issue, QTBUG-104930, on some macOS versions when in
// locale en_DE.) Our translation system might have a translation for a
// locale the platform doesn't believe in.
- const QString name = bcp47Name();
+ const QString name = bcp47Name(separator);
if (!name.isEmpty() && language() != C && !uiLanguages.contains(name)) {
// That uses contains(name) as a cheap pre-test, but there may be an
// entry that matches this on purging likely subtags.
@@ -4642,11 +4806,11 @@ QStringList QLocale::uiLanguages() const
j = i + 1;
} else if (id.language_id == C) {
// Attempt no likely sub-tag amendments to C:
- uiLanguages.append(QString::fromLatin1(id.name()));
+ uiLanguages.append(QString::fromLatin1(id.name(sep)));
continue;
} else {
// Plain locale or empty system uiLanguages; just append.
- prior = id.name();
+ prior = id.name(sep);
uiLanguages.append(QString::fromLatin1(prior));
j = uiLanguages.size();
}
@@ -4655,7 +4819,7 @@ QStringList QLocale::uiLanguages() const
const QLocaleId min = max.withLikelySubtagsRemoved();
// Include minimal version (last) unless it's what our locale is derived from:
- if (auto name = min.name(); name != prior)
+ if (auto name = min.name(sep); name != prior)
uiLanguages.insert(j, QString::fromLatin1(name));
else if (!isSystem)
--j; // bcp47Name() matches min(): put more specific forms *before* it.
@@ -4664,7 +4828,7 @@ QStringList QLocale::uiLanguages() const
// Include scriptless version if likely-equivalent and distinct:
id.script_id = 0;
if (id != min && id.withLikelySubtagsAdded() == max) {
- if (auto name = id.name(); name != prior)
+ if (auto name = id.name(sep); name != prior)
uiLanguages.insert(j, QString::fromLatin1(name));
}
}
@@ -4675,14 +4839,14 @@ QStringList QLocale::uiLanguages() const
// Include version with territory if it likely-equivalent and distinct:
id.territory_id = max.territory_id;
if (id != max && id.withLikelySubtagsAdded() == max) {
- if (auto name = id.name(); name != prior)
+ if (auto name = id.name(sep); name != prior)
uiLanguages.insert(j, QString::fromLatin1(name));
}
}
// Include version with all likely sub-tags (first) if distinct from the rest:
if (max != min && max != id) {
- if (auto name = max.name(); name != prior)
+ if (auto name = max.name(sep); name != prior)
uiLanguages.insert(j, QString::fromLatin1(name));
}
}