diff options
Diffstat (limited to 'src/corelib/text/qlocale.cpp')
-rw-r--r-- | src/corelib/text/qlocale.cpp | 4076 |
1 files changed, 2309 insertions, 1767 deletions
diff --git a/src/corelib/text/qlocale.cpp b/src/corelib/text/qlocale.cpp index 40defe386f..ab95b300eb 100644 --- a/src/corelib/text/qlocale.cpp +++ b/src/corelib/text/qlocale.cpp @@ -1,46 +1,14 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Copyright (C) 2019 Intel Corporation. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2022 The Qt Company Ltd. +// Copyright (C) 2021 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qglobal.h" -#if !defined(QWS) && defined(Q_OS_MAC) +#if (defined(QT_STATIC) || defined(QT_BOOTSTRAPPED)) && defined(Q_CC_GNU_ONLY) && Q_CC_GNU >= 1000 +QT_WARNING_DISABLE_GCC("-Wfree-nonheap-object") // false positive tracking +#endif + +#if defined(Q_OS_MACOS) # include "private/qcore_mac_p.h" # include <CoreFoundation/CoreFoundation.h> #endif @@ -54,6 +22,7 @@ #include "qlocale.h" #include "qlocale_p.h" #include "qlocale_tools_p.h" +#include <private/qtools_p.h> #if QT_CONFIG(datetimeparser) #include "private/qdatetimeparser_p.h" #endif @@ -61,8 +30,13 @@ #include "qdatetime.h" #include "qstringlist.h" #include "qvariant.h" +#include "qvarlengtharray.h" #include "qstringbuilder.h" +#if QT_CONFIG(timezone) +# include "qtimezone.h" +#endif #include "private/qnumeric_p.h" +#include "private/qtools_p.h" #include <cmath> #ifndef QT_NO_SYSTEMLOCALE # include "qmutex.h" @@ -76,20 +50,40 @@ #include "private/qgregoriancalendar_p.h" #include "qcalendar.h" +#include <q20iterator.h> + QT_BEGIN_NAMESPACE +constexpr int QLocale::DefaultTwoDigitBaseYear; + +QT_IMPL_METATYPE_EXTERN_TAGGED(QList<Qt::DayOfWeek>, QList_Qt__DayOfWeek) #ifndef QT_NO_SYSTEMLOCALE -static QSystemLocale *_systemLocale = nullptr; -class QSystemLocaleSingleton: public QSystemLocale -{ -public: - QSystemLocaleSingleton() : QSystemLocale(true) {} -}; +QT_IMPL_METATYPE_EXTERN_TAGGED(QSystemLocale::CurrencyToStringArgument, + QSystemLocale__CurrencyToStringArgument) +#endif + +using namespace Qt::StringLiterals; +using namespace QtMiscUtils; -Q_GLOBAL_STATIC(QSystemLocaleSingleton, QSystemLocale_globalSystemLocale) -static QLocaleData globalLocaleData; +#ifndef QT_NO_SYSTEMLOCALE +Q_CONSTINIT static QSystemLocale *_systemLocale = nullptr; +Q_CONSTINIT static QLocaleData systemLocaleData = {}; #endif +static_assert(ascii_isspace(' ')); +static_assert(ascii_isspace('\t')); +static_assert(ascii_isspace('\n')); +static_assert(ascii_isspace('\v')); +static_assert(ascii_isspace('\f')); +static_assert(ascii_isspace('\r')); +static_assert(!ascii_isspace('\0')); +static_assert(!ascii_isspace('\a')); +static_assert(!ascii_isspace('a')); +static_assert(!ascii_isspace('\177')); +static_assert(!ascii_isspace(uchar('\200'))); +static_assert(!ascii_isspace(uchar('\xA0'))); // NBSP (is a space but Latin 1, not ASCII) +static_assert(!ascii_isspace(uchar('\377'))); + /****************************************************************************** ** Helpers for accessing Qt locale database */ @@ -98,39 +92,67 @@ QT_BEGIN_INCLUDE_NAMESPACE #include "qlocale_data_p.h" QT_END_INCLUDE_NAMESPACE -QLocale::Language QLocalePrivate::codeToLanguage(QStringView code) noexcept +QLocale::Language QLocalePrivate::codeToLanguage(QStringView code, + QLocale::LanguageCodeTypes codeTypes) noexcept { const auto len = code.size(); if (len != 2 && len != 3) - return QLocale::C; - ushort uc1 = code[0].toLower().unicode(); - ushort uc2 = code[1].toLower().unicode(); - ushort uc3 = len > 2 ? code[2].toLower().unicode() : 0; + return QLocale::AnyLanguage; - const unsigned char *c = language_code_list; - for (; *c != 0; c += 3) { - if (uc1 == c[0] && uc2 == c[1] && uc3 == c[2]) - return QLocale::Language((c - language_code_list)/3); + const char16_t uc1 = code[0].toLower().unicode(); + const char16_t uc2 = code[1].toLower().unicode(); + const char16_t uc3 = len > 2 ? code[2].toLower().unicode() : 0; + + // All language codes are ASCII. + if (uc1 > 0x7F || uc2 > 0x7F || uc3 > 0x7F) + return QLocale::AnyLanguage; + + const AlphaCode codeBuf = { char(uc1), char(uc2), char(uc3) }; + + auto searchCode = [codeBuf](auto f) { + return std::find_if(languageCodeList.begin(), languageCodeList.end(), + [=](LanguageCodeEntry i) { return f(i) == codeBuf; }); + }; + + if (codeTypes.testFlag(QLocale::ISO639Part1) && uc3 == 0) { + auto i = searchCode([](LanguageCodeEntry i) { return i.part1; }); + if (i != languageCodeList.end()) + return QLocale::Language(std::distance(languageCodeList.begin(), i)); } - if (uc3 == 0) { - // legacy codes - if (uc1 == 'n' && uc2 == 'o') { // no -> nb - static_assert(QLocale::Norwegian == QLocale::NorwegianBokmal); - return QLocale::Norwegian; + if (uc3 != 0) { + if (codeTypes.testFlag(QLocale::ISO639Part2B)) { + auto i = searchCode([](LanguageCodeEntry i) { return i.part2B; }); + if (i != languageCodeList.end()) + return QLocale::Language(std::distance(languageCodeList.begin(), i)); } - if (uc1 == 't' && uc2 == 'l') { // tl -> fil - static_assert(QLocale::Tagalog == QLocale::Filipino); - return QLocale::Tagalog; - } - if (uc1 == 's' && uc2 == 'h') { // sh -> sr[_Latn] - static_assert(QLocale::SerboCroatian == QLocale::Serbian); - return QLocale::SerboCroatian; + + // Optimization: Part 2T code if present is always the same as Part 3 code. + // This is asserted in iso639_3.LanguageCodeData. + if (codeTypes.testFlag(QLocale::ISO639Part2T) + && !codeTypes.testFlag(QLocale::ISO639Part3)) { + auto i = searchCode([](LanguageCodeEntry i) { return i.part2T; }); + if (i != languageCodeList.end()) + return QLocale::Language(std::distance(languageCodeList.begin(), i)); } - if (uc1 == 'm' && uc2 == 'o') { // mo -> ro - static_assert(QLocale::Moldavian == QLocale::Romanian); - return QLocale::Moldavian; + + if (codeTypes.testFlag(QLocale::ISO639Part3)) { + auto i = searchCode([](LanguageCodeEntry i) { return i.part3; }); + if (i != languageCodeList.end()) + return QLocale::Language(std::distance(languageCodeList.begin(), i)); } + } + + if (codeTypes.testFlag(QLocale::LegacyLanguageCode) && uc3 == 0) { + // legacy codes + if (uc1 == 'n' && uc2 == 'o') // no -> nb + return QLocale::NorwegianBokmal; + if (uc1 == 't' && uc2 == 'l') // tl -> fil + return QLocale::Filipino; + if (uc1 == 's' && uc2 == 'h') // sh -> sr[_Latn] + return QLocale::Serbian; + if (uc1 == 'm' && uc2 == 'o') // mo -> ro + return QLocale::Romanian; // Android uses the following deprecated codes if (uc1 == 'i' && uc2 == 'w') // iw -> he return QLocale::Hebrew; @@ -139,7 +161,7 @@ QLocale::Language QLocalePrivate::codeToLanguage(QStringView code) noexcept if (uc1 == 'j' && uc2 == 'i') // ji -> yi return QLocale::Yiddish; } - return QLocale::C; + return QLocale::AnyLanguage; } QLocale::Script QLocalePrivate::codeToScript(QStringView code) noexcept @@ -155,118 +177,203 @@ QLocale::Script QLocalePrivate::codeToScript(QStringView code) noexcept unsigned char c3 = code[3].toLower().toLatin1(); const unsigned char *c = script_code_list; - for (int i = 0; i < QLocale::LastScript; ++i, c += 4) { + for (qsizetype i = 0; i < QLocale::LastScript; ++i, c += 4) { if (c0 == c[0] && c1 == c[1] && c2 == c[2] && c3 == c[3]) return QLocale::Script(i); } return QLocale::AnyScript; } -QLocale::Country QLocalePrivate::codeToCountry(QStringView code) noexcept +QLocale::Territory QLocalePrivate::codeToTerritory(QStringView code) noexcept { const auto len = code.size(); if (len != 2 && len != 3) - return QLocale::AnyCountry; + return QLocale::AnyTerritory; - ushort uc1 = code[0].toUpper().unicode(); - ushort uc2 = code[1].toUpper().unicode(); - ushort uc3 = len > 2 ? code[2].toUpper().unicode() : 0; + char16_t uc1 = code[0].toUpper().unicode(); + char16_t uc2 = code[1].toUpper().unicode(); + char16_t uc3 = len > 2 ? code[2].toUpper().unicode() : 0; - const unsigned char *c = country_code_list; + const unsigned char *c = territory_code_list; for (; *c != 0; c += 3) { if (uc1 == c[0] && uc2 == c[1] && uc3 == c[2]) - return QLocale::Country((c - country_code_list)/3); + return QLocale::Territory((c - territory_code_list)/3); } - return QLocale::AnyCountry; + return QLocale::AnyTerritory; } -QLatin1String QLocalePrivate::languageToCode(QLocale::Language language) +std::array<char, 4> QLocalePrivate::languageToCode(QLocale::Language language, + QLocale::LanguageCodeTypes codeTypes) { - if (language == QLocale::AnyLanguage) - return QLatin1String(); + if (language == QLocale::AnyLanguage || language > QLocale::LastLanguage) + return {}; if (language == QLocale::C) - return QLatin1String("C"); + return {'C'}; + + const LanguageCodeEntry &i = languageCodeList[language]; + + if (codeTypes.testFlag(QLocale::ISO639Part1) && i.part1.isValid()) + return i.part1.decode(); - const unsigned char *c = language_code_list + 3*(uint(language)); + if (codeTypes.testFlag(QLocale::ISO639Part2B) && i.part2B.isValid()) + return i.part2B.decode(); - return QLatin1String(reinterpret_cast<const char*>(c), c[2] == 0 ? 2 : 3); + if (codeTypes.testFlag(QLocale::ISO639Part2T) && i.part2T.isValid()) + return i.part2T.decode(); + if (codeTypes.testFlag(QLocale::ISO639Part3)) + return i.part3.decode(); + + return {}; } -QLatin1String QLocalePrivate::scriptToCode(QLocale::Script script) +QLatin1StringView QLocalePrivate::scriptToCode(QLocale::Script script) { if (script == QLocale::AnyScript || script > QLocale::LastScript) - return QLatin1String(); - const unsigned char *c = script_code_list + 4*(uint(script)); - return QLatin1String(reinterpret_cast<const char *>(c), 4); + return {}; + const unsigned char *c = script_code_list + 4 * script; + return {reinterpret_cast<const char *>(c), 4}; } -QLatin1String QLocalePrivate::countryToCode(QLocale::Country country) +QLatin1StringView QLocalePrivate::territoryToCode(QLocale::Territory territory) { - if (country == QLocale::AnyCountry) - return QLatin1String(); - - const unsigned char *c = country_code_list + 3*(uint(country)); + if (territory == QLocale::AnyTerritory || territory > QLocale::LastTerritory) + return {}; - return QLatin1String(reinterpret_cast<const char*>(c), c[2] == 0 ? 2 : 3); + const unsigned char *c = territory_code_list + 3 * territory; + return {reinterpret_cast<const char*>(c), c[2] == 0 ? 2 : 3}; } -// http://www.unicode.org/reports/tr35/#Likely_Subtags -static bool addLikelySubtags(QLocaleId &localeId) +namespace { +struct LikelyPair { - // ### optimize with bsearch - const int likely_subtags_count = sizeof(likely_subtags) / sizeof(likely_subtags[0]); - const QLocaleId *p = likely_subtags; - const QLocaleId *const e = p + likely_subtags_count; - for ( ; p < e; p += 2) { - if (localeId == p[0]) { - localeId = p[1]; - return true; - } - } - return false; -} + QLocaleId key; // Search key. + QLocaleId value = QLocaleId { 0, 0, 0 }; +}; + +bool operator<(LikelyPair lhs, LikelyPair rhs) +{ + // Must match the comparison LocaleDataWriter.likelySubtags() uses when + // sorting, see qtbase/util/locale_database.qlocalexml2cpp.py + const auto compare = [](int lhs, int rhs) { + // 0 sorts after all other values; lhs and rhs are passed ushort values. + const int huge = 0x10000; + return (lhs ? lhs : huge) - (rhs ? rhs : huge); + }; + const auto &left = lhs.key; + const auto &right = rhs.key; + // Comparison order: language, region, script: + if (int cmp = compare(left.language_id, right.language_id)) + return cmp < 0; + if (int cmp = compare(left.territory_id, right.territory_id)) + return cmp < 0; + return compare(left.script_id, right.script_id) < 0; +} +} // anonymous namespace +/*! + Fill in blank fields of a locale ID. + + An ID in which some fields are zero stands for any locale that agrees with + it in its non-zero fields. CLDR's likely-subtag data is meant to help us + chose which candidate to prefer. (Note, however, that CLDR does have some + cases where it maps an ID to a "best match" for which CLDR does not provide + data, even though there are locales for which CLDR does provide data that do + match the given ID. It's telling us, unhelpfully but truthfully, what + locale would (most likely) be meant by (someone using) the combination + requested, even when that locale isn't yet supported.) It may also map an + obsolete or generic tag to a modern or more specific replacement, possibly + filling in some of the other fields in the process (presently only for + countries). Note that some fields of the result may remain blank, but there + is no more specific recommendation available. + + For the formal specification, see + https://www.unicode.org/reports/tr35/#Likely_Subtags + + \note We also search und_script_region and und_region; they're not mentioned + in the spec, but the examples clearly presume them and CLDR does provide + such likely matches. +*/ QLocaleId QLocaleId::withLikelySubtagsAdded() const { - // language_script_region - if (language_id || script_id || country_id) { - QLocaleId id = QLocaleId::fromIds(language_id, script_id, country_id); - if (addLikelySubtags(id)) - return id; - } - // language_region - if (script_id) { - QLocaleId id = QLocaleId::fromIds(language_id, 0, country_id); - if (addLikelySubtags(id)) { - id.script_id = script_id; - return id; + /* Each pattern that appears in a comments below, language_script_region and + similar, indicates which of this's fields (even if blank) are being + attended to in a given search; for fields left out of the pattern, the + search uses 0 regardless of whether this has specified the field. + + If a key matches what we're searching for (possibly with a wildcard in + the key matching a non-wildcard in our search), the tags from this that + are specified in the key are replaced by the match (even if different); + but the other tags of this replace what's in the match (even when the + match does specify a value). + */ + static_assert(std::size(likely_subtags) % 2 == 0); + auto *pairs = reinterpret_cast<const LikelyPair *>(likely_subtags); + auto *const afterPairs = pairs + std::size(likely_subtags) / 2; + LikelyPair sought { *this }; + // Our array is sorted in the order that puts all candidate matches in the + // order we would want them; ones we should prefer appear before the others. + if (language_id) { + // language_script_region, language_region, language_script, language: + pairs = std::lower_bound(pairs, afterPairs, sought); + // Single language's block isn't long enough to warrant more binary + // chopping within it - just traverse it all: + for (; pairs < afterPairs && pairs->key.language_id == language_id; ++pairs) { + const QLocaleId &key = pairs->key; + if (key.territory_id && key.territory_id != territory_id) + continue; + if (key.script_id && key.script_id != script_id) + continue; + QLocaleId value = pairs->value; + if (territory_id && !key.territory_id) + value.territory_id = territory_id; + if (script_id && !key.script_id) + value.script_id = script_id; + return value; } } - // language_script - if (country_id) { - QLocaleId id = QLocaleId::fromIds(language_id, script_id, 0); - if (addLikelySubtags(id)) { - id.country_id = country_id; - return id; + // und_script_region or und_region (in that order): + if (territory_id) { + sought.key = QLocaleId { 0, script_id, territory_id }; + pairs = std::lower_bound(pairs, afterPairs, sought); + // Again, individual und_?_region block isn't long enough to make binary + // chop a win: + for (; pairs < afterPairs && pairs->key.territory_id == territory_id; ++pairs) { + const QLocaleId &key = pairs->key; + Q_ASSERT(!key.language_id); + if (key.script_id && key.script_id != script_id) + continue; + QLocaleId value = pairs->value; + if (language_id) + value.language_id = language_id; + if (script_id && !key.script_id) + value.script_id = script_id; + return value; } } - // language - if (script_id && country_id) { - QLocaleId id = QLocaleId::fromIds(language_id, 0, 0); - if (addLikelySubtags(id)) { - id.script_id = script_id; - id.country_id = country_id; - return id; + // und_script: + if (script_id) { + sought.key = QLocaleId { 0, script_id, 0 }; + pairs = std::lower_bound(pairs, afterPairs, sought); + if (pairs < afterPairs && pairs->key.script_id == script_id) { + Q_ASSERT(!pairs->key.language_id && !pairs->key.territory_id); + QLocaleId value = pairs->value; + if (language_id) + value.language_id = language_id; + if (territory_id) + value.territory_id = territory_id; + return value; } } - // und_script - if (language_id) { - QLocaleId id = QLocaleId::fromIds(0, script_id, 0); - if (addLikelySubtags(id)) { - id.language_id = language_id; - return id; + if (matchesAll()) { // Skipped all of the above. + // CLDR has no match-all at v37, but might get one some day ... + pairs = std::lower_bound(pairs, afterPairs, sought); + if (pairs < afterPairs) { + // All other keys are < match-all. + Q_ASSERT(pairs + 1 == afterPairs); + Q_ASSERT(pairs->key.matchesAll()); + return pairs->value; } } return *this; @@ -277,19 +384,19 @@ QLocaleId QLocaleId::withLikelySubtagsRemoved() const QLocaleId max = withLikelySubtagsAdded(); // language { - QLocaleId id = QLocaleId::fromIds(language_id, 0, 0); + QLocaleId id { language_id, 0, 0 }; if (id.withLikelySubtagsAdded() == max) return id; } // language_region - if (country_id) { - QLocaleId id = QLocaleId::fromIds(language_id, 0, country_id); + if (territory_id) { + QLocaleId id { language_id, 0, territory_id }; if (id.withLikelySubtagsAdded() == max) return id; } // language_script if (script_id) { - QLocaleId id = QLocaleId::fromIds(language_id, script_id, 0); + QLocaleId id { language_id, script_id, 0 }; if (id.withLikelySubtagsAdded() == max) return id; } @@ -303,19 +410,34 @@ QByteArray QLocaleId::name(char separator) const if (language_id == QLocale::C) return QByteArrayLiteral("C"); - const unsigned char *lang = language_code_list + 3 * language_id; + const LanguageCodeEntry &language = languageCodeList[language_id]; + AlphaCode lang; + qsizetype langLen; + + if (language.part1.isValid()) { + lang = language.part1; + langLen = 2; + } else { + lang = language.part2B.isValid() ? language.part2B : language.part3; + langLen = 3; + } + const unsigned char *script = (script_id != QLocale::AnyScript ? script_code_list + 4 * script_id : nullptr); const unsigned char *country = - (country_id != QLocale::AnyCountry ? country_code_list + 3 * country_id : nullptr); - char len = (lang[2] != 0 ? 3 : 2) + (script ? 4 + 1 : 0) - + (country ? (country[2] != 0 ? 3 : 2) + 1 : 0); + (territory_id != QLocale::AnyTerritory + ? territory_code_list + 3 * territory_id : nullptr); + qsizetype len = langLen + (script ? 4 + 1 : 0) + (country ? (country[2] != 0 ? 3 : 2) + 1 : 0); QByteArray name(len, Qt::Uninitialized); char *uc = name.data(); - *uc++ = lang[0]; - *uc++ = lang[1]; - if (lang[2] != 0) - *uc++ = lang[2]; + + auto langArray = lang.decode(); + + *uc++ = langArray[0]; + *uc++ = langArray[1]; + if (langLen > 2) + *uc++ = langArray[2]; + if (script) { *uc++ = separator; *uc++ = script[0]; @@ -340,275 +462,186 @@ QByteArray QLocalePrivate::bcp47Name(char separator) const if (m_data->m_language_id == QLocale::C) return QByteArrayLiteral("en"); - QLocaleId localeId = QLocaleId::fromIds(m_data->m_language_id, m_data->m_script_id, - m_data->m_country_id); - return localeId.withLikelySubtagsRemoved().name(separator); + return m_data->id().withLikelySubtagsRemoved().name(separator); } -/*! - \internal - */ -QByteArray QLocalePrivate::rawName(char separator) const +static qsizetype findLocaleIndexById(QLocaleId localeId) { - QByteArrayList parts; - if (m_data->m_language_id != QLocale::AnyLanguage) - parts.append(languageCode().latin1()); - if (m_data->m_script_id != QLocale::AnyScript) - parts.append(scriptCode().latin1()); - if (m_data->m_country_id != QLocale::AnyCountry) - parts.append(countryCode().latin1()); - - return parts.join(separator); -} - - -static const QLocaleData *findLocaleDataById(const QLocaleId &lid) -{ - QLocaleId localeId = lid.withLikelySubtagsAdded(); - - const uint idx = locale_index[localeId.language_id]; - - const QLocaleData *data = locale_data + idx; - - if (idx == 0) // default language has no associated script or country - return data; + qsizetype idx = locale_index[localeId.language_id]; + // If there are no locales for specified language (so we we've got the + // default language, which has no associated script or country), give up: + if (localeId.language_id && idx == 0) + return idx; - Q_ASSERT(data->m_language_id == localeId.language_id); + Q_ASSERT(localeId.acceptLanguage(locale_data[idx].m_language_id)); - if (localeId.script_id == QLocale::AnyScript && localeId.country_id == QLocale::AnyCountry) - return data; + do { + if (localeId.acceptScriptTerritory(locale_data[idx].id())) + return idx; + ++idx; + } while (localeId.acceptLanguage(locale_data[idx].m_language_id)); - if (localeId.script_id == QLocale::AnyScript) { - do { - if (data->m_country_id == localeId.country_id) - return data; - ++data; - } while (data->m_language_id && data->m_language_id == localeId.language_id); - } else if (localeId.country_id == QLocale::AnyCountry) { - do { - if (data->m_script_id == localeId.script_id) - return data; - ++data; - } while (data->m_language_id && data->m_language_id == localeId.language_id); - } else { - do { - if (data->m_script_id == localeId.script_id - && data->m_country_id == localeId.country_id) { - return data; - } - ++data; - } while (data->m_language_id && data->m_language_id == localeId.language_id); - } - - return nullptr; + return -1; } -const QLocaleData *QLocaleData::findLocaleData(QLocale::Language language, QLocale::Script script, - QLocale::Country country) +qsizetype QLocaleData::findLocaleIndex(QLocaleId lid) { - QLocaleId localeId = QLocaleId::fromIds(language, script, country); + QLocaleId localeId = lid; QLocaleId likelyId = localeId.withLikelySubtagsAdded(); - - const uint idx = locale_index[likelyId.language_id]; + const ushort fallback = likelyId.language_id; // Try a straight match with the likely data: - if (const QLocaleData *const data = findLocaleDataById(likelyId)) - return data; - QList<QLocaleId> tried; + qsizetype index = findLocaleIndexById(likelyId); + if (index >= 0) + return index; + QVarLengthArray<QLocaleId, 6> tried; tried.push_back(likelyId); +#define CheckCandidate(id) do { \ + if (!tried.contains(id)) { \ + index = findLocaleIndexById(id); \ + if (index >= 0) \ + return index; \ + tried.push_back(id); \ + } \ + } while (false) // end CheckCandidate + // No match; try again with raw data: - if (!tried.contains(localeId)) { - if (const QLocaleData *const data = findLocaleDataById(localeId)) - return data; - tried.push_back(localeId); - } + CheckCandidate(localeId); - // No match; try again with likely country - if (country != QLocale::AnyCountry - && (language != QLocale::AnyLanguage || script != QLocale::AnyScript)) { - localeId = QLocaleId::fromIds(language, script, QLocale::AnyCountry); + // No match; try again with likely country for language_script + if (lid.territory_id && (lid.language_id || lid.script_id)) { + localeId.territory_id = 0; likelyId = localeId.withLikelySubtagsAdded(); - if (!tried.contains(likelyId)) { - if (const QLocaleData *const data = findLocaleDataById(likelyId)) - return data; - tried.push_back(likelyId); - } + CheckCandidate(likelyId); // No match; try again with any country - if (!tried.contains(localeId)) { - if (const QLocaleData *const data = findLocaleDataById(localeId)) - return data; - tried.push_back(localeId); - } + CheckCandidate(localeId); } - // No match; try again with likely script - if (script != QLocale::AnyScript - && (language != QLocale::AnyLanguage || country != QLocale::AnyCountry)) { - localeId = QLocaleId::fromIds(language, QLocale::AnyScript, country); + // No match; try again with likely script for language_region + if (lid.script_id && (lid.language_id || lid.territory_id)) { + localeId = QLocaleId { lid.language_id, 0, lid.territory_id }; likelyId = localeId.withLikelySubtagsAdded(); - if (!tried.contains(likelyId)) { - if (const QLocaleData *const data = findLocaleDataById(likelyId)) - return data; - tried.push_back(likelyId); - } + CheckCandidate(likelyId); // No match; try again with any script - if (!tried.contains(localeId)) { - if (const QLocaleData *const data = findLocaleDataById(localeId)) - return data; - tried.push_back(localeId); - } + CheckCandidate(localeId); } +#undef CheckCandidate - // No match; return data at original index - return locale_data + idx; + // No match; return base index for initial likely language: + return locale_index[fallback]; } -uint QLocaleData::findLocaleOffset(QLocale::Language language, QLocale::Script script, - QLocale::Country country) +static QStringView findTag(QStringView name) noexcept { - return findLocaleData(language, script, country) - locale_data; + const std::u16string_view v(name.utf16(), size_t(name.size())); + const auto i = v.find_first_of(u"_-.@"); + if (i == std::string_view::npos) + return name; + return name.first(qsizetype(i)); } -static bool parse_locale_tag(const QString &input, int &i, QString *result, - const QString &separators) +static bool validTag(QStringView tag) { - *result = QString(8, Qt::Uninitialized); // worst case according to BCP47 - QChar *pch = result->data(); - const QChar *uc = input.data() + i; - const int l = input.length(); - int size = 0; - for (; i < l && size < 8; ++i, ++size) { - if (separators.contains(*uc)) - break; - if (! ((uc->unicode() >= 'a' && uc->unicode() <= 'z') || - (uc->unicode() >= 'A' && uc->unicode() <= 'Z') || - (uc->unicode() >= '0' && uc->unicode() <= '9')) ) // latin only + // Is tag is a non-empty sequence of ASCII letters and/or digits ? + for (QChar uc : tag) { + const char16_t ch = uc.unicode(); + if (!isAsciiLetterOrNumber(ch)) return false; - *pch++ = *uc++; } - result->truncate(size); - return true; + return tag.size() > 0; } -bool qt_splitLocaleName(const QString &name, QString &lang, QString &script, QString &cntry) +static bool isScript(QStringView tag) { - const int length = name.length(); - - lang = script = cntry = QString(); + // Every script name is 4 characters, a capital followed by three lower-case; + // so a search for tag in allScripts *can* only match if it's aligned. + static const QString allScripts = + QString::fromLatin1(reinterpret_cast<const char *>(script_code_list), + sizeof(script_code_list) - 1); + return tag.size() == 4 && allScripts.indexOf(tag) % 4 == 0; +} - const QString separators = QStringLiteral("_-.@"); +bool qt_splitLocaleName(QStringView name, QStringView *lang, QStringView *script, QStringView *land) +{ + // Assume each of lang, script and land is nullptr or points to an empty QStringView. enum ParserState { NoState, LangState, ScriptState, CountryState }; ParserState state = LangState; - for (int i = 0; i < length && state != NoState; ) { - QString value; - if (!parse_locale_tag(name, i, &value, separators) ||value.isEmpty()) + while (name.size() && state != NoState) { + const QStringView tag = findTag(name); + if (!validTag(tag)) break; - QChar sep = i < length ? name.at(i) : QChar(); + name = name.sliced(tag.size()); + const bool sep = name.size() > 0; + if (sep) // tag wasn't all that remained; there was a separator + name = name.sliced(1); + switch (state) { case LangState: - if (!sep.isNull() && !separators.contains(sep)) { - state = NoState; - break; - } - lang = value; - if (i == length) { - // just language was specified - state = NoState; - break; - } - state = ScriptState; + if (tag.size() != 2 && tag.size() != 3) + return false; + if (lang) + *lang = tag; + state = sep ? ScriptState : NoState; break; - case ScriptState: { - QString scripts = QString::fromLatin1((const char *)script_code_list, - sizeof(script_code_list) - 1); - if (value.length() == 4 && scripts.indexOf(value) % 4 == 0) { - // script name is always 4 characters - script = value; - state = CountryState; - } else { - // it wasn't a script, maybe it is a country then? - cntry = value; - state = NoState; + case ScriptState: + if (isScript(tag)) { + if (script) + *script = tag; + state = sep ? CountryState : NoState; + break; } - break; - } + // It wasn't a script, assume it's a country. + Q_FALLTHROUGH(); case CountryState: - cntry = value; + if (land) + *land = tag; state = NoState; break; - case NoState: - // shouldn't happen - qWarning("QLocale: This should never happen"); + case NoState: // Precluded by loop condition ! + Q_UNREACHABLE(); break; } - ++i; } - return lang.length() == 2 || lang.length() == 3; + return state != LangState; } -void QLocalePrivate::getLangAndCountry(const QString &name, QLocale::Language &lang, - QLocale::Script &script, QLocale::Country &cntry) +QLocaleId QLocaleId::fromName(QStringView name) { - lang = QLocale::C; - script = QLocale::AnyScript; - cntry = QLocale::AnyCountry; + QStringView lang; + QStringView script; + QStringView land; + if (!qt_splitLocaleName(name, &lang, &script, &land)) + return { QLocale::C, 0, 0 }; - QString lang_code; - QString script_code; - QString cntry_code; - if (!qt_splitLocaleName(name, lang_code, script_code, cntry_code)) - return; - - lang = QLocalePrivate::codeToLanguage(lang_code); - if (lang == QLocale::C) - return; - script = QLocalePrivate::codeToScript(script_code); - cntry = QLocalePrivate::codeToCountry(cntry_code); + QLocale::Language langId = QLocalePrivate::codeToLanguage(lang); + if (langId == QLocale::AnyLanguage) + return { QLocale::C, 0, 0 }; + return { langId, QLocalePrivate::codeToScript(script), QLocalePrivate::codeToTerritory(land) }; } -static const QLocaleData *findLocaleData(const QString &name) +QString qt_readEscapedFormatString(QStringView format, qsizetype *idx) { - QLocale::Language lang; - QLocale::Script script; - QLocale::Country cntry; - QLocalePrivate::getLangAndCountry(name, lang, script, cntry); - - return QLocaleData::findLocaleData(lang, script, cntry); -} + qsizetype &i = *idx; -static uint findLocaleOffset(const QString &name) -{ - QLocale::Language lang; - QLocale::Script script; - QLocale::Country cntry; - QLocalePrivate::getLangAndCountry(name, lang, script, cntry); - - return QLocaleData::findLocaleOffset(lang, script, cntry); -} - -QString qt_readEscapedFormatString(QStringView format, int *idx) -{ - int &i = *idx; - - Q_ASSERT(format.at(i) == QLatin1Char('\'')); + Q_ASSERT(format.at(i) == u'\''); ++i; if (i == format.size()) return QString(); - if (format.at(i).unicode() == '\'') { // "''" outside of a quoted stirng + if (format.at(i).unicode() == '\'') { // "''" outside of a quoted string ++i; - return QLatin1String("'"); + return "'"_L1; } QString result; while (i < format.size()) { if (format.at(i).unicode() == '\'') { - if (format.mid(i + 1).startsWith(QLatin1Char('\''))) { + if (format.mid(i + 1).startsWith(u'\'')) { // "''" inside a quoted string - result.append(QLatin1Char('\'')); + result.append(u'\''); i += 2; } else { break; @@ -639,7 +672,7 @@ QString qt_readEscapedFormatString(QStringView format, int *idx) qt_repeatCount(u"aab"); // == 2 \endcode */ -int qt_repeatCount(QStringView s) +qsizetype qt_repeatCount(QStringView s) { if (s.isEmpty()) return 0; @@ -647,60 +680,66 @@ int qt_repeatCount(QStringView s) qsizetype j = 1; while (j < s.size() && s.at(j) == c) ++j; - return int(j); + return j; } -static const QLocaleData *default_data = nullptr; +Q_CONSTINIT static const QLocaleData *default_data = nullptr; +Q_CONSTINIT QBasicAtomicInt QLocalePrivate::s_generation = Q_BASIC_ATOMIC_INITIALIZER(0); -static const QLocaleData *const c_data = locale_data; static QLocalePrivate *c_private() { - static QLocalePrivate c_locale{ - c_data, Q_BASIC_ATOMIC_INITIALIZER(1), 0, QLocale::OmitGroupSeparator }; + static QLocalePrivate c_locale(locale_data, 0, QLocale::OmitGroupSeparator, 1); return &c_locale; } -static const QLocaleData *systemData(); -Q_GLOBAL_STATIC_WITH_ARGS(QExplicitlySharedDataPointer<QLocalePrivate>, systemLocalePrivate, - (QLocalePrivate::create(systemData()))) - #ifndef QT_NO_SYSTEMLOCALE /****************************************************************************** ** Default system locale behavior */ /*! - Constructs a QSystemLocale object. - - The constructor will automatically install this object as the system locale, - if there's not one active. It also resets the flag that'll prompt - QLocale::system() to re-initialize its data, so that instantiating a - QSystemLocale transiently (doesn't install the transient as system locale if - there was one already and) triggers an update to the system locale's data. + \internal + Constructs a QSystemLocale object. + + The constructor will automatically install this object as the system locale. + It and the destructor maintain a stack of system locales, with the + most-recently-created instance (that hasn't yet been deleted) used as the + 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 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. + + This constructor also resets the flag that'll prompt QLocale::system() to + re-initialize its data, so that instantiating a QSystemLocale (even + transiently) triggers a refresh of the system locale's data. This is + exploited by some test code. */ -QSystemLocale::QSystemLocale() +QSystemLocale::QSystemLocale() : next(_systemLocale) { - if (!_systemLocale) - _systemLocale = this; + _systemLocale = this; - globalLocaleData.m_language_id = 0; + systemLocaleData.m_language_id = 0; } /*! \internal -*/ -QSystemLocale::QSystemLocale(bool) -{ } - -/*! - Deletes the object. + Deletes the object. */ QSystemLocale::~QSystemLocale() { if (_systemLocale == this) { - _systemLocale = nullptr; + _systemLocale = next; - globalLocaleData.m_language_id = 0; + // Change to system locale => force refresh. + systemLocaleData.m_language_id = 0; + } else { + for (QSystemLocale *p = _systemLocale; p; p = p->next) { + if (p->next == this) + p->next = next; + } } } @@ -708,7 +747,12 @@ static const QSystemLocale *systemLocale() { if (_systemLocale) return _systemLocale; - return QSystemLocale_globalSystemLocale(); + + // As this is only ever instantiated with _systemLocale null, it is + // necessarily the ->next-most in any chain that may subsequently develop; + // and it won't be destructed until exit()-time. + static QSystemLocale globalInstance; + return &globalInstance; } static void updateSystemPrivate() @@ -719,48 +763,69 @@ static void updateSystemPrivate() const QSystemLocale *sys_locale = systemLocale(); // tell the object that the system locale has changed. - sys_locale->query(QSystemLocale::LocaleChanged, QVariant()); + sys_locale->query(QSystemLocale::LocaleChanged); - // Populate global with fallback as basis: - globalLocaleData = *sys_locale->fallbackUiLocaleData(); + // Populate system locale with fallback as basis + systemLocaleData = locale_data[sys_locale->fallbackLocaleIndex()]; - QVariant res = sys_locale->query(QSystemLocale::LanguageId, QVariant()); + QVariant res = sys_locale->query(QSystemLocale::LanguageId); if (!res.isNull()) { - globalLocaleData.m_language_id = res.toInt(); - globalLocaleData.m_script_id = QLocale::AnyScript; // default for compatibility + systemLocaleData.m_language_id = res.toInt(); + systemLocaleData.m_script_id = QLocale::AnyScript; // default for compatibility } - res = sys_locale->query(QSystemLocale::CountryId, QVariant()); + res = sys_locale->query(QSystemLocale::TerritoryId); if (!res.isNull()) { - globalLocaleData.m_country_id = res.toInt(); - globalLocaleData.m_script_id = QLocale::AnyScript; // default for compatibility + systemLocaleData.m_territory_id = res.toInt(); + systemLocaleData.m_script_id = QLocale::AnyScript; // default for compatibility } - res = sys_locale->query(QSystemLocale::ScriptId, QVariant()); + res = sys_locale->query(QSystemLocale::ScriptId); if (!res.isNull()) - globalLocaleData.m_script_id = res.toInt(); + 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. */ { - static QBasicMutex systemDataMutex; + Q_CONSTINIT static QLocaleId sysId; + bool updated = false; + + Q_CONSTINIT static QBasicMutex systemDataMutex; systemDataMutex.lock(); - if (globalLocaleData.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 &globalLocaleData; + return &systemLocaleData; #else + Q_UNUSED(sysIndex); return locale_data; #endif } @@ -772,10 +837,26 @@ static const QLocaleData *defaultData() return default_data; } +static qsizetype defaultIndex() +{ + const QLocaleData *const data = defaultData(); +#ifndef QT_NO_SYSTEMLOCALE + if (data == &systemLocaleData) { + // Work out a suitable index matching the system data, for use when + // accessing calendar data, when not fetched from system. + return QLocaleData::findLocaleIndex(data->id()); + } +#endif + + using QtPrivate::q_points_into_range; + Q_ASSERT(q_points_into_range(data, locale_data)); + return data - locale_data; +} + const QLocaleData *QLocaleData::c() { Q_ASSERT(locale_index[QLocale::C] == 0); - return c_data; + return locale_data; } #ifndef QT_NO_DATASTREAM @@ -794,66 +875,79 @@ QDataStream &operator>>(QDataStream &ds, QLocale &l) } #endif // QT_NO_DATASTREAM +static constexpr qsizetype locale_data_size = q20::ssize(locale_data) - 1; // trailing guard -static const int locale_data_size = sizeof(locale_data)/sizeof(QLocaleData) - 1; +Q_GLOBAL_STATIC(QSharedDataPointer<QLocalePrivate>, defaultLocalePrivate, + new QLocalePrivate(defaultData(), defaultIndex())) -Q_GLOBAL_STATIC_WITH_ARGS(QSharedDataPointer<QLocalePrivate>, defaultLocalePrivate, - (QLocalePrivate::create(defaultData()))) - -static QLocalePrivate *localePrivateByName(const QString &name) +static QLocalePrivate *localePrivateByName(QStringView name) { - if (name == QLatin1String("C")) + if (name == u"C") return c_private(); - // TODO: Remove this version, and use offset everywhere - const QLocaleData *data = findLocaleData(name); - return QLocalePrivate::create(data, findLocaleOffset(name), - data->m_language_id == QLocale::C - ? QLocale::OmitGroupSeparator : QLocale::DefaultNumberOptions); + const qsizetype index = QLocaleData::findLocaleIndex(QLocaleId::fromName(name)); + Q_ASSERT(index >= 0 && index < locale_data_size); + return new QLocalePrivate(locale_data + index, index, + locale_data[index].m_language_id == QLocale::C + ? QLocale::OmitGroupSeparator : QLocale::DefaultNumberOptions); } static QLocalePrivate *findLocalePrivate(QLocale::Language language, QLocale::Script script, - QLocale::Country country) + QLocale::Territory territory) { if (language == QLocale::C) return c_private(); - // TODO: Remove pointer, use index instead - const QLocaleData *data = QLocaleData::findLocaleData(language, script, country); - const uint offset = QLocaleData::findLocaleOffset(language, script, country); + qsizetype index = QLocaleData::findLocaleIndex(QLocaleId { language, script, territory }); + Q_ASSERT(index >= 0 && index < locale_data_size); + const QLocaleData *data = locale_data + index; QLocale::NumberOptions numberOptions = QLocale::DefaultNumberOptions; - // If not found, should default to system + // If not found, should use default locale: if (data->m_language_id == QLocale::C) { if (defaultLocalePrivate.exists()) numberOptions = defaultLocalePrivate->data()->m_numberOptions; data = defaultData(); + index = defaultIndex(); } - return QLocalePrivate::create(data, offset, numberOptions); + return new QLocalePrivate(data, index, numberOptions); } -QString QLocaleData::decimalPoint() const +static std::optional<QString> +systemLocaleString(const QLocaleData *that, QSystemLocale::QueryType type) { #ifndef QT_NO_SYSTEMLOCALE - if (this == systemData()) { - QVariant res = systemLocale()->query(QSystemLocale::DecimalPoint, QVariant()); - if (!res.isNull()) - return res.toString(); - } + if (that != &systemLocaleData) + return std::nullopt; + + QVariant v = systemLocale()->query(type); + if (v.metaType() != QMetaType::fromType<QString>()) + return std::nullopt; + + return v.toString(); +#else + Q_UNUSED(that) + Q_UNUSED(type) + return std::nullopt; #endif - return decimalSeparator().getData(single_character_data); +} + +static QString localeString(const QLocaleData *that, QSystemLocale::QueryType type, + QLocaleData::DataRange range) +{ + if (auto opt = systemLocaleString(that, type)) + return *opt; + return range.getData(single_character_data); +} + +QString QLocaleData::decimalPoint() const +{ + return localeString(this, QSystemLocale::DecimalPoint, decimalSeparator()); } QString QLocaleData::groupSeparator() const { -#ifndef QT_NO_SYSTEMLOCALE - if (this == systemData()) { - QVariant res = systemLocale()->query(QSystemLocale::GroupSeparator, QVariant()); - if (!res.isNull()) - return res.toString(); - } -#endif - return groupDelim().getData(single_character_data); + return localeString(this, QSystemLocale::GroupSeparator, groupDelim()); } QString QLocaleData::percentSign() const @@ -868,23 +962,15 @@ QString QLocaleData::listSeparator() const QString QLocaleData::zeroDigit() const { -#ifndef QT_NO_SYSTEMLOCALE - if (this == systemData()) { - QVariant res = systemLocale()->query(QSystemLocale::ZeroDigit, QVariant()); - if (!res.isNull()) - return res.toString(); - } -#endif - return zero().getData(single_character_data); + return localeString(this, QSystemLocale::ZeroDigit, zero()); } -uint QLocaleData::zeroUcs() const +char32_t QLocaleData::zeroUcs() const { #ifndef QT_NO_SYSTEMLOCALE - if (this == systemData()) { - QVariant res = systemLocale()->query(QSystemLocale::ZeroDigit, QVariant()); - if (!res.isNull()) { - const QString text = res.toString(); + if (this == &systemLocaleData) { + const auto text = systemLocale()->query(QSystemLocale::ZeroDigit).toString(); + if (!text.isEmpty()) { if (text.size() == 1 && !text.at(0).isSurrogate()) return text.at(0).unicode(); if (text.size() == 2 && text.at(0).isHighSurrogate()) @@ -897,26 +983,12 @@ uint QLocaleData::zeroUcs() const QString QLocaleData::negativeSign() const { -#ifndef QT_NO_SYSTEMLOCALE - if (this == systemData()) { - QVariant res = systemLocale()->query(QSystemLocale::NegativeSign, QVariant()); - if (!res.isNull()) - return res.toString(); - } -#endif - return minus().getData(single_character_data); + return localeString(this, QSystemLocale::NegativeSign, minus()); } QString QLocaleData::positiveSign() const { -#ifndef QT_NO_SYSTEMLOCALE - if (this == systemData()) { - QVariant res = systemLocale()->query(QSystemLocale::PositiveSign, QVariant()); - if (!res.isNull()) - return res.toString(); - } -#endif - return plus().getData(single_character_data); + return localeString(this, QSystemLocale::PositiveSign, plus()); } QString QLocaleData::exponentSeparator() const @@ -931,108 +1003,115 @@ 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() +*/ /*! - Constructs a QLocale object with the specified \a name, - which has the format - "language[_script][_country][.codeset][@modifier]" or "C", where: + \since 6.3 - \list - \li language is a lowercase, two-letter, ISO 639 language code (also some - three-letter codes), - \li script is a titlecase, four-letter, ISO 15924 script code, - \li country is an uppercase, two-letter, ISO 3166 country code - (also "419" as defined by United Nations), - \li and codeset and modifier are ignored. - \endlist + Constructs a QLocale object with the specified \a name. - The separator can be either underscore or a minus sign. + The name has the format "language[_script][_territory][.codeset][@modifier]" + or "C", where: - If the string violates the locale format, or language is not - a valid ISO 639 code, the "C" locale is used instead. If country - is not present, or is not a valid ISO 3166 code, the most - appropriate country is chosen for the specified language. + \list + \li language is a lowercase, two-letter, ISO 639 language code (some + three-letter codes are also recognized), + \li script is a capitalized, four-letter, ISO 15924 script code, + \li territory is an uppercase, two-letter, ISO 3166 territory code + (some numeric codes are also recognized), and + \li codeset and modifier are ignored. + \endlist - The language, script and country codes are converted to their respective - \c Language, \c Script and \c Country enums. After this conversion is - performed, the constructor behaves exactly like QLocale(Country, Script, - Language). + The separator can be either underscore \c{'_'} (U+005F, "low line") or a + dash \c{'-'} (U+002D, "hyphen-minus"). If QLocale has no data for the + specified combination of language, script, and territory, then it uses the + most suitable match it can find instead. If the string violates the locale + format, or no suitable data can be found for the specified keys, the "C" + locale is used instead. - This constructor is much slower than QLocale(Country, Script, Language). + This constructor is much slower than QLocale(Language, Script, Territory) or + QLocale(Language, Territory). - \sa bcp47Name() + \sa bcp47Name(), {Matching combinations of language, script and territory} */ - -QLocale::QLocale(const QString &name) +QLocale::QLocale(QStringView name) : d(localePrivateByName(name)) { } /*! - Constructs a QLocale object initialized with the default locale. If - no default locale was set using setDefault(), this locale will - be the same as the one returned by system(). + \fn QLocale::QLocale(const QString &name) + \overload +*/ + +/*! + Constructs a QLocale object initialized with the default locale. - \sa setDefault() + If no default locale was set using setDefault(), this locale will be the + same as the one returned by system(). + + \sa setDefault(), system() */ QLocale::QLocale() : d(*defaultLocalePrivate) { - // Make sure system data is up to date + // Make sure system data is up to date: systemData(); } /*! - Constructs a QLocale object with the specified \a language and \a - country. + Constructs a QLocale object for the specified \a language and \a territory. - \list - \li If the language/country pair is found in the database, it is used. - \li If the language is found but the country is not, or if the country - is \c AnyCountry, the language is used with the most - appropriate available country (for example, Germany for German), - \li If neither the language nor the country are found, QLocale - defaults to the default locale (see setDefault()). - \endlist - - The language and country that are actually used can be queried - using language() and country(). + If there is more than one script in use for this combination, a likely + script will be selected. If QLocale has no data for the specified \a + language, the default locale is used. If QLocale has no data for the + specified combination of \a language and \a territory, an alternative + territory may be used instead. - \sa setDefault(), language(), country() + \sa setDefault(), {Matching combinations of language, script and territory} */ -QLocale::QLocale(Language language, Country country) - : d(findLocalePrivate(language, QLocale::AnyScript, country)) +QLocale::QLocale(Language language, Territory territory) + : d(findLocalePrivate(language, AnyScript, territory)) { } /*! \since 4.8 - Constructs a QLocale object with the specified \a language, \a script and - \a country. + Constructs a QLocale object for the specified \a language, \a script and \a + territory. + + If QLocale does not have data for the given combination, it will find data + for as good a match as it can. It falls back on the default locale if \list - \li If the language/script/country is found in the database, it is used. - \li If both \a script is AnyScript and \a country is AnyCountry, the - language is used with the most appropriate available script and country - (for example, Germany for German), - \li If either \a script is AnyScript or \a country is AnyCountry, the - language is used with the first locale that matches the given \a script - and \a country. - \li If neither the language nor the country are found, QLocale - defaults to the default locale (see setDefault()). + \li \a language is \c AnyLanguage and no language can be inferred from \a + script and \a territory + \li QLocale has no data for the language, either given as \a language or + inferred as above. \endlist - The language, script and country that are actually used can be queried - using language(), script() and country(). - - \sa setDefault(), language(), script(), country() + \sa setDefault(), {Matching combinations of language, script and territory} */ -QLocale::QLocale(Language language, Script script, Country country) - : d(findLocalePrivate(language, script, country)) +QLocale::QLocale(Language language, Script script, Territory territory) + : d(findLocalePrivate(language, script, territory)) { } @@ -1040,10 +1119,7 @@ QLocale::QLocale(Language language, Script script, Country country) Constructs a QLocale object as a copy of \a other. */ -QLocale::QLocale(const QLocale &other) -{ - d = other.d; -} +QLocale::QLocale(const QLocale &other) noexcept = default; /*! Destructor @@ -1058,20 +1134,16 @@ QLocale::~QLocale() to this QLocale object. */ -QLocale &QLocale::operator=(const QLocale &other) -{ - d = other.d; - return *this; -} +QLocale &QLocale::operator=(const QLocale &other) noexcept = default; -bool QLocale::operator==(const QLocale &other) const -{ - return d->m_data == other.d->m_data && d->m_numberOptions == other.d->m_numberOptions; -} +/*! + \internal + Equality comparison. +*/ -bool QLocale::operator!=(const QLocale &other) const +bool QLocale::equals(const QLocale &other) const { - return d->m_data != other.d->m_data || d->m_numberOptions != other.d->m_numberOptions; + return d->m_data == other.d->m_data && d->m_numberOptions == other.d->m_numberOptions; } /*! @@ -1100,7 +1172,7 @@ size_t qHash(const QLocale &key, size_t seed) noexcept Sets the \a options related to number conversions for this QLocale instance. - \sa numberOptions() + \sa numberOptions(), FloatingPointPrecisionOption */ void QLocale::setNumberOptions(NumberOptions options) { @@ -1116,7 +1188,7 @@ void QLocale::setNumberOptions(NumberOptions options) By default, no options are set for the standard locales, except for the "C" locale, which has OmitGroupSeparator set by default. - \sa setNumberOptions(), toString(), groupSeparator() + \sa setNumberOptions(), toString(), groupSeparator(), FloatingPointPrecisionOption */ QLocale::NumberOptions QLocale::numberOptions() const { @@ -1140,12 +1212,12 @@ QLocale::NumberOptions QLocale::numberOptions() const QString QLocale::quoteString(QStringView str, QuotationStyle style) const { #ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { + 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()) @@ -1154,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 { @@ -1173,8 +1245,9 @@ QString QLocale::quoteString(QStringView str, QuotationStyle style) const */ QString QLocale::createSeparatedList(const QStringList &list) const { + // May be empty if list is empty or sole entry is empty. #ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { + if (d->m_data == &systemLocaleData) { QVariant res = systemLocale()->query(QSystemLocale::ListToSeparatedString, QVariant::fromValue(list)); @@ -1183,7 +1256,7 @@ QString QLocale::createSeparatedList(const QStringList &list) const } #endif - const int size = list.size(); + const qsizetype size = list.size(); if (size < 1) return QString(); @@ -1198,7 +1271,7 @@ QString QLocale::createSeparatedList(const QStringList &list) const QStringView formatMid = d->m_data->midListPattern().viewData(list_pattern_part_data); QStringView formatEnd = d->m_data->endListPattern().viewData(list_pattern_part_data); QString result = formatStart.arg(list.at(0), list.at(1)); - for (int i = 2; i < size - 1; ++i) + for (qsizetype i = 2; i < size - 1; ++i) result = formatMid.arg(result, list.at(i)); result = formatEnd.arg(result, list.at(size - 1)); return result; @@ -1233,12 +1306,13 @@ void QLocale::setDefault(const QLocale &locale) // update the cached private *defaultLocalePrivate = locale.d; + QLocalePrivate::s_generation.fetchAndAddRelaxed(1); } /*! Returns the language of this locale. - \sa script(), country(), languageToString(), bcp47Name() + \sa script(), territory(), languageToString(), bcp47Name() */ QLocale::Language QLocale::language() const { @@ -1250,7 +1324,7 @@ QLocale::Language QLocale::language() const Returns the script of this locale. - \sa language(), country(), languageToString(), scriptToString(), bcp47Name() + \sa language(), territory(), languageToString(), scriptToString(), bcp47Name() */ QLocale::Script QLocale::script() const { @@ -1258,61 +1332,120 @@ QLocale::Script QLocale::script() const } /*! - Returns the country of this locale. + \since 6.2 - \sa language(), script(), countryToString(), bcp47Name() + Returns the territory of this locale. + + \sa language(), script(), territoryToString(), bcp47Name() */ -QLocale::Country QLocale::country() const +QLocale::Territory QLocale::territory() const { - return Country(d->countryId()); + return Territory(d->territoryId()); } +#if QT_DEPRECATED_SINCE(6, 6) /*! - Returns the language and country of this locale as a - string of the form "language_country", where - language is a lowercase, two-letter ISO 639 language code, - and country is an uppercase, two- or three-letter ISO 3166 country code. + \deprecated [6.6] Use \l territory() instead. - Note that even if QLocale object was constructed with an explicit script, - name() will not contain it for compatibility reasons. Use bcp47Name() instead - if you need a full locale name. + Returns the territory of this locale. - \sa QLocale(), language(), script(), country(), bcp47Name() + \sa language(), script(), territoryToString(), bcp47Name() */ - -QString QLocale::name() const +QLocale::Country QLocale::country() const { - Language l = language(); - if (l == C) - return d->languageCode(); - - Country c = country(); - if (c == AnyCountry) - return d->languageCode(); - - return d->languageCode() + QLatin1Char('_') + d->countryCode(); + return territory(); } +#endif -static qlonglong toIntegral_helper(const QLocaleData *d, QStringView str, bool *ok, - QLocale::NumberOptions mode, qlonglong) +/*! + \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) { - return d->stringToLongLong(str, 10, ok, mode); + qWarning("QLocale::%s(): Using non-ASCII separator '%c' (%02x) is unsupported", + method, sep, uint(uchar(sep))); } -static qulonglong toIntegral_helper(const QLocaleData *d, QStringView str, bool *ok, - QLocale::NumberOptions mode, qulonglong) +/*! + \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. 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 + you need a full locale name, or construct the string you want to identify a + locale by from those returned by passing its \l language() to \l + languageToCode() and similar for the script and territory. + + \sa QLocale(), language(), script(), territory(), bcp47Name(), uiLanguages() +*/ + +QString QLocale::name(TagSeparator separator) const { - return d->stringToUnsLongLong(str, 10, ok, mode); + const char sep = char(separator); + if (uchar(sep) > 0x7f) { + badSeparatorWarning("name", sep); + return {}; + } + const auto code = d->languageCode(); + QLatin1StringView view{code.data()}; + + Language l = language(); + if (l == C) + return view; + + Territory c = territory(); + if (c == AnyTerritory) + return view; + + return view + QLatin1Char(sep) + d->territoryCode(); } template <typename T> static inline T toIntegral_helper(const QLocalePrivate *d, QStringView str, bool *ok) { - using Int64 = - typename std::conditional<std::is_unsigned<T>::value, qulonglong, qlonglong>::type; + constexpr bool isUnsigned = std::is_unsigned_v<T>; + using Int64 = typename std::conditional_t<isUnsigned, quint64, qint64>; + + QSimpleParsedNumber<Int64> r{}; + if constexpr (isUnsigned) + r = d->m_data->stringToUnsLongLong(str, 10, d->m_numberOptions); + else + r = d->m_data->stringToLongLong(str, 10, d->m_numberOptions); - // we select the right overload by the last, unused parameter - Int64 val = toIntegral_helper(d->m_data, str, ok, d->m_numberOptions, Int64()); + if (ok) + *ok = r.ok(); + + Int64 val = r.result; if (T(val) != val) { if (ok != nullptr) *ok = false; @@ -1325,64 +1458,229 @@ T toIntegral_helper(const QLocalePrivate *d, QStringView str, bool *ok) /*! \since 4.8 - Returns the dash-separated language, script and country (and possibly other - BCP47 fields) of this locale as a string. + \brief Returns the BCP47 field names joined with dashes. + + This combines as many of language, script and territory (and possibly other + BCP47 fields) for this locale as are needed to uniquely specify it. Note + that fields may be omitted if the Unicode consortium's \l {Matching + combinations of language, script and territory}{Likely Subtag Rules} imply + the omitted fields when given those retained. See \l name() for how to + construct a string from individual fields, if some other format is needed. + + Unlike uiLanguages(), the value returned by bcp47Name() represents the + 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 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(TagSeparator separator) const +{ + const char sep = char(separator); + if (uchar(sep) > 0x7f) { + badSeparatorWarning("bcp47Name", sep); + return {}; + } + return QString::fromLatin1(d->bcp47Name(sep)); +} + +/*! + Returns the two- or three-letter language code for \a language, as defined + in the ISO 639 standards. + + If specified, \a codeTypes selects which set of codes to consider. The first + code from the set that is defined for \a language is returned. Otherwise, + all ISO-639 codes are considered. The codes are considered in the following + order: \c ISO639Part1, \c ISO639Part2B, \c ISO639Part2T, \c ISO639Part3. + \c LegacyLanguageCode is ignored by this function. + + \note For \c{QLocale::C} the function returns \c{"C"}. + For \c QLocale::AnyLanguage an empty string is returned. + If the language has no code in any selected code set, an empty string + is returned. + + \since 6.3 + \sa codeToLanguage(), language(), name(), bcp47Name(), territoryToCode(), scriptToCode() +*/ +QString QLocale::languageToCode(Language language, LanguageCodeTypes codeTypes) +{ + const auto code = QLocalePrivate::languageToCode(language, codeTypes); + return QLatin1StringView{code.data()}; +} + +/*! + Returns the QLocale::Language enum corresponding to the two- or three-letter + \a languageCode, as defined in the ISO 639 standards. + + If specified, \a codeTypes selects which set of codes to consider for + conversion. By default all codes known to Qt are considered. The codes are + matched in the following order: \c ISO639Part1, \c ISO639Part2B, + \c ISO639Part2T, \c ISO639Part3, \c LegacyLanguageCode. + + If the code is invalid or not known \c QLocale::AnyLanguage is returned. + + \since 6.3 + \sa languageToCode(), codeToTerritory(), codeToScript() +*/ +QLocale::Language QLocale::codeToLanguage(QStringView languageCode, + LanguageCodeTypes codeTypes) noexcept +{ + return QLocalePrivate::codeToLanguage(languageCode, codeTypes); +} + +/*! + \since 6.2 + + Returns the two-letter territory code for \a territory, as defined + in the ISO 3166 standard. + + \note For \c{QLocale::AnyTerritory} an empty string is returned. + + \sa codeToTerritory(), territory(), name(), bcp47Name(), languageToCode(), scriptToCode() +*/ +QString QLocale::territoryToCode(QLocale::Territory territory) +{ + return QLocalePrivate::territoryToCode(territory); +} + +/*! + \since 6.2 + + Returns the QLocale::Territory enum corresponding to the two-letter or + three-digit \a territoryCode, as defined in the ISO 3166 standard. + + If the code is invalid or not known QLocale::AnyTerritory is returned. + + \sa territoryToCode(), codeToLanguage(), codeToScript() +*/ +QLocale::Territory QLocale::codeToTerritory(QStringView territoryCode) noexcept +{ + return QLocalePrivate::codeToTerritory(territoryCode); +} + +#if QT_DEPRECATED_SINCE(6, 6) +/*! + \deprecated [6.6] Use \l territoryToCode() instead. - Unlike the uiLanguages() the returned value of the bcp47Name() represents - the locale name of the QLocale data but not the language the user-interface - should be in. + Returns the two-letter territory code for \a country, as defined + in the ISO 3166 standard. - This function tries to conform the locale name to BCP47. + \note For \c{QLocale::AnyTerritory} or \c{QLocale::AnyCountry} an empty string is returned. - \sa language(), country(), script(), uiLanguages() + \sa codeToTerritory(), territory(), name(), bcp47Name(), languageToCode(), scriptToCode() */ -QString QLocale::bcp47Name() const +QString QLocale::countryToCode(Country country) { - return QString::fromLatin1(d->bcp47Name()); + return territoryToCode(country); +} + +/*! + Returns the QLocale::Territory enum corresponding to the two-letter or + three-digit \a countryCode, as defined in the ISO 3166 standard. + + If the code is invalid or not known QLocale::AnyTerritory is returned. + + \deprecated [6.6] Use codeToTerritory(QStringView) instead. + \since 6.1 + \sa territoryToCode(), codeToLanguage(), codeToScript() +*/ +QLocale::Country QLocale::codeToCountry(QStringView countryCode) noexcept +{ + return QLocalePrivate::codeToTerritory(countryCode); +} +#endif + +/*! + Returns the four-letter script code for \a script, as defined in the + ISO 15924 standard. + + \note For \c{QLocale::AnyScript} an empty string is returned. + + \since 6.1 + \sa script(), name(), bcp47Name(), languageToCode(), territoryToCode() +*/ +QString QLocale::scriptToCode(Script script) +{ + return QLocalePrivate::scriptToCode(script); +} + +/*! + Returns the QLocale::Script enum corresponding to the four-letter script + \a scriptCode, as defined in the ISO 15924 standard. + + If the code is invalid or not known QLocale::AnyScript is returned. + + \since 6.1 + \sa scriptToCode(), codeToLanguage(), codeToTerritory() +*/ +QLocale::Script QLocale::codeToScript(QStringView scriptCode) noexcept +{ + return QLocalePrivate::codeToScript(scriptCode); } /*! Returns a QString containing the name of \a language. - \sa countryToString(), scriptToString(), bcp47Name() + \sa territoryToString(), scriptToString(), bcp47Name() */ QString QLocale::languageToString(Language language) { - if (uint(language) > uint(QLocale::LastLanguage)) - return QLatin1String("Unknown"); - return QLatin1String(language_name_list + language_name_index[language]); + if (language > LastLanguage) + return "Unknown"_L1; + return QString::fromUtf8(language_name_list + language_name_index[language]); } /*! - Returns a QString containing the name of \a country. + \since 6.2 + + Returns a QString containing the name of \a territory. - \sa languageToString(), scriptToString(), country(), bcp47Name() + \sa languageToString(), scriptToString(), territory(), bcp47Name() */ +QString QLocale::territoryToString(Territory territory) +{ + if (territory > LastTerritory) + return "Unknown"_L1; + return QString::fromUtf8(territory_name_list + territory_name_index[territory]); +} + +#if QT_DEPRECATED_SINCE(6, 6) +/*! + \deprecated [6.6] Use \l territoryToString() instead. + + Returns a QString containing the name of \a country. + \sa languageToString(), scriptToString(), territory(), bcp47Name() +*/ QString QLocale::countryToString(Country country) { - if (uint(country) > uint(QLocale::LastCountry)) - return QLatin1String("Unknown"); - return QLatin1String(country_name_list + country_name_index[country]); + return territoryToString(country); } +#endif /*! \since 4.8 Returns a QString containing the name of \a script. - \sa languageToString(), countryToString(), script(), bcp47Name() + \sa languageToString(), territoryToString(), script(), bcp47Name() */ -QString QLocale::scriptToString(QLocale::Script script) +QString QLocale::scriptToString(Script script) { - if (uint(script) > uint(QLocale::LastScript)) - return QLatin1String("Unknown"); - return QLatin1String(script_name_list + script_name_index[script]); + if (script > LastScript) + return "Unknown"_L1; + return QString::fromUtf8(script_name_list + script_name_index[script]); } -#if QT_STRINGVIEW_LEVEL < 2 /*! + \fn short QLocale::toShort(const QString &s, bool *ok) const + Returns the short int represented by the localized string \a s. If the conversion fails the function returns 0. @@ -1395,12 +1693,9 @@ QString QLocale::scriptToString(QLocale::Script script) \sa toUShort(), toString() */ -short QLocale::toShort(const QString &s, bool *ok) const -{ - return toIntegral_helper<short>(d, s, ok); -} - /*! + \fn ushort QLocale::toUShort(const QString &s, bool *ok) const + Returns the unsigned short int represented by the localized string \a s. If the conversion fails the function returns 0. @@ -1413,12 +1708,8 @@ short QLocale::toShort(const QString &s, bool *ok) const \sa toShort(), toString() */ -ushort QLocale::toUShort(const QString &s, bool *ok) const -{ - return toIntegral_helper<ushort>(d, s, ok); -} - /*! + \fn int QLocale::toInt(const QString &s, bool *ok) const Returns the int represented by the localized string \a s. If the conversion fails the function returns 0. @@ -1431,12 +1722,8 @@ ushort QLocale::toUShort(const QString &s, bool *ok) const \sa toUInt(), toString() */ -int QLocale::toInt(const QString &s, bool *ok) const -{ - return toIntegral_helper<int>(d, s, ok); -} - /*! + \fn uint QLocale::toUInt(const QString &s, bool *ok) const Returns the unsigned int represented by the localized string \a s. If the conversion fails the function returns 0. @@ -1449,54 +1736,41 @@ int QLocale::toInt(const QString &s, bool *ok) const \sa toInt(), toString() */ -uint QLocale::toUInt(const QString &s, bool *ok) const -{ - return toIntegral_helper<uint>(d, s, ok); -} - /*! - Returns the long int represented by the localized string \a s. - - If the conversion fails the function returns 0. - - If \a ok is not \nullptr, failure is reported by setting *\a{ok} - to \c false, and success by setting *\a{ok} to \c true. + \since 5.13 + \fn long QLocale::toLong(const QString &s, bool *ok) const - This function ignores leading and trailing whitespace. + Returns the long int represented by the localized string \a s. - \sa toInt(), toULong(), toDouble(), toString() + If the conversion fails the function returns 0. - \since 5.13 - */ + If \a ok is not \nullptr, failure is reported by setting *\a{ok} + to \c false, and success by setting *\a{ok} to \c true. + This function ignores leading and trailing whitespace. -long QLocale::toLong(const QString &s, bool *ok) const -{ - return toIntegral_helper<long>(d, s, ok); -} + \sa toInt(), toULong(), toDouble(), toString() +*/ /*! - Returns the unsigned long int represented by the localized - string \a s. + \since 5.13 + \fn ulong QLocale::toULong(const QString &s, bool *ok) const - If the conversion fails the function returns 0. + Returns the unsigned long int represented by the localized + string \a s. - If \a ok is not \nullptr, failure is reported by setting *\a{ok} - to \c false, and success by setting *\a{ok} to \c true. + If the conversion fails the function returns 0. - This function ignores leading and trailing whitespace. + If \a ok is not \nullptr, failure is reported by setting *\a{ok} + to \c false, and success by setting *\a{ok} to \c true. - \sa toLong(), toInt(), toDouble(), toString() + This function ignores leading and trailing whitespace. - \since 5.13 + \sa toLong(), toInt(), toDouble(), toString() */ -ulong QLocale::toULong(const QString &s, bool *ok) const -{ - return toIntegral_helper<ulong>(d, s, ok); -} - /*! + \fn qlonglong QLocale::toLongLong(const QString &s, bool *ok) const Returns the long long int represented by the localized string \a s. If the conversion fails the function returns 0. @@ -1509,13 +1783,9 @@ ulong QLocale::toULong(const QString &s, bool *ok) const \sa toInt(), toULongLong(), toDouble(), toString() */ - -qlonglong QLocale::toLongLong(const QString &s, bool *ok) const -{ - return toIntegral_helper<qlonglong>(d, s, ok); -} - /*! + \fn qulonglong QLocale::toULongLong(const QString &s, bool *ok) const + Returns the unsigned long long int represented by the localized string \a s. @@ -1529,12 +1799,9 @@ qlonglong QLocale::toLongLong(const QString &s, bool *ok) const \sa toLongLong(), toInt(), toDouble(), toString() */ -qulonglong QLocale::toULongLong(const QString &s, bool *ok) const -{ - return toIntegral_helper<qulonglong>(d, s, ok); -} - /*! + \fn float QLocale::toFloat(const QString &s, bool *ok) const + Returns the float represented by the localized string \a s. Returns an infinity if the conversion overflows or 0.0 if the @@ -1543,20 +1810,13 @@ qulonglong QLocale::toULongLong(const QString &s, bool *ok) const If \a ok is not \nullptr, failure is reported by setting *\a{ok} to \c false, and success by setting *\a{ok} to \c true. - This function does not fall back to the 'C' locale if the string - cannot be interpreted in this locale. - This function ignores leading and trailing whitespace. \sa toDouble(), toInt(), toString() */ -float QLocale::toFloat(const QString &s, bool *ok) const -{ - return QLocaleData::convertDoubleToFloat(toDouble(s, ok), ok); -} - /*! + \fn double QLocale::toDouble(const QString &s, bool *ok) const Returns the double represented by the localized string \a s. Returns an infinity if the conversion overflows or 0.0 if the @@ -1565,9 +1825,6 @@ float QLocale::toFloat(const QString &s, bool *ok) const If \a ok is not \nullptr, failure is reported by setting *\a{ok} to \c false, and success by setting *\a{ok} to \c true. - This function does not fall back to the 'C' locale if the string - cannot be interpreted in this locale. - \snippet code/src_corelib_text_qlocale.cpp 3 Notice that the last conversion returns 1234.0, because '.' is the @@ -1578,12 +1835,6 @@ float QLocale::toFloat(const QString &s, bool *ok) const \sa toFloat(), toInt(), toString() */ -double QLocale::toDouble(const QString &s, bool *ok) const -{ - return d->m_data->stringToDouble(s, ok, d->m_numberOptions); -} -#endif // QT_STRINGVIEW_LEVEL < 2 - /*! Returns the short int represented by the localized string \a s. @@ -1665,20 +1916,18 @@ uint QLocale::toUInt(QStringView s, bool *ok) const } /*! - Returns the long int represented by the localized string \a s. - - If the conversion fails the function returns 0. + \since 5.13 + Returns the long int represented by the localized string \a s. - If \a ok is not \nullptr, failure is reported by setting *\a{ok} - to \c false, and success by setting *\a{ok} to \c true. - - This function ignores leading and trailing whitespace. + If the conversion fails the function returns 0. - \sa toInt(), toULong(), toDouble(), toString() + If \a ok is not \nullptr, failure is reported by setting *\a{ok} + to \c false, and success by setting *\a{ok} to \c true. - \since 5.13 - */ + This function ignores leading and trailing whitespace. + \sa toInt(), toULong(), toDouble(), toString() +*/ long QLocale::toLong(QStringView s, bool *ok) const { @@ -1686,20 +1935,19 @@ long QLocale::toLong(QStringView s, bool *ok) const } /*! - Returns the unsigned long int represented by the localized - string \a s. - - If the conversion fails the function returns 0. + \since 5.13 + Returns the unsigned long int represented by the localized + string \a s. - If \a ok is not \nullptr, failure is reported by setting *\a{ok} - to \c false, and success by setting *\a{ok} to \c true. + If the conversion fails the function returns 0. - This function ignores leading and trailing whitespace. + If \a ok is not \nullptr, failure is reported by setting *\a{ok} + to \c false, and success by setting *\a{ok} to \c true. - \sa toLong(), toInt(), toDouble(), toString() + This function ignores leading and trailing whitespace. - \since 5.13 - */ + \sa toLong(), toInt(), toDouble(), toString() +*/ ulong QLocale::toULong(QStringView s, bool *ok) const { @@ -1778,10 +2026,6 @@ float QLocale::toFloat(QStringView s, bool *ok) const If \a ok is not \nullptr, failure is reported by setting *\a{ok} to \c false, and success by setting *\a{ok} to \c true. - Unlike QString::toDouble(), this function does not fall back to - the "C" locale if the string cannot be interpreted in this - locale. - \snippet code/src_corelib_text_qlocale.cpp 3-qstringview Notice that the last conversion returns 1234.0, because '.' is the @@ -1807,10 +2051,8 @@ double QLocale::toDouble(QStringView s, bool *ok) const QString QLocale::toString(qlonglong i) const { - int flags = d->m_numberOptions & OmitGroupSeparator - ? 0 - : (d->m_data->m_country_id == Country::India) - ? QLocaleData::IndianNumberGrouping : QLocaleData::ThousandsGroup; + int flags = (d->m_numberOptions & OmitGroupSeparator + ? 0 : QLocaleData::GroupDigits); return d->m_data->longLongToString(i, -1, 10, -1, flags); } @@ -1823,15 +2065,12 @@ QString QLocale::toString(qlonglong i) const QString QLocale::toString(qulonglong i) const { - int flags = d->m_numberOptions & OmitGroupSeparator - ? 0 - : (d->m_data->m_country_id == Country::India) - ? QLocaleData::IndianNumberGrouping : QLocaleData::ThousandsGroup; + int flags = (d->m_numberOptions & OmitGroupSeparator + ? 0 : QLocaleData::GroupDigits); return d->m_data->unsLongLongToString(i, -1, 10, -1, flags); } -#if QT_STRINGVIEW_LEVEL < 2 /*! Returns a localized string representation of the given \a date in the specified \a format. @@ -1842,39 +2081,97 @@ QString QLocale::toString(qulonglong i) const QString QLocale::toString(QDate date, const QString &format) const { - return QCalendar().dateTimeToString(format, QDateTime(), date, QTime(), *this); + return toString(date, qToStringViewIgnoringNull(format)); } -#endif /*! - \since 5.10 + Returns a localized string representation of the given \a time according + to the specified \a format. + If \a format is an empty string, an empty string is returned. + + \sa QTime::toString() +*/ + +QString QLocale::toString(QTime time, const QString &format) const +{ + return toString(time, qToStringViewIgnoringNull(format)); +} + +/*! + \since 4.4 + \fn QString QLocale::toString(const QDateTime &dateTime, const QString &format) const + + Returns a localized string representation of the given \a dateTime according + to the specified \a format. + If \a format is an empty string, an empty string is returned. + + \sa QDateTime::toString(), QDate::toString(), QTime::toString() +*/ + +/*! + \since 5.14 Returns a localized string representation of the given \a date in the - specified \a format. + specified \a format, optionally for a specified calendar \a cal. If \a format is an empty string, an empty string is returned. \sa QDate::toString() */ +QString QLocale::toString(QDate date, QStringView format, QCalendar cal) const +{ + return cal.dateTimeToString(format, QDateTime(), date, QTime(), *this); +} + +/*! + \since 5.10 + \overload +*/ QString QLocale::toString(QDate date, QStringView format) const { return QCalendar().dateTimeToString(format, QDateTime(), date, QTime(), *this); } /*! + \since 5.14 + Returns a localized string representation of the given \a date according - to the specified \a format (see dateFormat()). + to the specified \a format (see dateFormat()), optionally for a specified + calendar \a cal. \note Some locales may use formats that limit the range of years they can represent. */ +QString QLocale::toString(QDate date, FormatType format, QCalendar cal) const +{ + if (!date.isValid()) + return QString(); +#ifndef QT_NO_SYSTEMLOCALE + if (cal.isGregorian() && d->m_data == &systemLocaleData) { + QVariant res = systemLocale()->query(format == LongFormat + ? QSystemLocale::DateToStringLong + : QSystemLocale::DateToStringShort, + date); + if (!res.isNull()) + return res.toString(); + } +#endif + + QString format_str = dateFormat(format); + return toString(date, format_str, cal); +} + +/*! + \since 4.5 + \overload +*/ QString QLocale::toString(QDate date, FormatType format) const { if (!date.isValid()) return QString(); #ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { + if (d->m_data == &systemLocaleData) { QVariant res = systemLocale()->query(format == LongFormat ? QSystemLocale::DateToStringLong : QSystemLocale::DateToStringShort, @@ -1890,7 +2187,7 @@ QString QLocale::toString(QDate date, FormatType format) const static bool timeFormatContainsAP(QStringView format) { - int i = 0; + qsizetype i = 0; while (i < format.size()) { if (format.at(i).unicode() == '\'') { qt_readEscapedFormatString(format, &i); @@ -1905,22 +2202,8 @@ static bool timeFormatContainsAP(QStringView format) return false; } -#if QT_STRINGVIEW_LEVEL < 2 -/*! - Returns a localized string representation of the given \a time according - to the specified \a format. - If \a format is an empty string, an empty string is returned. - - \sa QTime::toString() -*/ -QString QLocale::toString(QTime time, const QString &format) const -{ - return QCalendar().dateTimeToString(format, QDateTime(), QDate(), time, *this); -} -#endif - /*! - \since 5.10 + \since 4.5 Returns a localized string representation of the given \a time according to the specified \a format. @@ -1933,70 +2216,46 @@ QString QLocale::toString(QTime time, QStringView format) const return QCalendar().dateTimeToString(format, QDateTime(), QDate(), time, *this); } -#if QT_STRINGVIEW_LEVEL < 2 /*! - \since 4.4 + \since 5.14 Returns a localized string representation of the given \a dateTime according - to the specified \a format. + to the specified \a format, optionally for a specified calendar \a cal. If \a format is an empty string, an empty string is returned. \sa QDateTime::toString(), QDate::toString(), QTime::toString() */ - -QString QLocale::toString(const QDateTime &dateTime, const QString &format) const +QString QLocale::toString(const QDateTime &dateTime, QStringView format, QCalendar cal) const { - return QCalendar().dateTimeToString(format, dateTime, QDate(), QTime(), *this); + return cal.dateTimeToString(format, dateTime, QDate(), QTime(), *this); } -#endif /*! \since 5.10 - - Returns a localized string representation of the given \a dateTime according - to the specified \a format. - If \a format is an empty string, an empty string is returned. - - \sa QDateTime::toString(), QDate::toString(), QTime::toString() + \overload */ QString QLocale::toString(const QDateTime &dateTime, QStringView format) const { return QCalendar().dateTimeToString(format, dateTime, QDate(), QTime(), *this); } -QString QLocale::toString(QDate date, QStringView format, QCalendar cal) const -{ - return cal.dateTimeToString(format, QDateTime(), date, QTime(), *this); -} - -QString QLocale::toString(QDate date, QLocale::FormatType format, QCalendar cal) const -{ - if (!date.isValid()) - return QString(); - -#ifndef QT_NO_SYSTEMLOCALE - if (cal.isGregorian() && d->m_data == systemData()) { - QVariant res = systemLocale()->query(format == LongFormat - ? QSystemLocale::DateToStringLong - : QSystemLocale::DateToStringShort, - date); - if (!res.isNull()) - return res.toString(); - } -#endif +/*! + \since 5.14 - QString format_str = dateFormat(format); - return toString(date, format_str, cal); -} + Returns a localized string representation of the given \a dateTime according + to the specified \a format (see dateTimeFormat()), optionally for a + specified calendar \a cal. -QString QLocale::toString(const QDateTime &dateTime, QLocale::FormatType format, - QCalendar cal) const + \note Some locales may use formats that limit the range of years they can + represent. +*/ +QString QLocale::toString(const QDateTime &dateTime, FormatType format, QCalendar cal) const { if (!dateTime.isValid()) return QString(); #ifndef QT_NO_SYSTEMLOCALE - if (cal.isGregorian() && d->m_data == systemData()) { + if (cal.isGregorian() && d->m_data == &systemLocaleData) { QVariant res = systemLocale()->query(format == LongFormat ? QSystemLocale::DateTimeToStringLong : QSystemLocale::DateTimeToStringShort, @@ -2010,28 +2269,17 @@ QString QLocale::toString(const QDateTime &dateTime, QLocale::FormatType format, return toString(dateTime, format_str, cal); } -QString QLocale::toString(const QDateTime &dateTime, QStringView format, QCalendar cal) const -{ - return cal.dateTimeToString(format, dateTime, QDate(), QTime(), *this); -} - /*! \since 4.4 - - Returns a localized string representation of the given \a dateTime according - to the specified \a format (see dateTimeFormat()). - - \note Some locales may use formats that limit the range of years they can - represent. + \overload */ - QString QLocale::toString(const QDateTime &dateTime, FormatType format) const { if (!dateTime.isValid()) return QString(); #ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { + if (d->m_data == &systemLocaleData) { QVariant res = systemLocale()->query(format == LongFormat ? QSystemLocale::DateTimeToStringLong : QSystemLocale::DateTimeToStringShort, @@ -2057,7 +2305,7 @@ QString QLocale::toString(QTime time, FormatType format) const return QString(); #ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { + if (d->m_data == &systemLocaleData) { QVariant res = systemLocale()->query(format == LongFormat ? QSystemLocale::TimeToStringLong : QSystemLocale::TimeToStringShort, @@ -2086,7 +2334,7 @@ QString QLocale::toString(QTime time, FormatType format) const QString QLocale::dateFormat(FormatType format) const { #ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { + if (d->m_data == &systemLocaleData) { QVariant res = systemLocale()->query(format == LongFormat ? QSystemLocale::DateFormatLong : QSystemLocale::DateFormatShort, @@ -2117,7 +2365,7 @@ QString QLocale::dateFormat(FormatType format) const QString QLocale::timeFormat(FormatType format) const { #ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { + if (d->m_data == &systemLocaleData) { QVariant res = systemLocale()->query(format == LongFormat ? QSystemLocale::TimeFormatLong : QSystemLocale::TimeFormatShort, @@ -2148,7 +2396,7 @@ QString QLocale::timeFormat(FormatType format) const QString QLocale::dateTimeFormat(FormatType format) const { #ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { + if (d->m_data == &systemLocaleData) { QVariant res = systemLocale()->query(format == LongFormat ? QSystemLocale::DateTimeFormatLong : QSystemLocale::DateTimeFormatShort, @@ -2158,16 +2406,20 @@ QString QLocale::dateTimeFormat(FormatType format) const } } #endif - return dateFormat(format) + QLatin1Char(' ') + timeFormat(format); + return dateFormat(format) + u' ' + timeFormat(format); } #if QT_CONFIG(datestring) /*! \since 4.4 - Parses the time string given in \a string and returns the - time. The format of the time string is chosen according to the - \a format parameter (see timeFormat()). + Reads \a string as a time in a locale-specific \a format. + + Parses \a string and returns the time it represents. The format of the time + string is chosen according to the \a format parameter (see timeFormat()). + + \note Any am/pm indicators used must match \l amText() or \l pmText(), + ignoring case. If the time could not be parsed, returns an invalid time. @@ -2178,76 +2430,89 @@ QTime QLocale::toTime(const QString &string, FormatType format) const return toTime(string, timeFormat(format)); } -#if QT_DEPRECATED_SINCE(5, 15) -/*! - \since 5.14 - \overload - \deprecated -*/ -QTime QLocale::toTime(const QString &string, FormatType format, QCalendar cal) const -{ -QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED - return toTime(string, timeFormat(format), cal); -QT_WARNING_POP -} -#endif - /*! \since 4.4 - Parses the date string given in \a string and returns the - date. The format of the date string is chosen according to the - \a format parameter (see dateFormat()). + Reads \a string as a date in a locale-specific \a format. + + 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. If the date could not be parsed, returns an invalid date. \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); } /*! \since 4.4 - Parses the date/time string given in \a string and returns the - time. The format of the date/time string is chosen according to the - \a format parameter (see dateTimeFormat()). + Reads \a string as a date-time in a locale-specific \a format. + + Parses \a string and returns the date-time it represents. The format of the + 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. If the string could not be parsed, returns an invalid QDateTime. \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); } /*! \since 4.4 - Parses the time string given in \a string and returns the - time. See QTime::fromString() for information on what is a valid - format string. + Reads \a string as a time in the given \a format. + + Parses \a string and returns the time it represents. See QTime::fromString() + for the interpretation of \a format. + + \note Any am/pm indicators used must match \l amText() or \l pmText(), + ignoring case. If the time could not be parsed, returns an invalid time. @@ -2268,63 +2533,52 @@ QTime QLocale::toTime(const QString &string, const QString &format) const return time; } -#if QT_DEPRECATED_SINCE(5, 15) -/*! - \since 5.14 - \overload - \deprecated -*/ -QTime QLocale::toTime(const QString &string, const QString &format, QCalendar cal) const -{ - QTime time; -#if QT_CONFIG(datetimeparser) - QDateTimeParser dt(QMetaType::QTime, QDateTimeParser::FromString, cal); - dt.setDefaultLocale(*this); - if (dt.parseFormat(format)) - dt.fromString(string, nullptr, &time); -#else - Q_UNUSED(cal); - Q_UNUSED(string); - Q_UNUSED(format); -#endif - return time; -} -#endif - /*! \since 4.4 - Parses the date string given in \a string and returns the - date. See QDate::fromString() for information on the expressions - that can be used with this function. + Reads \a string as a date in the given \a format. - This function searches month names and the names of the days of - the week in the current locale. + 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. If the date could not be parsed, returns an invalid date. \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; @@ -2333,38 +2587,51 @@ QDate QLocale::toDate(const QString &string, const QString &format, QCalendar ca /*! \since 4.4 - Parses the date/time string given in \a string and returns the - time. See QDateTime::fromString() for information on the expressions - that can be used with this function. + Reads \a string as a date-time in the given \a format. - \note The month and day names used must be given in the user's local - language. + Parses \a string and returns the date-time it represents. See + QDateTime::fromString() for the interpretation of \a format. - If the string could not be parsed, returns an invalid QDateTime. + \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. + + If the string could not be parsed, returns an invalid QDateTime. If the + string can be parsed and represents an invalid date-time (e.g. in a gap + skipped by a time-zone transition), an invalid QDateTime is returned, whose + toMSecsSinceEpoch() represents a near-by date-time that is valid. Passing + that to fromMSecsSinceEpoch() will produce a valid date-time that isn't + faithfully represented by the string parsed. \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)) + 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(); @@ -2374,7 +2641,14 @@ QDateTime QLocale::toDateTime(const QString &string, const QString &format, QCal /*! \since 4.1 - Returns the decimal point character of this locale. + Returns the fractional part separator for this locale. + + This is the token that separates the whole number part from the fracional + part in the representation of a number which has a fractional part. This is + commonly called the "decimal point character" - even though, in many + locales, it is not a "point" (or similar dot). It is (since Qt 6.0) returned + as a string in case some locale needs more than one UTF-16 code-point to + represent its separator. \sa groupSeparator(), toString() */ @@ -2386,7 +2660,14 @@ QString QLocale::decimalPoint() const /*! \since 4.1 - Returns the group separator character of this locale. + Returns the digit-grouping separator for this locale. + + This is a token used to break up long sequences of digits, in the + representation of a number, to make it easier to read. In some locales it + may be empty, indicating that digits should not be broken up into groups in + this way. In others it may be a spacing character. It is (since Qt 6.0) + returned as a string in case some locale needs more than one UTF-16 + code-point to represent its separator. \sa decimalPoint(), toString() */ @@ -2398,7 +2679,12 @@ QString QLocale::groupSeparator() const /*! \since 4.1 - Returns the percent character of this locale. + Returns the percent marker of this locale. + + This is a token presumed to be appended to a number to indicate a + percentage. It is (since Qt 6.0) returned as a string because, in some + locales, it is not a single character - for example, because it includes a + text-direction-control character. \sa toString() */ @@ -2412,6 +2698,13 @@ QString QLocale::percent() const Returns the zero digit character of this locale. + This is a single Unicode character but may be encoded as a surrogate pair, + so is (since Qt 6.0) returned as a string. In most locales, other digits + follow it in Unicode ordering - however, some number systems, notably those + using U+3007 as zero, do not have contiguous digits. Use toString() to + obtain suitable representations of numbers, rather than trying to construct + them from this zero digit. + \sa toString() */ QString QLocale::zeroDigit() const @@ -2422,7 +2715,12 @@ QString QLocale::zeroDigit() const /*! \since 4.1 - Returns the negative sign character of this locale. + Returns the negative sign indicator of this locale. + + This is a token presumed to be used as a prefix to a number to indicate that + it is negative. It is (since Qt 6.0) returned as a string because, in some + locales, it is not a single character - for example, because it includes a + text-direction-control character. \sa positiveSign(), toString() */ @@ -2434,7 +2732,12 @@ QString QLocale::negativeSign() const /*! \since 4.5 - Returns the positive sign character of this locale. + Returns the positive sign indicator of this locale. + + This is a token presumed to be used as a prefix to a number to indicate that + it is positive. It is (since Qt 6.0) returned as a string because, in some + locales, it is not a single character - for example, because it includes a + text-direction-control character. \sa negativeSign(), toString() */ @@ -2446,8 +2749,13 @@ QString QLocale::positiveSign() const /*! \since 4.1 - Returns the exponential character of this locale, used to separate exponent - from mantissa in some floating-point numeric representations. + Returns the exponent separator for this locale. + + This is a token used to separate mantissa from exponent in some + floating-point numeric representations. It is (since Qt 6.0) returned as a + string because, in some locales, it is not a single character - for example, + it may consist of a multiplication sign and a representation of the "ten to + the power" operator. \sa toString(double, char, int) */ @@ -2456,57 +2764,66 @@ QString QLocale::exponential() const return d->m_data->exponentSeparator(); } -static bool qIsUpper(char c) -{ - return c >= 'A' && c <= 'Z'; -} - -static char qToLower(char c) -{ - if (c >= 'A' && c <= 'Z') - return c - 'A' + 'a'; - else - return c; -} - /*! \overload - - \a f and \a prec have the same meaning as in QString::number(double, char, int). - - \sa toDouble(), numberOptions(), exponential(), decimalPoint(), zeroDigit(), positiveSign(), percent() + Returns a string representing the floating-point number \a f. + + The form of the representation is controlled by the \a format and \a + precision parameters. + + The \a format defaults to \c{'g'}. It can be any of the following: + + \table + \header \li Format \li Meaning \li Meaning of \a precision + \row \li \c 'e' \li format as [-]9.9e[+|-]999 \li number of digits \e after the decimal point + \row \li \c 'E' \li format as [-]9.9E[+|-]999 \li " + \row \li \c 'f' \li format as [-]9.9 \li " + \row \li \c 'F' \li same as \c 'f' except for INF and NAN (see below) \li " + \row \li \c 'g' \li use \c 'e' or \c 'f' format, whichever is more concise \li maximum number of significant digits (trailing zeroes are omitted) + \row \li \c 'G' \li use \c 'E' or \c 'F' format, whichever is more concise \li " + \endtable + + The special \a precision value QLocale::FloatingPointShortest selects the + shortest representation that, when read as a number, gets back the original floating-point + value. Aside from that, any negative \a precision is ignored in favor of the + default, 6. + + For the \c 'e', \c 'f' and \c 'g' formats, positive infinity is represented + as "inf", negative infinity as "-inf" and floating-point NaN (not-a-number) + values are represented as "nan". For the \c 'E', \c 'F' and \c 'G' formats, + "INF" and "NAN" are used instead. This does not vary with locale. + + \sa toDouble(), numberOptions(), exponential(), decimalPoint(), zeroDigit(), + positiveSign(), percent(), toCurrencyString(), formattedDataSize(), + QLocale::FloatingPointPrecisionOption */ -QString QLocale::toString(double i, char f, int prec) const +QString QLocale::toString(double f, char format, int precision) const { QLocaleData::DoubleForm form = QLocaleData::DFDecimal; - uint flags = 0; + uint flags = isAsciiUpper(format) ? QLocaleData::CapitalEorX : 0; - if (qIsUpper(f)) - flags = QLocaleData::CapitalEorX; - f = qToLower(f); - - switch (f) { - case 'f': - form = QLocaleData::DFDecimal; - break; - case 'e': - form = QLocaleData::DFExponent; - break; - case 'g': - form = QLocaleData::DFSignificantDigits; - break; - default: - break; + switch (QtMiscUtils::toAsciiLower(format)) { + case 'f': + form = QLocaleData::DFDecimal; + break; + case 'e': + form = QLocaleData::DFExponent; + break; + case 'g': + form = QLocaleData::DFSignificantDigits; + break; + default: + break; } if (!(d->m_numberOptions & OmitGroupSeparator)) - flags |= QLocaleData::ThousandsGroup; + flags |= QLocaleData::GroupDigits; if (!(d->m_numberOptions & OmitLeadingZeroInExponent)) flags |= QLocaleData::ZeroPadExponent; if (d->m_numberOptions & IncludeTrailingZeroesAfterDot) flags |= QLocaleData::AddTrailingZeroes; - return d->m_data->doubleToString(i, prec, form, -1, flags); + return d->m_data->doubleToString(f, precision, form, -1, flags); } /*! @@ -2531,66 +2848,87 @@ QString QLocale::toString(double i, char f, int prec) const /*! Returns a QLocale object initialized to the system locale. - On Windows and Mac, this locale will use the decimal/grouping characters and - date/time formats specified in the system configuration panel. + The system locale may use system-specific sources for locale data, where + available, otherwise falling back on QLocale's built-in database entry for + the language, script and territory the system reports. + + For example, on Windows and Mac, this locale will use the decimal/grouping + characters and date/time formats specified in the system configuration + panel. \sa c() */ QLocale QLocale::system() { - QT_PREPEND_NAMESPACE(systemData)(); // trigger updating of the system data if necessary - if (systemLocalePrivate.isDestroyed()) - return QLocale(QLocale::C); - return QLocale(*systemLocalePrivate->data()); -} + 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); +} /*! - \since 4.8 - Returns a list of valid locale objects that match the given \a language, \a - script and \a country. + script and \a territory. Getting a list of all locales: QList<QLocale> allLocales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, - QLocale::AnyCountry); + QLocale::AnyTerritory); Getting a list of locales suitable for Russia: QList<QLocale> locales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::Russia); */ -QList<QLocale> QLocale::matchingLocales(QLocale::Language language, - QLocale::Script script, - QLocale::Country country) +QList<QLocale> QLocale::matchingLocales(Language language, Script script, Territory territory) { - if (uint(language) > QLocale::LastLanguage || uint(script) > QLocale::LastScript || - uint(country) > QLocale::LastCountry) + 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 (language == QLocale::AnyLanguage && script == QLocale::AnyScript - && country == QLocale::AnyCountry) { + if (filter.matchesAll()) result.reserve(locale_data_size); - } - const QLocaleData *data = locale_data + locale_index[language]; - while ( (data != locale_data + locale_data_size) - && (language == QLocale::AnyLanguage || data->m_language_id == uint(language))) { - if ((script == QLocale::AnyScript || data->m_script_id == uint(script)) - && (country == QLocale::AnyCountry || data->m_country_id == uint(country))) { - result.append(QLocale(*(data->m_language_id == C ? c_private() - : QLocalePrivate::create(data)))); + + quint16 index = locale_index[language]; + // There may be no matches, for some languages (e.g. Abkhazian at CLDR v39). + while (filter.acceptLanguage(locale_data[index].m_language_id)) { + const QLocaleId id = locale_data[index].id(); + if (filter.acceptScriptTerritory(id)) { + result.append(QLocale(*(id.language_id == C ? c_private() + : new QLocalePrivate(locale_data + index, index)))); } - ++data; + ++index; } + + // Add current system locale, if it matches + const auto syslocaledata = systemData(); + + if (filter.acceptLanguage(syslocaledata->m_language_id)) { + const QLocaleId id = syslocaledata->id(); + if (filter.acceptScriptTerritory(id)) + result.append(system()); + } + return result; } +#if QT_DEPRECATED_SINCE(6, 6) /*! - \obsolete + \deprecated [6.6] Use \l matchingLocales() instead and consult the \l territory() of each. \since 4.3 Returns the list of countries that have entries for \a language in Qt's locale @@ -2601,23 +2939,14 @@ QList<QLocale> QLocale::matchingLocales(QLocale::Language language, */ QList<QLocale::Country> QLocale::countriesForLanguage(Language language) { + const auto locales = matchingLocales(language, AnyScript, AnyCountry); QList<Country> result; - if (language == C) { - result << AnyCountry; - return result; - } - - unsigned language_id = language; - const QLocaleData *data = locale_data + locale_index[language_id]; - while (data->m_language_id == language_id) { - const QLocale::Country country = static_cast<Country>(data->m_country_id); - if (!result.contains(country)) - result.append(country); - ++data; - } - + result.reserve(locales.size()); + for (const auto &locale : locales) + result.append(locale.territory()); return result; } +#endif /*! \since 4.2 @@ -2689,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 */ @@ -2797,20 +3134,30 @@ 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_data_offset], - 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 == systemData()) { + if (locale.d->m_data == &systemLocaleData) { Q_ASSERT(month >= 1 && month <= 12); - QVariant res = systemLocale()->query(format == QLocale::LongFormat - ? QSystemLocale::MonthNameLong - : QSystemLocale::MonthNameShort, - month); + QSystemLocale::QueryType queryType = QSystemLocale::MonthNameLong; + switch (format) { + case QLocale::LongFormat: + queryType = QSystemLocale::MonthNameLong; + break; + case QLocale::ShortFormat: + queryType = QSystemLocale::MonthNameShort; + break; + case QLocale::NarrowFormat: + queryType = QSystemLocale::MonthNameNarrow; + break; + } + QVariant res = systemLocale()->query(queryType, month); if (!res.isNull()) return res.toString(); } @@ -2823,20 +3170,30 @@ QString QCalendarBackend::standaloneMonthName(const QLocale &locale, int month, QLocale::FormatType format) const { Q_ASSERT(month >= 1 && month <= maximumMonthsInYear()); - return rawStandaloneMonthName(localeMonthIndexData()[locale.d->m_data_offset], - 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 == systemData()) { + if (locale.d->m_data == &systemLocaleData) { Q_ASSERT(month >= 1 && month <= 12); - QVariant res = systemLocale()->query(format == QLocale::LongFormat - ? QSystemLocale::StandaloneMonthNameLong - : QSystemLocale::StandaloneMonthNameShort, - month); + QSystemLocale::QueryType queryType = QSystemLocale::StandaloneMonthNameLong; + switch (format) { + case QLocale::LongFormat: + queryType = QSystemLocale::StandaloneMonthNameLong; + break; + case QLocale::ShortFormat: + queryType = QSystemLocale::StandaloneMonthNameShort; + break; + case QLocale::NarrowFormat: + queryType = QSystemLocale::StandaloneMonthNameNarrow; + break; + } + QVariant res = systemLocale()->query(queryType, month); if (!res.isNull()) return res.toString(); } @@ -2854,11 +3211,20 @@ QString QCalendarBackend::weekDayName(const QLocale &locale, int day, return QString(); #ifndef QT_NO_SYSTEMLOCALE - if (locale.d->m_data == systemData()) { - QVariant res = systemLocale()->query(format == QLocale::LongFormat - ? QSystemLocale::DayNameLong - : QSystemLocale::DayNameShort, - day); + if (locale.d->m_data == &systemLocaleData) { + QSystemLocale::QueryType queryType = QSystemLocale::DayNameLong; + switch (format) { + case QLocale::LongFormat: + queryType = QSystemLocale::DayNameLong; + break; + case QLocale::ShortFormat: + queryType = QSystemLocale::DayNameShort; + break; + case QLocale::NarrowFormat: + queryType = QSystemLocale::DayNameNarrow; + break; + } + QVariant res = systemLocale()->query(queryType, day); if (!res.isNull()) return res.toString(); } @@ -2874,11 +3240,20 @@ QString QCalendarBackend::standaloneWeekDayName(const QLocale &locale, int day, return QString(); #ifndef QT_NO_SYSTEMLOCALE - if (locale.d->m_data == systemData()) { - QVariant res = systemLocale()->query(format == QLocale::LongFormat - ? QSystemLocale::DayNameLong - : QSystemLocale::DayNameShort, - day); + if (locale.d->m_data == &systemLocaleData) { + QSystemLocale::QueryType queryType = QSystemLocale::StandaloneDayNameLong; + switch (format) { + case QLocale::LongFormat: + queryType = QSystemLocale::StandaloneDayNameLong; + break; + case QLocale::ShortFormat: + queryType = QSystemLocale::StandaloneDayNameShort; + break; + case QLocale::NarrowFormat: + queryType = QSystemLocale::StandaloneDayNameNarrow; + break; + } + QVariant res = systemLocale()->query(queryType, day); if (!res.isNull()) return res.toString(); } @@ -2897,8 +3272,8 @@ QString QCalendarBackend::standaloneWeekDayName(const QLocale &locale, int day, Qt::DayOfWeek QLocale::firstDayOfWeek() const { #ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { - QVariant res = systemLocale()->query(QSystemLocale::FirstDayOfWeek, QVariant()); + if (d->m_data == &systemLocaleData) { + const auto res = systemLocale()->query(QSystemLocale::FirstDayOfWeek); if (!res.isNull()) return static_cast<Qt::DayOfWeek>(res.toUInt()); } @@ -2908,10 +3283,10 @@ Qt::DayOfWeek QLocale::firstDayOfWeek() const QLocale::MeasurementSystem QLocalePrivate::measurementSystem() const { - for (int i = 0; i < ImperialMeasurementSystemsCount; ++i) { - if (ImperialMeasurementSystems[i].languageId == m_data->m_language_id - && ImperialMeasurementSystems[i].countryId == m_data->m_country_id) { - return ImperialMeasurementSystems[i].system; + for (const auto &system : ImperialMeasurementSystems) { + if (system.languageId == m_data->m_language_id + && system.territoryId == m_data->m_territory_id) { + return system.system; } } return QLocale::MetricSystem; @@ -2925,10 +3300,11 @@ QLocale::MeasurementSystem QLocalePrivate::measurementSystem() const QList<Qt::DayOfWeek> QLocale::weekdays() const { #ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { - QVariant res = systemLocale()->query(QSystemLocale::Weekdays, QVariant()); - if (!res.isNull()) - return static_cast<QList<Qt::DayOfWeek> >(qvariant_cast<QList<Qt::DayOfWeek> >(res)); + if (d->m_data == &systemLocaleData) { + auto res + = qvariant_cast<QList<Qt::DayOfWeek> >(systemLocale()->query(QSystemLocale::Weekdays)); + if (!res.isEmpty()) + return res; } #endif QList<Qt::DayOfWeek> weekdays; @@ -2950,8 +3326,8 @@ QList<Qt::DayOfWeek> QLocale::weekdays() const QLocale::MeasurementSystem QLocale::measurementSystem() const { #ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { - QVariant res = systemLocale()->query(QSystemLocale::MeasurementSystem, QVariant()); + if (d->m_data == &systemLocaleData) { + const auto res = systemLocale()->query(QSystemLocale::MeasurementSystem); if (!res.isNull()) return MeasurementSystem(res.toInt()); } @@ -2968,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; @@ -3063,10 +3439,10 @@ QString QLocale::toLower(const QString &str) const QString QLocale::amText() const { #ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { - QVariant res = systemLocale()->query(QSystemLocale::AMText, QVariant()); - if (!res.isNull()) - return res.toString(); + if (d->m_data == &systemLocaleData) { + auto res = systemLocale()->query(QSystemLocale::AMText).toString(); + if (!res.isEmpty()) + return res; } #endif return d->m_data->anteMeridiem().getData(am_data); @@ -3083,10 +3459,10 @@ QString QLocale::amText() const QString QLocale::pmText() const { #ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { - QVariant res = systemLocale()->query(QSystemLocale::PMText, QVariant()); - if (!res.isNull()) - return res.toString(); + if (d->m_data == &systemLocaleData) { + auto res = systemLocale()->query(QSystemLocale::PMText).toString(); + if (!res.isEmpty()) + return res; } #endif return d->m_data->postMeridiem().getData(pm_data); @@ -3128,7 +3504,19 @@ QString QCalendarBackend::dateTimeToString(QStringView format, const QDateTime & day = parts.day; } - int i = 0; + auto appendToResult = [&](int t, int repeat) { + auto data = locale.d->m_data; + if (repeat > 1) + result.append(data->longLongToString(t, -1, 10, repeat, QLocaleData::ZeroPadded)); + else + result.append(data->longLongToString(t)); + }; + + auto formatType = [](int repeat) { + return repeat == 3 ? QLocale::ShortFormat : QLocale::LongFormat; + }; + + qsizetype i = 0; while (i < format.size()) { if (format.at(i).unicode() == '\'') { result.append(qt_readEscapedFormatString(format, &i)); @@ -3136,7 +3524,9 @@ QString QCalendarBackend::dateTimeToString(QStringView format, const QDateTime & } const QChar c = format.at(i); - int 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()) { @@ -3148,15 +3538,11 @@ QString QCalendarBackend::dateTimeToString(QStringView format, const QDateTime & repeat = 2; switch (repeat) { - case 4: { - const int len = (year < 0) ? 5 : 4; - result.append(locale.d->m_data->longLongToString(year, -1, 10, len, - QLocaleData::ZeroPadded)); + case 4: + appendToResult(year, (year < 0) ? 5 : 4); break; - } case 2: - result.append(locale.d->m_data->longLongToString(year % 100, -1, 10, 2, - QLocaleData::ZeroPadded)); + appendToResult(year % 100, 2); break; default: repeat = 1; @@ -3168,43 +3554,20 @@ QString QCalendarBackend::dateTimeToString(QStringView format, const QDateTime & case 'M': used = true; repeat = qMin(repeat, 4); - switch (repeat) { - case 1: - result.append(locale.d->m_data->longLongToString(month)); - break; - case 2: - result.append(locale.d->m_data->longLongToString(month, -1, 10, 2, - QLocaleData::ZeroPadded)); - break; - case 3: - result.append(monthName(locale, month, year, QLocale::ShortFormat)); - break; - case 4: - result.append(monthName(locale, month, year, QLocale::LongFormat)); - break; - } + if (repeat <= 2) + appendToResult(month, repeat); + else + result.append(monthName(locale, month, year, formatType(repeat))); break; case 'd': used = true; repeat = qMin(repeat, 4); - switch (repeat) { - case 1: - result.append(locale.d->m_data->longLongToString(day)); - break; - case 2: - result.append(locale.d->m_data->longLongToString(day, -1, 10, 2, - QLocaleData::ZeroPadded)); - break; - case 3: - result.append(locale.dayName( - dayOfWeek(date.toJulianDay()), QLocale::ShortFormat)); - break; - case 4: - result.append(locale.dayName( - dayOfWeek(date.toJulianDay()), QLocale::LongFormat)); - break; - } + if (repeat <= 2) + appendToResult(day, repeat); + else + result.append( + locale.dayName(dayOfWeek(date.toJulianDay()), formatType(repeat))); break; default: @@ -3223,83 +3586,51 @@ QString QCalendarBackend::dateTimeToString(QStringView format, const QDateTime & else if (hour == 0) hour = 12; } - - switch (repeat) { - case 1: - result.append(locale.d->m_data->longLongToString(hour)); - break; - case 2: - result.append(locale.d->m_data->longLongToString(hour, -1, 10, 2, - QLocaleData::ZeroPadded)); - break; - } + appendToResult(hour, repeat); break; } case 'H': used = true; repeat = qMin(repeat, 2); - switch (repeat) { - case 1: - result.append(locale.d->m_data->longLongToString(time.hour())); - break; - case 2: - result.append(locale.d->m_data->longLongToString(time.hour(), -1, 10, 2, - QLocaleData::ZeroPadded)); - break; - } + appendToResult(time.hour(), repeat); break; case 'm': used = true; repeat = qMin(repeat, 2); - switch (repeat) { - case 1: - result.append(locale.d->m_data->longLongToString(time.minute())); - break; - case 2: - result.append(locale.d->m_data->longLongToString(time.minute(), -1, 10, 2, - QLocaleData::ZeroPadded)); - break; - } + appendToResult(time.minute(), repeat); break; case 's': used = true; repeat = qMin(repeat, 2); - switch (repeat) { - case 1: - result.append(locale.d->m_data->longLongToString(time.second())); - break; - case 2: - result.append(locale.d->m_data->longLongToString(time.second(), -1, 10, 2, - QLocaleData::ZeroPadded)); - break; - } - break; - - case 'a': - used = true; - repeat = format.mid(i + 1).startsWith(QLatin1Char('p')) ? 2 : 1; - result.append(time.hour() < 12 ? locale.amText().toLower() - : locale.pmText().toLower()); + appendToResult(time.second(), repeat); break; case 'A': + case 'a': { + QString text = time.hour() < 12 ? locale.amText() : locale.pmText(); used = true; - repeat = format.mid(i + 1).startsWith(QLatin1Char('P')) ? 2 : 1; - result.append(time.hour() < 12 ? locale.amText().toUpper() - : locale.pmText().toUpper()); + repeat = 1; + if (format.mid(i + 1).startsWith(u'p', Qt::CaseInsensitive)) + ++repeat; + if (c.unicode() == 'A' && (repeat == 1 || format.at(i + 1).unicode() == 'P')) + text = std::move(text).toUpper(); + else if (c.unicode() == 'a' && (repeat == 1 || format.at(i + 1).unicode() == 'p')) + text = std::move(text).toLower(); + // else 'Ap' or 'aP' => use CLDR text verbatim, preserving case + result.append(text); break; + } case 'z': used = true; - repeat = (repeat >= 3) ? 3 : 1; + repeat = qMin(repeat, 3); // note: the millisecond component is treated like the decimal part of the seconds // so ms == 2 is always printed as "002", but ms == 200 can be either "2" or "200" - result.append(locale.d->m_data->longLongToString(time.msec(), -1, 10, 3, - QLocaleData::ZeroPadded)); - if (repeat == 1) { + appendToResult(time.msec(), 3); + if (repeat != 3) { if (result.endsWith(locale.zeroDigit())) result.chop(1); if (result.endsWith(locale.zeroDigit())) @@ -3307,20 +3638,63 @@ QString QCalendarBackend::dateTimeToString(QStringView format, const QDateTime & } break; - case 't': + 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 = 1; - // If we have a QDateTime use the time spec otherwise use the current system tzname - result.append(formatDate ? datetime.timeZoneAbbreviation() - : QDateTime::currentDateTime().timeZoneAbbreviation()); + 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) { + case 4: + text = tzAbbr(when, Long); + break; + 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 = 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()) + result.append(text); break; + } default: break; } } if (!used) - result.append(QString(repeat, c)); + result.resize(result.size() + repeat, c); i += repeat; } @@ -3332,53 +3706,50 @@ QString QCalendarBackend::dateTimeToString(QStringView format, const QDateTime & QString QLocaleData::doubleToString(double d, int precision, DoubleForm form, int width, unsigned flags) const { - return doubleToString(zeroDigit(), positiveSign(), negativeSign(), - exponentSeparator(), groupSeparator(), decimalPoint(), - d, precision, form, width, flags); -} - -QString QLocaleData::doubleToString(const QString &zero, const QString &plus, const QString &minus, - const QString &exponential, - const QString &group, const QString &decimal, - double d, int precision, DoubleForm form, int width, - unsigned flags) -{ + // Although the special handling of F.P.Shortest below is limited to + // DFSignificantDigits, the double-conversion library does treat it + // specially for the other forms, shedding trailing zeros for DFDecimal and + // using the shortest mantissa that faithfully represents the value for + // DFExponent. if (precision != QLocale::FloatingPointShortest && precision < 0) precision = 6; if (width < 0) width = 0; - bool negative = false; - QString num_str; - int decpt; - int bufSize = 1; + qsizetype bufSize = 1; if (precision == QLocale::FloatingPointShortest) - bufSize += DoubleMaxSignificant; - else if (form == DFDecimal) // optimize for numbers between -512k and 512k - bufSize += ((d > (1 << 19) || d < -(1 << 19)) ? DoubleMaxDigitsBeforeDecimal : 6) + - precision; - else // Add extra digit due to different interpretations of precision. Also, "nan" has to fit. - bufSize += qMax(2, precision) + 1; + bufSize += std::numeric_limits<double>::max_digits10; + 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" QVarLengthArray<char> buf(bufSize); int length; - + bool negative = false; qt_doubleToAscii(d, form, precision, buf.data(), bufSize, negative, length, decpt); - if (qstrncmp(buf.data(), "inf", 3) == 0 || qstrncmp(buf.data(), "nan", 3) == 0) { - num_str = QString::fromLatin1(buf.data(), length); - } else { // Handle normal numbers + const QString prefix = signPrefix(negative && !isZero(d), flags); + QString numStr; + + if (length == 3 + && (qstrncmp(buf.data(), "inf", 3) == 0 || qstrncmp(buf.data(), "nan", 3) == 0)) { + numStr = QString::fromLatin1(buf.data(), length); + } else { // Handle finite values + const QString zero = zeroDigit(); QString digits = QString::fromLatin1(buf.data(), length); if (zero == u"0") { // No need to convert digits. + Q_ASSERT(std::all_of(buf.cbegin(), buf.cbegin() + length, isAsciiDigit)); + // That check is taken care of in unicodeForDigits, below. } else if (zero.size() == 2 && zero.at(0).isHighSurrogate()) { - const uint zeroUcs4 = QChar::surrogateToUcs4(zero.at(0), zero.at(1)); + const char32_t zeroUcs4 = QChar::surrogateToUcs4(zero.at(0), zero.at(1)); QString converted; converted.reserve(2 * digits.size()); - for (int i = 0; i < digits.length(); ++i) { - const uint digit = zeroUcs4 - '0' + digits.at(i).unicode(); + for (QChar ch : std::as_const(digits)) { + const char32_t digit = unicodeForDigit(ch.unicode() - '0', zeroUcs4); Q_ASSERT(QChar::requiresSurrogates(digit)); converted.append(QChar::highSurrogate(digit)); converted.append(QChar::lowSurrogate(digit)); @@ -3387,329 +3758,527 @@ QString QLocaleData::doubleToString(const QString &zero, const QString &plus, co } else { Q_ASSERT(zero.size() == 1); Q_ASSERT(!zero.at(0).isSurrogate()); - ushort z = zero.at(0).unicode() - '0'; - for (int i = 0; i < digits.length(); ++i) - reinterpret_cast<ushort *>(digits.data())[i] += z; + char16_t z = zero.at(0).unicode(); + char16_t *const value = reinterpret_cast<char16_t *>(digits.data()); + for (qsizetype i = 0; i < digits.size(); ++i) + value[i] = unicodeForDigit(value[i] - '0', z); } - bool always_show_decpt = (flags & ForcePoint); + const bool mustMarkDecimal = flags & ForcePoint; + const bool groupDigits = flags & GroupDigits; + const int minExponentDigits = flags & ZeroPadExponent ? 2 : 1; switch (form) { - case DFExponent: { - num_str = exponentForm(zero, decimal, exponential, group, plus, minus, - digits, decpt, precision, PMDecimalDigits, - always_show_decpt, flags & ZeroPadExponent); - break; - } - case DFDecimal: { - num_str = decimalForm(zero, decimal, group, - digits, decpt, precision, PMDecimalDigits, - always_show_decpt, flags & ThousandsGroup); - break; - } - case DFSignificantDigits: { - PrecisionMode mode = (flags & AddTrailingZeroes) ? - PMSignificantDigits : PMChopTrailingZeros; - - const auto digitWidth = zero.size(); - int cutoff = precision < 0 ? 6 : precision; - // Find out which representation is shorter - if (precision == QLocale::FloatingPointShortest && decpt > 0) { - cutoff = digits.length() / digitWidth + 4; // 'e', '+'/'-', one digit exponent - if (decpt <= 10) - ++cutoff; - else - cutoff += decpt > 100 ? 2 : 1; - if (!always_show_decpt && digits.length() / digitWidth > decpt) - ++cutoff; // decpt shown in exponent form, but not in decimal form + case DFExponent: + numStr = exponentForm(std::move(digits), decpt, precision, PMDecimalDigits, + mustMarkDecimal, minExponentDigits); + break; + case DFDecimal: + numStr = decimalForm(std::move(digits), decpt, precision, PMDecimalDigits, + mustMarkDecimal, groupDigits); + break; + case DFSignificantDigits: { + PrecisionMode mode + = (flags & AddTrailingZeroes) ? PMSignificantDigits : PMChopTrailingZeros; + + /* POSIX specifies sprintf() to follow fprintf(), whose 'g/G' format + says; with P = 6 if precision unspecified else 1 if precision is + 0 else precision; when 'e/E' would have exponent X, use: + * 'f/F' if P > X >= -4, with precision P-1-X + * 'e/E' otherwise, with precision P-1 + Helpfully, we already have mapped precision < 0 to 6 - except for + F.P.Shortest mode, which is its own story - and those of our + callers with unspecified precision either used 6 or -1 for it. + */ + bool useDecimal; + if (precision == QLocale::FloatingPointShortest) { + // Find out which representation is shorter. + // Set bias to everything added to exponent form but not + // decimal, minus the converse. + + // Exponent adds separator, sign and digits: + int bias = 2 + minExponentDigits; + // Decimal form may get grouping separators inserted: + if (groupDigits && decpt >= m_grouping_top + m_grouping_least) + bias -= (decpt - m_grouping_least) / m_grouping_higher + 1; + // X = decpt - 1 needs two digits if decpt > 10: + if (decpt > 10 && minExponentDigits == 1) + ++bias; + // Assume digitCount < 95, so we can ignore the 3-digit + // exponent case (we'll set useDecimal false anyway). + + const qsizetype digitCount = digits.size() / zero.size(); + if (!mustMarkDecimal) { + // Decimal separator is skipped if at end; adjust if + // that happens for only one form: + if (digitCount <= decpt && digitCount > 1) + ++bias; // decimal but not exponent + else if (digitCount == 1 && decpt <= 0) + --bias; // exponent but not decimal } - - if (decpt != digits.length() / digitWidth && (decpt <= -4 || decpt > cutoff)) - num_str = exponentForm(zero, decimal, exponential, group, plus, minus, - digits, decpt, precision, mode, - always_show_decpt, flags & ZeroPadExponent); - else - num_str = decimalForm(zero, decimal, group, - digits, decpt, precision, mode, - always_show_decpt, flags & ThousandsGroup); - break; + // When 0 < decpt <= digitCount, the forms have equal digit + // counts, plus things bias has taken into account; otherwise + // decimal form's digit count is right-padded with zeros to + // decpt, when decpt is positive, otherwise it's left-padded + // with 1 - decpt zeros. + useDecimal = (decpt <= 0 ? 1 - decpt <= bias + : decpt <= digitCount ? 0 <= bias : decpt <= digitCount + bias); + } else { + // X == decpt - 1, POSIX's P; -4 <= X < P iff -4 < decpt <= P + Q_ASSERT(precision >= 0); + useDecimal = decpt > -4 && decpt <= (precision ? precision : 1); } + + numStr = useDecimal + ? decimalForm(std::move(digits), decpt, precision, mode, + mustMarkDecimal, groupDigits) + : exponentForm(std::move(digits), decpt, precision, mode, + mustMarkDecimal, minExponentDigits); + break; + } } - if (isZero(d)) - negative = false; - - // pad with zeros. LeftAdjusted overrides this flag. Also, we don't - // pad special numbers - if (flags & QLocaleData::ZeroPadded && !(flags & QLocaleData::LeftAdjusted)) { - int num_pad_chars = width - num_str.length() / zero.length(); - // leave space for the sign - if (negative - || flags & QLocaleData::AlwaysShowSign - || flags & QLocaleData::BlankBeforePositive) - --num_pad_chars; - - for (int i = 0; i < num_pad_chars; ++i) - num_str.prepend(zero); + // Pad with zeros. LeftAdjusted overrides ZeroPadded. + if (flags & ZeroPadded && !(flags & LeftAdjusted)) { + for (qsizetype i = numStr.size() / zero.size() + prefix.size(); i < width; ++i) + numStr.prepend(zero); } } - // add sign - if (negative) - num_str.prepend(minus); - else if (flags & QLocaleData::AlwaysShowSign) - num_str.prepend(plus); - else if (flags & QLocaleData::BlankBeforePositive) - num_str.prepend(QLatin1Char(' ')); - - if (flags & QLocaleData::CapitalEorX) - num_str = std::move(num_str).toUpper(); - - return num_str; + return prefix + (flags & CapitalEorX ? std::move(numStr).toUpper() : numStr); } -QString QLocaleData::longLongToString(qlonglong l, int precision, - int base, int width, unsigned flags) const +QString QLocaleData::decimalForm(QString &&digits, int decpt, int precision, + PrecisionMode pm, bool mustMarkDecimal, + bool groupDigits) const { - return longLongToString(zeroDigit(), groupSeparator(), positiveSign(), negativeSign(), - l, precision, base, width, flags); -} + const QString zero = zeroDigit(); + const auto digitWidth = zero.size(); + Q_ASSERT(digitWidth == 1 || digitWidth == 2); + Q_ASSERT(digits.size() % digitWidth == 0); -QString QLocaleData::longLongToString(const QString &zero, const QString &group, - const QString &plus, const QString &minus, - qlonglong l, int precision, - int base, int width, unsigned flags) -{ - bool precision_not_specified = false; - if (precision == -1) { - precision_not_specified = true; - precision = 1; + // Separator needs to go at index decpt: so add zeros before or after the + // given digits, if they don't reach that position already: + if (decpt < 0) { + for (; decpt < 0; ++decpt) + digits.prepend(zero); + } else { + for (qsizetype i = digits.size() / digitWidth; i < decpt; ++i) + digits.append(zero); } - bool negative = l < 0; - if (base != 10) { - // these are not supported by sprintf for octal and hex - flags &= ~AlwaysShowSign; - flags &= ~BlankBeforePositive; - negative = false; // neither are negative numbers + switch (pm) { + case PMDecimalDigits: + for (qsizetype i = digits.size() / digitWidth - decpt; i < precision; ++i) + digits.append(zero); + break; + case PMSignificantDigits: + for (qsizetype i = digits.size() / digitWidth; i < precision; ++i) + digits.append(zero); + break; + case PMChopTrailingZeros: + Q_ASSERT(digits.size() / digitWidth <= qMax(decpt, 1) || !digits.endsWith(zero)); + break; } -QT_WARNING_PUSH - /* "unary minus operator applied to unsigned type, result still unsigned" */ -QT_WARNING_DISABLE_MSVC(4146) - /* - Negating std::numeric_limits<qlonglong>::min() hits undefined behavior, so - taking an absolute value has to cast to unsigned to change sign. - */ - QString num_str = qulltoa(negative ? -qulonglong(l) : qulonglong(l), base, zero); -QT_WARNING_POP - - const QString resultZero = base == 10 ? zero : QStringLiteral("0"); - const auto digitWidth = resultZero.size(); - uint cnt_thousand_sep = 0; - if (base == 10) { - if (flags & ThousandsGroup) { - for (int i = num_str.length() / digitWidth - 3; i > 0; i -= 3) { - num_str.insert(i * digitWidth, group); - ++cnt_thousand_sep; - } - } else if (flags & IndianNumberGrouping) { - const int size = num_str.length(); - if (size > 3 * digitWidth) - num_str.insert(size - 3 * digitWidth , group); - for (int i = size / digitWidth - 5; i > 0; i -= 2) { - num_str.insert(i * digitWidth, group); - ++cnt_thousand_sep; - } + if (mustMarkDecimal || decpt < digits.size() / digitWidth) + digits.insert(decpt * digitWidth, decimalPoint()); + + if (groupDigits) { + const QString group = groupSeparator(); + qsizetype i = decpt - m_grouping_least; + if (i >= m_grouping_top) { + digits.insert(i * digitWidth, group); + while ((i -= m_grouping_higher) > 0) + digits.insert(i * digitWidth, group); } } - for (int i = num_str.length()/* - cnt_thousand_sep*/; i < precision; ++i) - num_str.prepend(resultZero); - - if ((flags & ShowBase) - && base == 8 - && (num_str.isEmpty() || num_str[0].unicode() != QLatin1Char('0'))) - num_str.prepend(QLatin1Char('0')); - - // LeftAdjusted overrides this flag ZeroPadded. sprintf only padds - // when precision is not specified in the format string - bool zero_padded = flags & ZeroPadded - && !(flags & LeftAdjusted) - && precision_not_specified; - - if (zero_padded) { - int num_pad_chars = width - num_str.length() / digitWidth; - - // leave space for the sign - if (negative - || flags & AlwaysShowSign - || flags & BlankBeforePositive) - --num_pad_chars; - - // leave space for optional '0x' in hex form - if (base == 16 && (flags & ShowBase)) - num_pad_chars -= 2; - // leave space for optional '0b' in binary form - else if (base == 2 && (flags & ShowBase)) - num_pad_chars -= 2; - - while (num_pad_chars-- > 0) - num_str.prepend(resultZero); + if (decpt == 0) + digits.prepend(zero); + + return std::move(digits); +} + +QString QLocaleData::exponentForm(QString &&digits, int decpt, int precision, + PrecisionMode pm, bool mustMarkDecimal, + int minExponentDigits) const +{ + const QString zero = zeroDigit(); + const auto digitWidth = zero.size(); + Q_ASSERT(digitWidth == 1 || digitWidth == 2); + Q_ASSERT(digits.size() % digitWidth == 0); + + switch (pm) { + case PMDecimalDigits: + for (qsizetype i = digits.size() / digitWidth; i < precision + 1; ++i) + digits.append(zero); + break; + case PMSignificantDigits: + for (qsizetype i = digits.size() / digitWidth; i < precision; ++i) + digits.append(zero); + break; + case PMChopTrailingZeros: + Q_ASSERT(digits.size() / digitWidth <= 1 || !digits.endsWith(zero)); + break; } - if (flags & CapitalEorX) - num_str = std::move(num_str).toUpper(); + if (mustMarkDecimal || digits.size() > digitWidth) + digits.insert(digitWidth, decimalPoint()); - if (base == 16 && (flags & ShowBase)) - num_str.prepend(QLatin1String(flags & UppercaseBase ? "0X" : "0x")); - if (base == 2 && (flags & ShowBase)) - num_str.prepend(QLatin1String(flags & UppercaseBase ? "0B" : "0b")); + digits.append(exponentSeparator()); + digits.append(longLongToString(decpt - 1, minExponentDigits, 10, -1, AlwaysShowSign)); + + return std::move(digits); +} - // add sign +QString QLocaleData::signPrefix(bool negative, unsigned flags) const +{ if (negative) - num_str.prepend(minus); - else if (flags & AlwaysShowSign) - num_str.prepend(plus); - else if (flags & BlankBeforePositive) - num_str.prepend(QLatin1Char(' ')); + return negativeSign(); + if (flags & AlwaysShowSign) + return positiveSign(); + if (flags & BlankBeforePositive) + return QStringView(u" ").toString(); + return {}; +} - return num_str; +QString QLocaleData::longLongToString(qlonglong n, int precision, + int base, int width, unsigned flags) const +{ + bool negative = n < 0; + + /* + Negating std::numeric_limits<qlonglong>::min() hits undefined behavior, so + taking an absolute value has to take a slight detour. + */ + QString numStr = qulltoa(negative ? 1u + qulonglong(-(n + 1)) : qulonglong(n), + base, zeroDigit()); + + return applyIntegerFormatting(std::move(numStr), negative, precision, base, width, flags); } QString QLocaleData::unsLongLongToString(qulonglong l, int precision, int base, int width, unsigned flags) const { - return unsLongLongToString(zeroDigit(), groupSeparator(), positiveSign(), - l, precision, base, width, flags); + const QString zero = zeroDigit(); + QString resultZero = base == 10 ? zero : QStringLiteral("0"); + return applyIntegerFormatting(l ? qulltoa(l, base, zero) : resultZero, + false, precision, base, width, flags); } -QString QLocaleData::unsLongLongToString(const QString &zero, const QString &group, - const QString &plus, - qulonglong l, int precision, - int base, int width, unsigned flags) +QString QLocaleData::applyIntegerFormatting(QString &&numStr, bool negative, int precision, + int base, int width, unsigned flags) const { - const QString resultZero = base == 10 ? zero : QStringLiteral("0"); - QString num_str = l ? qulltoa(l, base, zero) : resultZero; + const QString zero = base == 10 ? zeroDigit() : QStringLiteral("0"); + const auto digitWidth = zero.size(); + const auto digitCount = numStr.size() / digitWidth; - bool precision_not_specified = false; - if (precision == -1) { - if (flags == NoFlags) - return num_str; // fast-path: nothing below applies, so we're done. + const auto basePrefix = [&] () -> QStringView { + if (flags & ShowBase) { + const bool upper = flags & UppercaseBase; + if (base == 16) + return upper ? u"0X" : u"0x"; + if (base == 2) + return upper ? u"0B" : u"0b"; + if (base == 8 && !numStr.startsWith(zero)) + return zero; + } + return {}; + }(); + + const QString prefix = signPrefix(negative, flags) + basePrefix; + // Count how much of width we've used up. Each digit counts as one + qsizetype usedWidth = digitCount + prefix.size(); + + if (base == 10 && flags & GroupDigits) { + const QString group = groupSeparator(); + qsizetype i = digitCount - m_grouping_least; + if (i >= m_grouping_top) { + numStr.insert(i * digitWidth, group); + ++usedWidth; + while ((i -= m_grouping_higher) > 0) { + numStr.insert(i * digitWidth, group); + ++usedWidth; + } + } + // TODO: should we group any zero-padding we add later ? + } - precision_not_specified = true; + const bool noPrecision = precision == -1; + if (noPrecision) precision = 1; + + for (qsizetype i = numStr.size(); i < precision; ++i) { + numStr.prepend(zero); + usedWidth++; } - const auto digitWidth = resultZero.size(); - uint cnt_thousand_sep = 0; - if (base == 10) { - if (flags & ThousandsGroup) { - for (int i = num_str.length() / digitWidth - 3; i > 0; i -= 3) { - num_str.insert(i * digitWidth, group); - ++cnt_thousand_sep; - } - } else if (flags & IndianNumberGrouping) { - const int size = num_str.length(); - if (size > 3 * digitWidth) - num_str.insert(size - 3 * digitWidth , group); - for (int i = size / digitWidth - 5; i > 0; i -= 2) { - num_str.insert(i * digitWidth, group); - ++cnt_thousand_sep; + // LeftAdjusted overrides ZeroPadded; and sprintf() only pads when + // precision is not specified in the format string. + if (noPrecision && flags & ZeroPadded && !(flags & LeftAdjusted)) { + for (qsizetype i = usedWidth; i < width; ++i) + numStr.prepend(zero); + } + + QString result(flags & CapitalEorX ? std::move(numStr).toUpper() : std::move(numStr)); + if (prefix.size()) + result.prepend(prefix); + return result; +} + +inline QLocaleData::NumericData QLocaleData::numericData(QLocaleData::NumberMode mode) const +{ + NumericData result; + if (this == c()) { + result.isC = true; + return result; + } + result.setZero(zero().viewData(single_character_data)); + result.group = groupDelim().viewData(single_character_data); + // Note: minus, plus and exponent might not actually be single characters. + result.minus = minus().viewData(single_character_data); + result.plus = plus().viewData(single_character_data); + if (mode != IntegerMode) + result.decimal = decimalSeparator().viewData(single_character_data); + if (mode == DoubleScientificMode) { + result.exponent = exponential().viewData(single_character_data); + // exponentCyrillic means "apply the Cyrrilic-specific exponent hack" + result.exponentCyrillic = m_script_id == QLocale::CyrillicScript; + } +#ifndef QT_NO_SYSTEMLOCALE + if (this == &systemLocaleData) { + const auto getString = [sys = systemLocale()](QSystemLocale::QueryType query) { + return sys->query(query).toString(); + }; + if (mode != IntegerMode) { + result.sysDecimal = getString(QSystemLocale::DecimalPoint); + if (result.sysDecimal.size()) + result.decimal = QStringView{result.sysDecimal}; + } + result.sysGroup = getString(QSystemLocale::GroupSeparator); + if (result.sysGroup.size()) + result.group = QStringView{result.sysGroup}; + result.sysMinus = getString(QSystemLocale::NegativeSign); + if (result.sysMinus.size()) + result.minus = QStringView{result.sysMinus}; + result.sysPlus = getString(QSystemLocale::PositiveSign); + if (result.sysPlus.size()) + result.plus = QStringView{result.sysPlus}; + result.setZero(getString(QSystemLocale::ZeroDigit)); + } +#endif + + return result; +} + +namespace { +// A bit like QStringIterator but rather specialized ... and some of the tokens +// it recognizes aren't single Unicode code-points (but it does map each to a +// single character). +class NumericTokenizer +{ + // TODO: use deterministic finite-state-automata. + // TODO QTBUG-95460: CLDR has Inf/NaN representations per locale. + static constexpr char lettersInfNaN[] = "afin"; // Letters of Inf, NaN + static constexpr auto matchInfNaN = QtPrivate::makeCharacterSetMatch<lettersInfNaN>(); + const QStringView m_text; + const QLocaleData::NumericData m_guide; + qsizetype m_index = 0; + const QLocaleData::NumberMode m_mode; + static_assert('+' + 1 == ',' && ',' + 1 == '-' && '-' + 1 == '.'); + char lastMark; // C locale accepts '+' through lastMark. +public: + NumericTokenizer(QStringView text, QLocaleData::NumericData &&guide, + QLocaleData::NumberMode mode) + : m_text(text), m_guide(guide), m_mode(mode), + lastMark(mode == QLocaleData::IntegerMode ? '-' : '.') + { + Q_ASSERT(m_guide.isValid(mode)); + } + bool done() const { return !(m_index < m_text.size()); } + qsizetype index() const { return m_index; } + inline int asBmpDigit(char16_t digit) const; + char nextToken(); +}; + +int NumericTokenizer::asBmpDigit(char16_t digit) const +{ + // If digit *is* a digit, result will be in range 0 through 9; otherwise not. + // Must match qlocale_tools.h's unicodeForDigit() + if (m_guide.zeroUcs != u'\u3007' || digit == m_guide.zeroUcs) + return digit - m_guide.zeroUcs; + + // QTBUG-85409: Suzhou's digits aren't contiguous ! + if (digit == u'\u3020') // U+3020 POSTAL MARK FACE is not a digit. + return -1; + // ... but is followed by digits 1 through 9. + return digit - u'\u3020'; +} + +char NumericTokenizer::nextToken() +{ + // As long as caller stops iterating on a zero return, those don't need to + // keep m_index correctly updated. + Q_ASSERT(!done()); + // Mauls non-letters above 'Z' but we don't care: + const auto asciiLower = [](unsigned char c) { return c >= 'A' ? c | 0x20 : c; }; + const QStringView tail = m_text.sliced(m_index); + const QChar ch = tail.front(); + if (ch == u'\u2212') { + // Special case: match the "proper" minus sign, for all locales. + ++m_index; + return '-'; + } + if (m_guide.isC) { + // "Conversion" to C locale is just a filter: + ++m_index; + if (Q_LIKELY(ch.unicode() < 256)) { + unsigned char ascii = asciiLower(ch.toLatin1()); + if (Q_LIKELY(isAsciiDigit(ascii) || ('+' <= ascii && ascii <= lastMark) + // No caller presently (6.5) passes DoubleStandardMode, + // so !IntegerMode implies scientific, for now. + || (m_mode != QLocaleData::IntegerMode + && matchInfNaN.matches(ascii)) + || (m_mode == QLocaleData::DoubleScientificMode + && ascii == 'e'))) { + return ascii; } } + return 0; + } + if (ch.unicode() < 256) { + // Accept the C locale's digits and signs in all locales: + char ascii = asciiLower(ch.toLatin1()); + if (isAsciiDigit(ascii) || ascii == '-' || ascii == '+' + // Also its Inf and NaN letters: + || (m_mode != QLocaleData::IntegerMode && matchInfNaN.matches(ascii))) { + ++m_index; + return ascii; + } } - int zeroPadding = precision - num_str.length() / digitWidth /* + cnt_thousand_sep*/; - while (zeroPadding-- > 0) - num_str.prepend(resultZero); - - if ((flags & ShowBase) - && base == 8 - && (num_str.isEmpty() || num_str.at(0).unicode() != QLatin1Char('0'))) - num_str.prepend(QLatin1Char('0')); - - // LeftAdjusted overrides this flag ZeroPadded. sprintf only padds - // when precision is not specified in the format string - bool zero_padded = flags & ZeroPadded - && !(flags & LeftAdjusted) - && precision_not_specified; - - if (zero_padded) { - int num_pad_chars = width - num_str.length() / digitWidth; - - // leave space for optional '0x' in hex form - if (base == 16 && flags & ShowBase) - num_pad_chars -= 2; - // leave space for optional '0b' in binary form - else if (base == 2 && flags & ShowBase) - num_pad_chars -= 2; - - while (num_pad_chars-- > 0) - num_str.prepend(resultZero); + // Other locales may be trickier: + if (tail.startsWith(m_guide.minus)) { + m_index += m_guide.minus.size(); + return '-'; + } + if (tail.startsWith(m_guide.plus)) { + m_index += m_guide.plus.size(); + return '+'; + } + if (!m_guide.group.isEmpty() && tail.startsWith(m_guide.group)) { + m_index += m_guide.group.size(); + return ','; + } + if (m_mode != QLocaleData::IntegerMode && tail.startsWith(m_guide.decimal)) { + m_index += m_guide.decimal.size(); + return '.'; + } + if (m_mode == QLocaleData::DoubleScientificMode + && tail.startsWith(m_guide.exponent, Qt::CaseInsensitive)) { + m_index += m_guide.exponent.size(); + return 'e'; } - if (flags & CapitalEorX) - num_str = std::move(num_str).toUpper(); + // Must match qlocale_tools.h's unicodeForDigit() + if (m_guide.zeroLen == 1) { + if (!ch.isSurrogate()) { + const uint gap = asBmpDigit(ch.unicode()); + if (gap < 10u) { + ++m_index; + return '0' + gap; + } + } else if (ch.isHighSurrogate() && tail.size() > 1 && tail.at(1).isLowSurrogate()) { + return 0; + } + } else if (ch.isHighSurrogate()) { + // None of the corner cases below matches a surrogate, so (update + // already and) return early if we don't have a digit. + if (tail.size() > 1) { + QChar low = tail.at(1); + if (low.isLowSurrogate()) { + m_index += 2; + const uint gap = QChar::surrogateToUcs4(ch, low) - m_guide.zeroUcs; + return gap < 10u ? '0' + gap : 0; + } + } + return 0; + } - if (base == 16 && flags & ShowBase) - num_str.prepend(QLatin1String(flags & UppercaseBase ? "0X" : "0x")); - else if (base == 2 && flags & ShowBase) - num_str.prepend(QLatin1String(flags & UppercaseBase ? "0B" : "0b")); + // All cases where tail starts with properly-matched surrogate pair + // have been handled by this point. + Q_ASSERT(!(ch.isHighSurrogate() && tail.size() > 1 && tail.at(1).isLowSurrogate())); - // add sign - if (flags & AlwaysShowSign) - num_str.prepend(plus); - else if (flags & BlankBeforePositive) - num_str.prepend(QLatin1Char(' ')); + // Weird corner cases follow (code above assumes these match no surrogates). + + // Some locales use a non-breaking space (U+00A0) or its thin version + // (U+202f) for grouping. These look like spaces, so people (and thus some + // of our tests) use a regular space instead and complain if it doesn't + // work. + // Should this be extended generally to any case where group is a space ? + if ((m_guide.group == u"\u00a0" || m_guide.group == u"\u202f") && tail.startsWith(u' ')) { + ++m_index; + return ','; + } - return num_str; + // Cyrillic has its own E, used by Ukrainian as exponent; but others + // 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", Qt::CaseInsensitive) + || tail.startsWith(u"E", Qt::CaseInsensitive))) { + ++m_index; + return 'e'; + } + + return 0; } +} // namespace with no name /* - Converts a number in locale to its representation in the C locale. - Only has to guarantee that a string that is a correct representation of - a number will be converted. If junk is passed in, junk will be passed - out and the error will be detected during the actual conversion to a - number. We can't detect junk here, since we don't even know the base - of the number. + Converts a number in locale representation to the C locale equivalent. + + Only has to guarantee that a string that is a correct representation of a + number will be converted. Checks signs, separators and digits appear in all + the places they should, and nowhere else. + + Returns true precisely if the number appears to be well-formed, modulo + things a parser for C Locale strings (without digit-grouping separators; + they're stripped) will catch. When it returns true, it records (and + '\0'-terminates) the C locale representation in *result. + + Note: only QString integer-parsing methods have a base parameter (hence need + to cope with letters as possible digits); but these are now all routed via + byteArrayToU?LongLong(), so no longer come via here. The QLocale + number-parsers only work in decimal, so don't have to cope with any digits + other than 0 through 9. */ bool QLocaleData::numberToCLocale(QStringView s, QLocale::NumberOptions number_options, - CharBuff *result) const + NumberMode mode, CharBuff *result) const { s = s.trimmed(); if (s.size() < 1) return false; + NumericTokenizer tokens(s, numericData(mode), mode); + + // Digit-grouping details (all modes): + qsizetype digitsInGroup = 0; + qsizetype last_separator_idx = -1; + qsizetype start_of_digits_idx = -1; + + // Floating-point details (non-integer modes): + qsizetype decpt_idx = -1; + qsizetype exponent_idx = -1; + + char last = '\0'; + while (!tokens.done()) { + qsizetype idx = tokens.index(); // before nextToken() advances + char out = tokens.nextToken(); + if (out == 0) + return false; + Q_ASSERT(tokens.index() > idx); // it always *should* advance (except on zero return) - const QChar *uc = s.data(); - auto length = s.size(); - decltype(length) idx = 0; - - const int leadingGroupWidth = (m_country_id == QLocale::India ? 2 : 3); - int digitsInGroup = 0; - int group_cnt = 0; // counts number of group chars - int decpt_idx = -1; - int last_separator_idx = -1; - int start_of_digits_idx = -1; - int exponent_idx = -1; - - while (idx < length) { - const QStringView in = QStringView(uc + idx, uc[idx].isHighSurrogate() ? 2 : 1); - - char out = numericToCLocale(in); - if (out == 0) { - const QChar simple = in.size() == 1 ? in.front() : QChar::Null; - if (in == listSeparator()) - out = ';'; - else if (in == percentSign()) - out = '%'; - // for handling base-x numbers - else if (simple.toLatin1() >= 'A' && simple.toLatin1() <= 'Z') - out = simple.toLower().toLatin1(); - else if (simple.toLatin1() >= 'a' && simple.toLatin1() <= 'z') - out = simple.toLatin1(); - else - break; - } else if (out == '.') { + if (out == '.') { // Fail if more than one decimal point or point after e if (decpt_idx != -1 || exponent_idx != -1) return false; @@ -3718,309 +4287,246 @@ bool QLocaleData::numberToCLocale(QStringView s, QLocale::NumberOptions number_o exponent_idx = idx; } - if (number_options & QLocale::RejectLeadingZeroInExponent) { - if (exponent_idx != -1 && out == '0' && idx < length - 1) { - // After the exponent there can only be '+', '-' or digits. - // If we find a '0' directly after some non-digit, then that is a leading zero. - if (result->last() < '0' || result->last() > '9') - return false; - } + if (number_options.testFlag(QLocale::RejectLeadingZeroInExponent) + && exponent_idx != -1 && out == '0') { + // After the exponent there can only be '+', '-' or digits. + // If we find a '0' directly after some non-digit, then that is a + // leading zero, acceptable only if it is the whole exponent. + if (!tokens.done() && !isAsciiDigit(last)) + return false; } - if (number_options & QLocale::RejectTrailingZeroesAfterDot) { - // If we've seen a decimal point and the last character after the exponent is 0, then - // that is a trailing zero. - if (decpt_idx >= 0 && idx == exponent_idx && result->last() == '0') - return false; + if (number_options.testFlag(QLocale::RejectTrailingZeroesAfterDot) && decpt_idx >= 0) { + // In a fractional part, a 0 just before the exponent is trailing: + if (idx == exponent_idx && last == '0') + return false; } - if (!(number_options & QLocale::RejectGroupSeparator)) { - if (start_of_digits_idx == -1 && out >= '0' && out <= '9') { - start_of_digits_idx = idx; - digitsInGroup++; + if (!number_options.testFlag(QLocale::RejectGroupSeparator)) { + if (isAsciiDigit(out)) { + if (start_of_digits_idx == -1) + start_of_digits_idx = idx; + ++digitsInGroup; } else if (out == ',') { // Don't allow group chars after the decimal point or exponent if (decpt_idx != -1 || exponent_idx != -1) return false; if (last_separator_idx == -1) { - if (start_of_digits_idx == -1 || digitsInGroup > leadingGroupWidth) + // Check distance from the beginning of the digits: + if (start_of_digits_idx == -1 || m_grouping_top > digitsInGroup + || digitsInGroup >= m_grouping_least + m_grouping_top) { return false; + } } else { - // check distance from the last separator or from the beginning of the digits - // ### FIXME: Some locales allow other groupings! - // See https://en.wikipedia.org/wiki/Thousands_separator - if (digitsInGroup != leadingGroupWidth) + // Check distance from the last separator: + if (digitsInGroup != m_grouping_higher) return false; } last_separator_idx = idx; - ++group_cnt; digitsInGroup = 0; - - // don't add the group separator - idx += in.size(); - continue; - } else if (out == '.' || idx == exponent_idx) { - // check distance from the last separator - // ### FIXME: Some locales allow other groupings! - // See https://en.wikipedia.org/wiki/Thousands_separator - if (last_separator_idx != -1 && digitsInGroup != 3) + } else if (mode != IntegerMode && (out == '.' || idx == exponent_idx) + && last_separator_idx != -1) { + // Were there enough digits since the last group separator? + if (digitsInGroup != m_grouping_least) return false; // stop processing separators last_separator_idx = -1; - } else if (out >= '0' && out <= '9') { - digitsInGroup++; } + } else if (out == ',') { + return false; } - result->append(out); - idx += in.size(); + last = out; + if (out != ',') // Leave group separators out of the result. + result->append(out); } - if (!(number_options & QLocale::RejectGroupSeparator)) { - // group separator post-processing - // did we end in a separator? - if (last_separator_idx + 1 == idx) - return false; - // were there enough digits since the last separator? - if (last_separator_idx != -1 && digitsInGroup != 3) + if (!number_options.testFlag(QLocale::RejectGroupSeparator) && last_separator_idx != -1) { + // Were there enough digits since the last group separator? + if (digitsInGroup != m_grouping_least) return false; } - if (number_options & QLocale::RejectTrailingZeroesAfterDot) { - // In decimal form, the last character can be a trailing zero if we've seen a decpt. - if (decpt_idx != -1 && exponent_idx == -1 && result->last() == '0') + if (number_options.testFlag(QLocale::RejectTrailingZeroesAfterDot) + && decpt_idx != -1 && exponent_idx == -1) { + // In the fractional part, a final zero is trailing: + if (last == '0') return false; } result->append('\0'); - return idx == length; + 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.length()); + ParsingResult result; + result.buff.reserve(str.size()); + enum { Whole, Fractional, Exponent } state = Whole; const bool scientific = numMode == DoubleScientificMode; - bool lastWasE = false; - bool lastWasDigit = false; - int eCnt = 0; - int decPointCnt = 0; - bool dec = false; - int decDigitCnt = 0; - - for (qsizetype i = 0; i < str.size();) { - const QStringView in = str.mid(i, str.at(i).isHighSurrogate() ? 2 : 1); - char c = numericToCLocale(in); - - if (c >= '0' && c <= '9') { - if (numMode != IntegerMode) { - // If a double has too many digits after decpt, it shall be Invalid. - if (dec && decDigits != -1 && decDigits < ++decDigitCnt) - return false; - } + NumericTokenizer tokens(str, numericData(numMode), numMode); + char last = '\0'; - // The only non-digit character after the 'e' can be '+' or '-'. - // If a zero is directly after that, then the exponent is zero-padded. - if ((number_options & QLocale::RejectLeadingZeroInExponent) - && c == '0' && eCnt > 0 && !lastWasDigit) { - return false; + while (!tokens.done()) { + char c = tokens.nextToken(); + + if (isAsciiDigit(c)) { + switch (state) { + case Whole: + // Nothing special to do (unless we want to check grouping sizes). + break; + case Fractional: + // If a double has too many digits in its fractional part it is Invalid. + if (decDigits-- == 0) + 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 {}; + } + break; } - lastWasDigit = true; } else { switch (c) { - case '.': - if (numMode == IntegerMode) { - // If an integer has a decimal point, it shall be Invalid. - return false; - } else { - // If a double has more than one decimal point, it shall be Invalid. - if (++decPointCnt > 1) - return false; -#if 0 - // If a double with no decimal digits has a decimal point, it shall be - // Invalid. - if (decDigits == 0) - return false; -#endif // On second thoughts, it shall be Valid. - - dec = true; - } - break; + case '.': + // 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 {}; + // Even when decDigits is 0, we do allow the decimal point to be + // present - just as long as no digits follow it. + + state = Fractional; + break; - case '+': - case '-': - if (scientific) { - // If a scientific has a sign that's not at the beginning or after - // an 'e', it shall be Invalid. - if (i != 0 && !lastWasE) - return false; - } else { - // If a non-scientific has a sign that's not at the beginning, - // it shall be Invalid. - if (i != 0) - return false; - } - break; + case '+': + case '-': + // A sign can only appear at the start or after the e of scientific: + if (last != '\0' && !(scientific && last == 'e')) + return {}; + break; - case ',': - //it can only be placed after a digit which is before the decimal point - if ((number_options & QLocale::RejectGroupSeparator) || !lastWasDigit || - decPointCnt > 0) - return false; - break; + case ',': + // Grouping is only allowed after a digit in the whole-number portion: + if ((number_options & QLocale::RejectGroupSeparator) || state != Whole + || !isAsciiDigit(last)) { + return {}; + } + // We could check grouping sizes are correct, but fixup()s are + // probably better off correcting any misplacement instead. + break; - case 'e': - if (scientific) { - // If a scientific has more than one 'e', it shall be Invalid. - if (++eCnt > 1) - return false; - dec = false; - } else { - // If a non-scientific has an 'e', it shall be Invalid. - return false; - } - break; + case 'e': + // Only one e is allowed and only in scientific: + if (!scientific || state == Exponent) + return {}; + state = Exponent; + break; - default: - // If it's not a valid digit, it shall be Invalid. - return false; + default: + // Nothing else can validly appear in a number. + // NumericTokenizer allows letters of "inf" and "nan", but + // 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 {}; } - lastWasDigit = false; } - lastWasE = c == 'e'; - if (c != ',') - buff->append(c); - - i += in.size(); + last = c; + if (c != ',') // Skip grouping + 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, QLocale::NumberOptions number_options) const { CharBuff buff; - if (!numberToCLocale(str, number_options, &buff)) { + if (!numberToCLocale(str, number_options, DoubleScientificMode, &buff)) { if (ok != nullptr) *ok = false; return 0.0; } - int processed = 0; - bool nonNullOk = false; - double d = qt_asciiToDouble(buff.constData(), buff.length() - 1, nonNullOk, processed); + auto r = qt_asciiToDouble(buff.constData(), buff.size() - 1); if (ok != nullptr) - *ok = nonNullOk; - return d; + *ok = r.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, &buff)) { - if (ok != nullptr) - *ok = false; - return 0; - } + if (!numberToCLocale(str, number_options, IntegerMode, &buff)) + return {}; - return bytearrayToLongLong(buff.constData(), 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, &buff)) { - if (ok != nullptr) - *ok = false; - return 0; - } + if (!numberToCLocale(str, number_options, IntegerMode, &buff)) + return {}; - return bytearrayToUnsLongLong(buff.constData(), base, ok); + return bytearrayToUnsLongLong(QByteArrayView(buff), base); } -double QLocaleData::bytearrayToDouble(const char *num, bool *ok) +static bool checkParsed(QByteArrayView num, qsizetype used) { - bool nonNullOk = false; - int len = static_cast<int>(strlen(num)); - Q_ASSERT(len >= 0); - int processed = 0; - double d = qt_asciiToDouble(num, len, nonNullOk, processed); - if (ok != nullptr) - *ok = nonNullOk; - return d; -} - -qlonglong QLocaleData::bytearrayToLongLong(const char *num, int base, bool *ok) -{ - bool _ok; - const char *endptr; - - if (*num == '\0') { - if (ok != nullptr) - *ok = false; - return 0; - } - - qlonglong l = qstrtoll(num, &endptr, base, &_ok); - - if (!_ok) { - if (ok != nullptr) - *ok = false; - return 0; - } + if (used <= 0) + return false; - if (*endptr != '\0') { - while (ascii_isspace(*endptr)) - ++endptr; + const qsizetype len = num.size(); + if (used < len && num[used] != '\0') { + while (used < len && ascii_isspace(num[used])) + ++used; } - if (*endptr != '\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(const char *num, int base, bool *ok) +QSimpleParsedNumber<qint64> QLocaleData::bytearrayToLongLong(QByteArrayView num, int base) { - bool _ok; - const char *endptr; - qulonglong l = qstrtoull(num, &endptr, base, &_ok); - - if (!_ok) { - if (ok != nullptr) - *ok = false; - return 0; - } - - if (*endptr != '\0') { - while (ascii_isspace(*endptr)) - ++endptr; - } - - if (*endptr != '\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; } /*! @@ -4039,24 +4545,24 @@ qulonglong QLocaleData::bytearrayToUnsLongLong(const char *num, int base, bool * \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 == systemData()) { - QVariant res = systemLocale()->query(QSystemLocale::CurrencySymbol, format); - if (!res.isNull()) - return res.toString(); + if (d->m_data == &systemLocaleData) { + auto res = systemLocale()->query(QSystemLocale::CurrencySymbol, format).toString(); + if (!res.isEmpty()) + return res; } #endif switch (format) { case CurrencySymbol: return d->m_data->currencySymbol().getData(currency_symbol_data); case CurrencyDisplayName: - return d->m_data->currencyDisplayName().getListEntry(currency_display_name_data, 0); + return d->m_data->currencyDisplayName().getData(currency_display_name_data); case CurrencyIsoCode: { const char *code = d->m_data->m_currency_iso_code; - if (int len = qstrnlen(code, 3)) - return QString::fromLatin1(code, len); + if (auto len = qstrnlen(code, 3)) + return QString::fromLatin1(code, qsizetype(len)); break; } } @@ -4074,12 +4580,12 @@ QString QLocale::currencySymbol(QLocale::CurrencySymbolFormat format) const QString QLocale::toCurrencyString(qlonglong value, const QString &symbol) const { #ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { + if (d->m_data == &systemLocaleData) { QSystemLocale::CurrencyToStringArgument arg(value, symbol); - QVariant res = - systemLocale()->query(QSystemLocale::CurrencyToString, QVariant::fromValue(arg)); - if (!res.isNull()) - return res.toString(); + auto res = systemLocale()->query(QSystemLocale::CurrencyToString, + QVariant::fromValue(arg)).toString(); + if (!res.isEmpty()) + return res; } #endif QLocaleData::DataRange range = d->m_data->currencyFormatNegative(); @@ -4090,8 +4596,8 @@ 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); - return range.getData(currency_format_data).arg(str, sym); + sym = currencySymbol(CurrencyIsoCode); + return range.viewData(currency_format_data).arg(str, sym); } /*! @@ -4101,32 +4607,21 @@ QString QLocale::toCurrencyString(qlonglong value, const QString &symbol) const QString QLocale::toCurrencyString(qulonglong value, const QString &symbol) const { #ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { + if (d->m_data == &systemLocaleData) { QSystemLocale::CurrencyToStringArgument arg(value, symbol); - QVariant res = - systemLocale()->query(QSystemLocale::CurrencyToString, QVariant::fromValue(arg)); - if (!res.isNull()) - return res.toString(); + auto res = systemLocale()->query(QSystemLocale::CurrencyToString, + QVariant::fromValue(arg)).toString(); + if (!res.isEmpty()) + return res; } #endif 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); } -#if QT_VERSION < QT_VERSION_CHECK(6,0,0) -/*! - \since 4.8 - \overload -*/ -QString QLocale::toCurrencyString(double value, const QString &symbol) const -{ - return toCurrencyString(value, symbol, d->m_data->m_currency_digits); -} -#endif - /*! \since 5.7 \overload toCurrencyString() @@ -4140,12 +4635,12 @@ QString QLocale::toCurrencyString(double value, const QString &symbol) const QString QLocale::toCurrencyString(double value, const QString &symbol, int precision) const { #ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { + if (d->m_data == &systemLocaleData) { QSystemLocale::CurrencyToStringArgument arg(value, symbol); - QVariant res = - systemLocale()->query(QSystemLocale::CurrencyToString, QVariant::fromValue(arg)); - if (!res.isNull()) - return res.toString(); + auto res = systemLocale()->query(QSystemLocale::CurrencyToString, + QVariant::fromValue(arg)).toString(); + if (!res.isEmpty()) + return res; } #endif QLocaleData::DataRange range = d->m_data->currencyFormatNegative(); @@ -4156,12 +4651,11 @@ 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); - return range.getData(currency_format_data).arg(str, sym); + sym = currencySymbol(CurrencyIsoCode); + return range.viewData(currency_format_data).arg(str, sym); } /*! - \fn QString QLocale::toCurrencyString(float i, const QString &symbol) const \fn QString QLocale::toCurrencyString(float i, const QString &symbol, int precision) const \overload toCurrencyString() */ @@ -4182,19 +4676,6 @@ QString QLocale::toCurrencyString(double value, const QString &symbol, int preci \sa formattedDataSize() */ -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) -/*! - \obsolete - - Use the const version instead. -*/ -QString QLocale::formattedDataSize(qint64 bytes, int precision, DataSizeFormats format) -{ - const auto *that = this; - return that->formattedDataSize(bytes, precision, format); -} -#endif - /*! \since 5.10 @@ -4230,94 +4711,139 @@ QString QLocale::formattedDataSize(qint64 bytes, int precision, DataSizeFormats // We don't support sizes in units larger than exbibytes because // the number of bytes would not fit into qint64. Q_ASSERT(power <= 6 && power >= 0); - QString unit; + QStringView unit; if (power > 0) { QLocaleData::DataRange range = (format & DataSizeSIQuantifiers) ? d->m_data->byteAmountSI() : d->m_data->byteAmountIEC(); - unit = range.getListEntry(byte_unit_data, power - 1); + unit = range.viewListEntry(byte_unit_data, power - 1); } else { - unit = d->m_data->byteCount().getData(byte_unit_data); + unit = d->m_data->byteCount().viewData(byte_unit_data); } - return number + QLatin1Char(' ') + unit; + return number + u' ' + unit; } /*! \since 4.8 - - Returns an ordered list of locale names for translation purposes in - preference order (like "en-Latn-US", "en-US", "en"). - - The return value represents locale names that the user expects to see the - UI translation in. - - Most like you do not need to use this function directly, but just pass the + \brief List of locale names for use in selecting translations + + 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. - The first item in the list is the most preferred one. - \sa QTranslator, bcp47Name() */ -QStringList QLocale::uiLanguages() const +QStringList QLocale::uiLanguages(TagSeparator separator) const { + const char sep = char(separator); QStringList uiLanguages; - QList<QLocale> locales; -#ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { - QVariant res = systemLocale()->query(QSystemLocale::UILanguages, QVariant()); - if (!res.isNull()) { - uiLanguages = res.toStringList(); - // ... but we need to include likely-adjusted forms of each of those, too: - for (const auto &entry : qAsConst(uiLanguages)) - locales.append(QLocale(entry)); + if (uchar(sep) > 0x7f) { + badSeparatorWarning("uiLanguages", sep); + return uiLanguages; + } + QList<QLocaleId> localeIds; +#ifdef QT_NO_SYSTEMLOCALE + constexpr bool isSystem = false; +#else + const bool isSystem = d->m_data == &systemLocaleData; + if (isSystem) { + uiLanguages = systemLocale()->query(QSystemLocale::UILanguages).toStringList(); + // ... 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)) + localeIds.append(QLocaleId::fromName(entry)); + if (localeIds.isEmpty()) + localeIds.append(systemLocale()->fallbackLocale().d->m_data->id()); + // If the system locale (isn't C and) didn't include itself in the list, + // or as fallback, presume to know better than it and put its name + // 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(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. + const QLocaleId mine = d->m_data->id().withLikelySubtagsRemoved(); + const auto isMine = [mine](const QString &entry) { + return QLocaleId::fromName(entry).withLikelySubtagsRemoved() == mine; + }; + if (std::none_of(uiLanguages.constBegin(), uiLanguages.constEnd(), isMine)) { + localeIds.prepend(d->m_data->id()); + uiLanguages.prepend(name); + } } - if (locales.isEmpty()) - locales.append(systemLocale()->fallbackUiLocale()); } else #endif { - locales.append(*this); + localeIds.append(d->m_data->id()); } - for (int i = locales.size(); i-- > 0; ) { - const QLocale &locale = locales.at(i); - int j; + for (qsizetype i = localeIds.size(); i-- > 0; ) { + QLocaleId id = localeIds.at(i); + qsizetype j; QByteArray prior; - if (i < uiLanguages.size()) { + if (isSystem && i < uiLanguages.size()) { // Adding likely-adjusted forms to system locale's list. // Name the locale is derived from: - const QString &name = uiLanguages.at(i); - prior = name.toLatin1(); - // Don't try to likely-adjust if construction's likely-adjustments - // were so drastic the result doesn't match the prior name: - if (locale.name() != name && locale.d->rawName() != prior) - continue; - // Insert just after prior: + prior = uiLanguages.at(i).toLatin1(); + // Insert just after the entry we're supplementing: j = i + 1; + } else if (id.language_id == C) { + // Attempt no likely sub-tag amendments to C: + uiLanguages.append(QString::fromLatin1(id.name(sep))); + continue; } else { - // Plain locale, not system locale; just append. + // Plain locale or empty system uiLanguages; just append. + prior = id.name(sep); + uiLanguages.append(QString::fromLatin1(prior)); j = uiLanguages.size(); } - const auto data = locale.d->m_data; - QLocaleId id - = QLocaleId::fromIds(data->m_language_id, data->m_script_id, data->m_country_id); const QLocaleId max = id.withLikelySubtagsAdded(); const QLocaleId min = max.withLikelySubtagsRemoved(); - id.script_id = 0; // For re-use as script-less variant. - // Include version with all likely sub-tags (last) if distinct from the rest: - if (max != min && max != id && max.name() != prior) - uiLanguages.insert(j, QString::fromLatin1(max.name())); + // Include minimal version (last) unless it's what our locale is derived from: + 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. + + if (id.script_id) { + // Include scriptless version if likely-equivalent and distinct: + id.script_id = 0; + if (id != min && id.withLikelySubtagsAdded() == max) { + if (auto name = id.name(sep); name != prior) + uiLanguages.insert(j, QString::fromLatin1(name)); + } + } - // Include scriptless version if likely-equivalent and distinct: - if (data->m_script_id && id != min && id.name() != prior - && id.withLikelySubtagsAdded() == max) { - uiLanguages.insert(j, QString::fromLatin1(id.name())); + if (!id.territory_id) { + Q_ASSERT(!min.territory_id); + Q_ASSERT(!id.script_id); // because we just cleared it. + // 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(sep); name != prior) + uiLanguages.insert(j, QString::fromLatin1(name)); + } } - // Include minimal version (first) unless it's what our locale is derived from: - if (min.name() != prior) - uiLanguages.insert(j, QString::fromLatin1(min.name())); + // Include version with all likely sub-tags (first) if distinct from the rest: + if (max != min && max != id) { + if (auto name = max.name(sep); name != prior) + uiLanguages.insert(j, QString::fromLatin1(name)); + } } return uiLanguages; } @@ -4336,8 +4862,8 @@ QStringList QLocale::uiLanguages() const QLocale QLocale::collation() const { #ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { - QString res = systemLocale()->query(QSystemLocale::Collation, QVariant()).toString(); + if (d->m_data == &systemLocaleData) { + const auto res = systemLocale()->query(QSystemLocale::Collation).toString(); if (!res.isEmpty()) return QLocale(res); } @@ -4349,41 +4875,57 @@ QLocale QLocale::collation() const \since 4.8 Returns a native name of the language for the locale. For example - "Schwiizertüütsch" for Swiss-German locale. + "Schweizer Hochdeutsch" for the Swiss-German locale. - \sa nativeCountryName(), languageToString() + \sa nativeTerritoryName(), languageToString() */ QString QLocale::nativeLanguageName() const { #ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { - QVariant res = systemLocale()->query(QSystemLocale::NativeLanguageName, QVariant()); - if (!res.isNull()) - return res.toString(); + if (d->m_data == &systemLocaleData) { + auto res = systemLocale()->query(QSystemLocale::NativeLanguageName).toString(); + if (!res.isEmpty()) + return res; } #endif return d->m_data->endonymLanguage().getData(endonyms_data); } /*! - \since 4.8 + \since 6.2 - Returns a native name of the country for the locale. For example + Returns a native name of the territory for the locale. For example "España" for Spanish/Spain locale. - \sa nativeLanguageName(), countryToString() + \sa nativeLanguageName(), territoryToString() */ -QString QLocale::nativeCountryName() const +QString QLocale::nativeTerritoryName() const { #ifndef QT_NO_SYSTEMLOCALE - if (d->m_data == systemData()) { - QVariant res = systemLocale()->query(QSystemLocale::NativeCountryName, QVariant()); - if (!res.isNull()) - return res.toString(); + if (d->m_data == &systemLocaleData) { + auto res = systemLocale()->query(QSystemLocale::NativeTerritoryName).toString(); + if (!res.isEmpty()) + return res; } #endif - return d->m_data->endonymCountry().getData(endonyms_data); + return d->m_data->endonymTerritory().getData(endonyms_data); +} + +#if QT_DEPRECATED_SINCE(6, 6) +/*! + \deprecated [6.6] Use \l nativeTerritoryName() instead. + \since 4.8 + + Returns a native name of the territory for the locale. For example + "España" for Spanish/Spain locale. + + \sa nativeLanguageName(), territoryToString() +*/ +QString QLocale::nativeCountryName() const +{ + return nativeTerritoryName(); } +#endif #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug dbg, const QLocale &l) @@ -4392,7 +4934,7 @@ QDebug operator<<(QDebug dbg, const QLocale &l) dbg.nospace().noquote() << "QLocale(" << QLocale::languageToString(l.language()) << ", " << QLocale::scriptToString(l.script()) - << ", " << QLocale::countryToString(l.country()) << ')'; + << ", " << QLocale::territoryToString(l.territory()) << ')'; return dbg; } #endif |