summaryrefslogtreecommitdiffstats
path: root/src/corelib/text/qlocale_win.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/text/qlocale_win.cpp')
-rw-r--r--src/corelib/text/qlocale_win.cpp486
1 files changed, 308 insertions, 178 deletions
diff --git a/src/corelib/text/qlocale_win.cpp b/src/corelib/text/qlocale_win.cpp
index 0c83dcffb9..e238b67d03 100644
--- a/src/corelib/text/qlocale_win.cpp
+++ b/src/corelib/text/qlocale_win.cpp
@@ -1,42 +1,6 @@
-/****************************************************************************
-**
-** Copyright (C) 2020 The Qt Company Ltd.
-** Copyright (C) 2016 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) 2021 The Qt Company Ltd.
+// Copyright (C) 2016 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qlocale_p.h"
#include "qlocale_tools_p.h"
@@ -46,16 +10,62 @@
#include "qdatetime.h"
#include "qdebug.h"
-#ifdef Q_OS_WIN
-# include <qt_windows.h>
-# include <time.h>
-#endif
+#include "QtCore/private/qgregoriancalendar_p.h" // for yearSharingWeekDays()
+
+#include <q20algorithm.h>
+
+// TODO QTBUG-121193: port away from the use of LCID to always use names.
+#include <qt_windows.h>
+#include <time.h>
+
+#if QT_CONFIG(cpp_winrt)
+# include <QtCore/private/qt_winrtbase_p.h>
+
+# include <winrt/Windows.Foundation.h>
+# include <winrt/Windows.Foundation.Collections.h>
+# include <winrt/Windows.System.UserProfile.h>
+#endif // QT_CONFIG(cpp_winrt)
QT_BEGIN_NAMESPACE
+using namespace Qt::StringLiterals;
+
+// Shared interpretation of %LANG%
+static auto scanLangEnv()
+{
+ struct R
+ {
+ QByteArray name; // empty means unknown; lookup from id may work
+ LCID id = 0; // 0 means unknown; lookup from name may work
+ } result;
+ const QByteArray lang = qgetenv("LANG");
+ if (lang.size() && (lang == "C" || qt_splitLocaleName(QString::fromLocal8Bit(lang)))) {
+ // See if we have a Windows locale code instead of a locale name:
+ const auto [id, used] = qstrntoll(lang.data(), lang.size(), 0);
+ if (used > 0 && id && INT_MIN <= id && id <= INT_MAX)
+ return R {QByteArray(), static_cast<LCID>(id)};
+ return R {lang, 0};
+ }
+ return R{};
+}
+
+static auto getDefaultWinId()
+{
+ const auto [name, id] = scanLangEnv();
+ if (id)
+ return id;
+
+ if (!name.isEmpty()) {
+ LCID id = LocaleNameToLCID(static_cast<LPCWSTR>(
+ QString::fromUtf8(name).toStdWString().data()), 0);
+ if (id)
+ return id;
+ }
+
+ return GetUserDefaultLCID();
+}
+
static QByteArray getWinLocaleName(LCID id = LOCALE_USER_DEFAULT);
-static QString winIso639LangName(LCID id = LOCALE_USER_DEFAULT);
-static QString winIso3116CtryName(LCID id = LOCALE_USER_DEFAULT);
#ifndef QT_NO_SYSTEMLOCALE
@@ -81,6 +91,17 @@ static QString winIso3116CtryName(LCID id = LOCALE_USER_DEFAULT);
# define LOCALE_SSHORTTIME 0x00000079
#endif
+namespace {
+template <typename T>
+static QVariant nullIfEmpty(T &&value)
+{
+ // For use where we should fall back to CLDR if we got an empty value.
+ if (value.isEmpty())
+ return {};
+ return std::move(value);
+}
+}
+
struct QSystemLocalePrivate
{
QSystemLocalePrivate();
@@ -94,6 +115,7 @@ struct QSystemLocalePrivate
QVariant timeFormat(QLocale::FormatType);
QVariant dateTimeFormat(QLocale::FormatType);
QVariant dayName(int, QLocale::FormatType);
+ QVariant standaloneMonthName(int, QLocale::FormatType);
QVariant monthName(int, QLocale::FormatType);
QVariant toString(QDate, QLocale::FormatType);
QVariant toString(QTime, QLocale::FormatType);
@@ -121,7 +143,7 @@ private:
// cached values:
LCID lcid;
- SubstitutionType substitutionType;
+ SubstitutionType substitutionType = SUnknown;
QString zero; // cached value for zeroDigit()
int getLocaleInfo(LCTYPE type, LPWSTR data, int size);
@@ -134,6 +156,8 @@ private:
SubstitutionType substitution();
QString substituteDigits(QString &&string);
+ QString correctDigits(QString &&string);
+ QString yearFix(int year, int fakeYear, QString &&formatted);
static QString winToQtFormat(QStringView sys_fmt);
@@ -141,9 +165,8 @@ private:
Q_GLOBAL_STATIC(QSystemLocalePrivate, systemLocalePrivate)
QSystemLocalePrivate::QSystemLocalePrivate()
- : substitutionType(SUnknown)
+ : lcid(getDefaultWinId())
{
- lcid = GetUserDefaultLCID();
}
inline int QSystemLocalePrivate::getCurrencyFormat(DWORD flags, LPCWSTR value, const CURRENCYFMTW *format, LPWSTR data, int size)
@@ -195,10 +218,11 @@ QVariant QSystemLocalePrivate::getLocaleInfo(LCTYPE type)
int QSystemLocalePrivate::getLocaleInfo_int(LCTYPE type)
{
- const QString str = getLocaleInfo(type).toString();
- bool ok = false;
- const int v = str.toInt(&ok);
- return ok ? v : 0;
+ DWORD value;
+ int r = GetLocaleInfo(lcid, type | LOCALE_RETURN_NUMBER,
+ reinterpret_cast<wchar_t *>(&value),
+ sizeof(value) / sizeof(wchar_t));
+ return r == sizeof(value) / sizeof(wchar_t) ? value : 0;
}
QSystemLocalePrivate::SubstitutionType QSystemLocalePrivate::substitution()
@@ -206,25 +230,25 @@ QSystemLocalePrivate::SubstitutionType QSystemLocalePrivate::substitution()
if (substitutionType == SUnknown) {
wchar_t buf[8];
if (!getLocaleInfo(LOCALE_IDIGITSUBSTITUTION, buf, 8)) {
- substitutionType = QSystemLocalePrivate::SNever;
+ substitutionType = SNever;
return substitutionType;
}
if (buf[0] == '1')
- substitutionType = QSystemLocalePrivate::SNever;
+ substitutionType = SNever;
else if (buf[0] == '0')
- substitutionType = QSystemLocalePrivate::SContext;
+ substitutionType = SContext;
else if (buf[0] == '2')
- substitutionType = QSystemLocalePrivate::SAlways;
+ substitutionType = SAlways;
else {
wchar_t digits[11]; // See zeroDigit() for why 11.
if (!getLocaleInfo(LOCALE_SNATIVEDIGITS, digits, 11)) {
- substitutionType = QSystemLocalePrivate::SNever;
+ substitutionType = SNever;
return substitutionType;
}
if (buf[0] == digits[0] + 2)
- substitutionType = QSystemLocalePrivate::SAlways;
+ substitutionType = SAlways;
else
- substitutionType = QSystemLocalePrivate::SNever;
+ substitutionType = SNever;
}
}
return substitutionType;
@@ -240,7 +264,7 @@ QString QSystemLocalePrivate::substituteDigits(QString &&string)
break;
Q_ASSERT(z > '9');
ushort *const qch = reinterpret_cast<ushort *>(string.data());
- for (int i = 0, stop = string.size(); i < stop; ++i) {
+ for (qsizetype i = 0, stop = string.size(); i < stop; ++i) {
ushort &ch = qch[i];
if (ch >= '0' && ch <= '9')
ch = unicodeForDigit(ch - '0', z);
@@ -265,6 +289,11 @@ QString QSystemLocalePrivate::substituteDigits(QString &&string)
return std::move(string);
}
+QString QSystemLocalePrivate::correctDigits(QString &&string)
+{
+ return substitution() == SAlways ? substituteDigits(std::move(string)) : std::move(string);
+}
+
QVariant QSystemLocalePrivate::zeroDigit()
{
if (zero.isEmpty()) {
@@ -282,36 +311,36 @@ QVariant QSystemLocalePrivate::zeroDigit()
zero = QString::fromWCharArray(digits, 1);
}
}
- return zero;
+ return nullIfEmpty(zero); // Do not std::move().
}
QVariant QSystemLocalePrivate::decimalPoint()
{
- return getLocaleInfo(LOCALE_SDECIMAL);
+ return nullIfEmpty(getLocaleInfo(LOCALE_SDECIMAL).toString());
}
QVariant QSystemLocalePrivate::groupSeparator()
{
- return getLocaleInfo(LOCALE_STHOUSAND);
+ return getLocaleInfo(LOCALE_STHOUSAND); // Empty means don't group digits.
}
QVariant QSystemLocalePrivate::negativeSign()
{
- return getLocaleInfo(LOCALE_SNEGATIVESIGN);
+ return nullIfEmpty(getLocaleInfo(LOCALE_SNEGATIVESIGN).toString());
}
QVariant QSystemLocalePrivate::positiveSign()
{
- return getLocaleInfo(LOCALE_SPOSITIVESIGN);
+ return nullIfEmpty(getLocaleInfo(LOCALE_SPOSITIVESIGN).toString());
}
QVariant QSystemLocalePrivate::dateFormat(QLocale::FormatType type)
{
switch (type) {
case QLocale::ShortFormat:
- return winToQtFormat(getLocaleInfo(LOCALE_SSHORTDATE).toString());
+ return nullIfEmpty(winToQtFormat(getLocaleInfo(LOCALE_SSHORTDATE).toString()));
case QLocale::LongFormat:
- return winToQtFormat(getLocaleInfo(LOCALE_SLONGDATE).toString());
+ return nullIfEmpty(winToQtFormat(getLocaleInfo(LOCALE_SLONGDATE).toString()));
case QLocale::NarrowFormat:
break;
}
@@ -322,89 +351,168 @@ QVariant QSystemLocalePrivate::timeFormat(QLocale::FormatType type)
{
switch (type) {
case QLocale::ShortFormat:
- return winToQtFormat(getLocaleInfo(LOCALE_SSHORTTIME).toString());
+ return nullIfEmpty(winToQtFormat(getLocaleInfo(LOCALE_SSHORTTIME).toString()));
case QLocale::LongFormat:
- return winToQtFormat(getLocaleInfo(LOCALE_STIMEFORMAT).toString());
+ return nullIfEmpty(winToQtFormat(getLocaleInfo(LOCALE_STIMEFORMAT).toString()));
case QLocale::NarrowFormat:
break;
}
- return QVariant();
+ return {};
}
QVariant QSystemLocalePrivate::dateTimeFormat(QLocale::FormatType type)
{
- return QString(dateFormat(type).toString() + QLatin1Char(' ') + timeFormat(type).toString());
+ QVariant d = dateFormat(type), t = timeFormat(type);
+ if (d.typeId() == QMetaType::QString && t.typeId() == QMetaType::QString)
+ return QString(d.toString() + u' ' + t.toString());
+ return {};
}
QVariant QSystemLocalePrivate::dayName(int day, QLocale::FormatType type)
{
if (day < 1 || day > 7)
- return QString();
+ return {};
- static const LCTYPE short_day_map[]
+ static constexpr LCTYPE short_day_map[]
= { LOCALE_SABBREVDAYNAME1, LOCALE_SABBREVDAYNAME2,
LOCALE_SABBREVDAYNAME3, LOCALE_SABBREVDAYNAME4, LOCALE_SABBREVDAYNAME5,
LOCALE_SABBREVDAYNAME6, LOCALE_SABBREVDAYNAME7 };
- static const LCTYPE long_day_map[]
+ static constexpr LCTYPE long_day_map[]
= { LOCALE_SDAYNAME1, LOCALE_SDAYNAME2,
LOCALE_SDAYNAME3, LOCALE_SDAYNAME4, LOCALE_SDAYNAME5,
LOCALE_SDAYNAME6, LOCALE_SDAYNAME7 };
- static const LCTYPE narrow_day_map[]
+ static constexpr LCTYPE narrow_day_map[]
= { LOCALE_SSHORTESTDAYNAME1, LOCALE_SSHORTESTDAYNAME2,
LOCALE_SSHORTESTDAYNAME3, LOCALE_SSHORTESTDAYNAME4,
LOCALE_SSHORTESTDAYNAME5, LOCALE_SSHORTESTDAYNAME6,
LOCALE_SSHORTESTDAYNAME7 };
- day -= 1;
-
- if (type == QLocale::LongFormat)
- return getLocaleInfo(long_day_map[day]);
- if (type == QLocale::NarrowFormat)
- return getLocaleInfo(narrow_day_map[day]);
- return getLocaleInfo(short_day_map[day]);
+ return nullIfEmpty(getLocaleInfo(
+ (type == QLocale::LongFormat ? long_day_map
+ : type == QLocale::NarrowFormat ? narrow_day_map
+ : short_day_map)[day - 1]).toString());
}
-QVariant QSystemLocalePrivate::monthName(int month, QLocale::FormatType type)
+QVariant QSystemLocalePrivate::standaloneMonthName(int month, QLocale::FormatType type)
{
- static const LCTYPE short_month_map[]
+ static constexpr LCTYPE short_month_map[]
= { LOCALE_SABBREVMONTHNAME1, LOCALE_SABBREVMONTHNAME2, LOCALE_SABBREVMONTHNAME3,
LOCALE_SABBREVMONTHNAME4, LOCALE_SABBREVMONTHNAME5, LOCALE_SABBREVMONTHNAME6,
LOCALE_SABBREVMONTHNAME7, LOCALE_SABBREVMONTHNAME8, LOCALE_SABBREVMONTHNAME9,
LOCALE_SABBREVMONTHNAME10, LOCALE_SABBREVMONTHNAME11, LOCALE_SABBREVMONTHNAME12 };
- static const LCTYPE long_month_map[]
+ static constexpr LCTYPE long_month_map[]
= { LOCALE_SMONTHNAME1, LOCALE_SMONTHNAME2, LOCALE_SMONTHNAME3,
LOCALE_SMONTHNAME4, LOCALE_SMONTHNAME5, LOCALE_SMONTHNAME6,
LOCALE_SMONTHNAME7, LOCALE_SMONTHNAME8, LOCALE_SMONTHNAME9,
LOCALE_SMONTHNAME10, LOCALE_SMONTHNAME11, LOCALE_SMONTHNAME12 };
- month -= 1;
- if (month < 0 || month > 11)
- return QString();
+ if (month < 1 || month > 12)
+ return {};
+
+ // Month is Jan = 1, ... Dec = 12; adjust by 1 to match array indexing from 0:
+ return nullIfEmpty(getLocaleInfo(
+ (type == QLocale::LongFormat ? long_month_map : short_month_map)[month - 1]).toString());
+}
+
+QVariant QSystemLocalePrivate::monthName(int month, QLocale::FormatType type)
+{
+ SYSTEMTIME st = {};
+ st.wYear = 2001;
+ st.wMonth = month;
+ st.wDay = 10;
+
+ const DWORD flags{}; // Must be clear when passing a format string.
+ // MS's docs for the LOCALE_SMONTHNAME* say to include the day in a format.
+ // Educated guess: this works for the LOCALE_SABBREVMONTHNAME*, too, in so
+ // far as the abbreviated plain name might differ from abbreviated
+ // standalone one.
+ const wchar_t *const format = type == QLocale::LongFormat ? L"ddMMMM" : L"ddMMM";
+ wchar_t buf[255];
+ if (getDateFormat(flags, &st, format, buf, 255) > 2) {
+ // Elide the two digits of day number
+ return nullIfEmpty(correctDigits(QString::fromWCharArray(buf + 2)));
+ }
+ return {};
+}
+
+static QString fourDigitYear(int year)
+{
+ // Return year formatted as an (at least) four digit number:
+ return QStringLiteral("%1").arg(year, 4, 10, QChar(u'0'));
+}
- LCTYPE lctype = (type == QLocale::ShortFormat || type == QLocale::NarrowFormat)
- ? short_month_map[month] : long_month_map[month];
- return getLocaleInfo(lctype);
+QString QSystemLocalePrivate::yearFix(int year, int fakeYear, QString &&formatted)
+{
+ // Replace our ersatz fakeYear (that MS formats faithfully) with the correct
+ // form of year. We know the two-digit short form of fakeYear can not be
+ // mistaken for the month or day-of-month in the formatted date.
+ Q_ASSERT(fakeYear >= 1970 && fakeYear <= 2400);
+ const bool matchTwo = year >= 0 && year % 100 == fakeYear % 100;
+ auto yearUsed = fourDigitYear(fakeYear);
+ QString sign(year < 0 ? 1 : 0, u'-');
+ auto trueYear = fourDigitYear(year < 0 ? -year : year);
+ if (formatted.contains(yearUsed))
+ return std::move(formatted).replace(yearUsed, sign + trueYear);
+
+ auto tail = QStringView{yearUsed}.last(2);
+ Q_ASSERT(!matchTwo || tail == QString(sign + trueYear.last(2)));
+ if (formatted.contains(tail)) {
+ if (matchTwo)
+ return std::move(formatted);
+ return std::move(formatted).replace(tail.toString(), sign + trueYear.last(2));
+ }
+
+ // Localized digits (regardless of SAlways), perhaps ?
+ // First call to substituteDigits() ensures zero is initialized:
+ trueYear = substituteDigits(std::move(trueYear));
+ if (zero != u'0') {
+ yearUsed = substituteDigits(std::move(yearUsed));
+ if (year < 0)
+ sign = negativeSign().toString();
+
+ if (formatted.contains(yearUsed))
+ return std::move(formatted).replace(yearUsed, sign + trueYear);
+
+ const qsizetype twoDigits = 2 * zero.size();
+ tail = QStringView{yearUsed}.last(twoDigits);
+ if (formatted.contains(tail)) {
+ if (matchTwo)
+ return std::move(formatted);
+ return std::move(formatted).replace(tail.toString(), sign + trueYear.last(twoDigits));
+ }
+ }
+ qWarning("Failed to fix up year in formatted date-string using %d for %d", fakeYear, year);
+ return std::move(formatted);
}
QVariant QSystemLocalePrivate::toString(QDate date, QLocale::FormatType type)
{
SYSTEMTIME st = {};
- st.wYear = date.year();
+ const int year = date.year();
+ // st.wYear is unsigned; and GetDateFormat() is documented to not handle
+ // dates before 1601.
+ const bool fixup = year < 1601;
+ st.wYear = fixup ? QGregorianCalendar::yearSharingWeekDays(date) : year;
st.wMonth = date.month();
st.wDay = date.day();
+ Q_ASSERT(!fixup || st.wYear % 100 != st.wMonth);
+ Q_ASSERT(!fixup || st.wYear % 100 != st.wDay);
+ // i.e. yearFix() can trust a match of its fakeYear's last two digits to not
+ // be the month or day part of the formatted date.
+
DWORD flags = (type == QLocale::LongFormat ? DATE_LONGDATE : DATE_SHORTDATE);
wchar_t buf[255];
if (getDateFormat(flags, &st, NULL, buf, 255)) {
- QString format = QString::fromWCharArray(buf);
- if (substitution() == SAlways)
- format = substituteDigits(std::move(format));
- return format;
+ QString text = QString::fromWCharArray(buf);
+ if (fixup)
+ text = yearFix(year, st.wYear, std::move(text));
+ return nullIfEmpty(correctDigits(std::move(text)));
}
- return QString();
+ return {};
}
QVariant QSystemLocalePrivate::toString(QTime time, QLocale::FormatType type)
@@ -417,22 +525,23 @@ QVariant QSystemLocalePrivate::toString(QTime time, QLocale::FormatType type)
DWORD flags = 0;
// keep the same conditional as timeFormat() above
- if (type == QLocale::ShortFormat)
- flags = TIME_NOSECONDS;
+ const QString format = type == QLocale::ShortFormat
+ ? getLocaleInfo(LOCALE_SSHORTTIME).toString()
+ : QString();
+ auto formatStr = reinterpret_cast<const wchar_t *>(format.isEmpty() ? nullptr : format.utf16());
wchar_t buf[255];
- if (getTimeFormat(flags, &st, NULL, buf, 255)) {
- QString format = QString::fromWCharArray(buf);
- if (substitution() == SAlways)
- format = substituteDigits(std::move(format));
- return format;
- }
- return QString();
+ if (getTimeFormat(flags, &st, formatStr, buf, int(std::size(buf))))
+ return nullIfEmpty(correctDigits(QString::fromWCharArray(buf)));
+ return {};
}
QVariant QSystemLocalePrivate::toString(const QDateTime &dt, QLocale::FormatType type)
{
- return QString(toString(dt.date(), type).toString() + QLatin1Char(' ') + toString(dt.time(), type).toString());
+ QVariant d = toString(dt.date(), type), t = toString(dt.time(), type);
+ if (d.typeId() == QMetaType::QString && t.typeId() == QMetaType::QString)
+ return QString(d.toString() + u' ' + t.toString());
+ return {};
}
QVariant QSystemLocalePrivate::measurementSystem()
@@ -440,10 +549,8 @@ QVariant QSystemLocalePrivate::measurementSystem()
wchar_t output[2];
if (getLocaleInfo(LOCALE_IMEASURE, output, 2)) {
- QString iMeasure = QString::fromWCharArray(output);
- if (iMeasure == QLatin1String("1")) {
+ if (output[0] == L'1' && !output[1])
return QLocale::ImperialSystem;
- }
}
return QLocale::MetricSystem;
@@ -459,7 +566,7 @@ QVariant QSystemLocalePrivate::amText()
wchar_t output[15]; // maximum length including terminating zero character for Win2003+
if (getLocaleInfo(LOCALE_S1159, output, 15))
- return QString::fromWCharArray(output);
+ return nullIfEmpty(QString::fromWCharArray(output));
return QVariant();
}
@@ -469,7 +576,7 @@ QVariant QSystemLocalePrivate::pmText()
wchar_t output[15]; // maximum length including terminating zero character for Win2003+
if (getLocaleInfo(LOCALE_S2359, output, 15))
- return QString::fromWCharArray(output);
+ return nullIfEmpty(QString::fromWCharArray(output));
return QVariant();
}
@@ -489,12 +596,14 @@ QVariant QSystemLocalePrivate::currencySymbol(QLocale::CurrencySymbolFormat form
wchar_t buf[13];
switch (format) {
case QLocale::CurrencySymbol:
+ // Some locales do have empty currency symbol. All the same, fall back
+ // to CLDR for confirmation if MS claims that applies.
if (getLocaleInfo(LOCALE_SCURRENCY, buf, 13))
- return QString::fromWCharArray(buf);
+ return nullIfEmpty(QString::fromWCharArray(buf));
break;
case QLocale::CurrencyIsoCode:
if (getLocaleInfo(LOCALE_SINTLSYMBOL, buf, 9))
- return QString::fromWCharArray(buf);
+ return nullIfEmpty(QString::fromWCharArray(buf));
break;
case QLocale::CurrencyDisplayName: {
QVarLengthArray<wchar_t, 64> buf(64);
@@ -505,7 +614,7 @@ QVariant QSystemLocalePrivate::currencySymbol(QLocale::CurrencySymbolFormat form
if (!getLocaleInfo(LOCALE_SNATIVECURRNAME, buf.data(), buf.size()))
break;
}
- return QString::fromWCharArray(buf.data());
+ return nullIfEmpty(QString::fromWCharArray(buf.data()));
}
default:
break;
@@ -565,7 +674,7 @@ QVariant QSystemLocalePrivate::toCurrencyString(const QSystemLocale::CurrencyToS
// int(32) == "12,34,56,789.00" == string("3;2;0")
// int(320)== "1234,56,789.00" == string("3;2")
QString groupingStr = getLocaleInfo(LOCALE_SMONGROUPING).toString();
- format.Grouping = groupingStr.remove(QLatin1Char(';')).toInt();
+ format.Grouping = groupingStr.remove(u';').toInt();
if (format.Grouping % 10 == 0) // magic
format.Grouping /= 10;
else
@@ -583,14 +692,26 @@ QVariant QSystemLocalePrivate::toCurrencyString(const QSystemLocale::CurrencyToS
pformat, out.data(), out.size());
}
- value = QString::fromWCharArray(out.data());
- if (substitution() == SAlways)
- value = substituteDigits(std::move(value));
- return value;
+ return nullIfEmpty(correctDigits(QString::fromWCharArray(out.data())));
}
QVariant QSystemLocalePrivate::uiLanguages()
{
+ QStringList result;
+#if QT_CONFIG(cpp_winrt)
+ using namespace winrt;
+ using namespace Windows::System::UserProfile;
+ QT_TRY {
+ auto languages = GlobalizationPreferences::Languages();
+ for (const auto &lang : languages)
+ result << QString::fromStdString(winrt::to_string(lang));
+ } QT_CATCH(...) {
+ // pass, just fall back to WIN32 API implementation
+ }
+ if (!result.isEmpty())
+ return result; // else just fall back to WIN32 API implementation
+#endif // QT_CONFIG(cpp_winrt)
+ // mingw and clang still have to use Win32 API
unsigned long cnt = 0;
QVarLengthArray<wchar_t, 64> buf(64);
# if !defined(QT_BOOTSTRAPPED) // Not present in MinGW 4.9/bootstrap builds.
@@ -601,11 +722,10 @@ QVariant QSystemLocalePrivate::uiLanguages()
GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &cnt, NULL, &size)) {
buf.resize(size);
if (!GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &cnt, buf.data(), &size))
- return QStringList();
+ return {};
}
}
# endif // !QT_BOOTSTRAPPED
- QStringList result;
result.reserve(cnt);
const wchar_t *str = buf.constData();
for (; cnt > 0; --cnt) {
@@ -615,7 +735,7 @@ QVariant QSystemLocalePrivate::uiLanguages()
result.append(s);
str += s.size() + 1;
}
- return result;
+ return nullIfEmpty(std::move(result));
}
QVariant QSystemLocalePrivate::nativeLanguageName()
@@ -631,7 +751,7 @@ QVariant QSystemLocalePrivate::nativeTerritoryName()
void QSystemLocalePrivate::update()
{
- lcid = GetUserDefaultLCID();
+ lcid = getDefaultWinId();
substitutionType = SUnknown;
zero.resize(0);
}
@@ -639,20 +759,20 @@ void QSystemLocalePrivate::update()
QString QSystemLocalePrivate::winToQtFormat(QStringView sys_fmt)
{
QString result;
- int i = 0;
+ qsizetype i = 0;
while (i < sys_fmt.size()) {
- if (sys_fmt.at(i).unicode() == QLatin1Char('\'')) {
+ if (sys_fmt.at(i).unicode() == u'\'') {
QString text = qt_readEscapedFormatString(sys_fmt, &i);
- if (text == QLatin1String("'"))
- result += QLatin1String("''");
+ if (text == "'"_L1)
+ result += "''"_L1;
else
- result += QLatin1Char('\'') + text + QLatin1Char('\'');
+ result += u'\'' + text + u'\'';
continue;
}
QChar c = sys_fmt.at(i);
- int repeat = qt_repeatCount(sys_fmt.mid(i));
+ qsizetype repeat = qt_repeatCount(sys_fmt.mid(i));
switch (c.unicode()) {
// Date
@@ -663,13 +783,13 @@ QString QSystemLocalePrivate::winToQtFormat(QStringView sys_fmt)
repeat = 2;
switch (repeat) {
case 1:
- result += QLatin1String("yy"); // "y" unsupported by Qt, use "yy"
+ result += "yy"_L1; // "y" unsupported by Qt, use "yy"
break;
case 5:
- result += QLatin1String("yyyy"); // "yyyyy" same as "yyyy" on Windows
+ result += "yyyy"_L1; // "yyyyy" same as "yyyy" on Windows
break;
default:
- result += QString(repeat, QLatin1Char('y'));
+ result += QString(repeat, u'y');
break;
}
break;
@@ -680,14 +800,14 @@ QString QSystemLocalePrivate::winToQtFormat(QStringView sys_fmt)
case 2:
break; // no equivalent of "gg" in Qt
default:
- result += QLatin1Char('g');
+ result += u'g';
break;
}
break;
case 't':
if (repeat > 2)
repeat = 2;
- result += QLatin1String("AP"); // "t" unsupported, use "AP"
+ result += "AP"_L1; // "t" unsupported, use "AP"
break;
default:
result += QString(repeat, c);
@@ -733,12 +853,25 @@ QVariant QSystemLocale::query(QueryType type, QVariant in) const
return d->dayName(in.toInt(), QLocale::LongFormat);
case DayNameShort:
return d->dayName(in.toInt(), QLocale::ShortFormat);
+ case DayNameNarrow:
+ return d->dayName(in.toInt(), QLocale::NarrowFormat);
+ case StandaloneDayNameLong:
+ case StandaloneDayNameShort:
+ case StandaloneDayNameNarrow:
+ // Windows does not provide standalone day names, so fall back to CLDR
+ return QVariant();
case MonthNameLong:
- case StandaloneMonthNameLong:
return d->monthName(in.toInt(), QLocale::LongFormat);
+ case StandaloneMonthNameLong:
+ return d->standaloneMonthName(in.toInt(), QLocale::LongFormat);
case MonthNameShort:
- case StandaloneMonthNameShort:
return d->monthName(in.toInt(), QLocale::ShortFormat);
+ case StandaloneMonthNameShort:
+ return d->standaloneMonthName(in.toInt(), QLocale::ShortFormat);
+ case MonthNameNarrow:
+ case StandaloneMonthNameNarrow:
+ // Windows provides no narrow month names, so we fall back to CLDR
+ return QVariant();
case DateToStringShort:
return d->toString(in.toDate(), QLocale::ShortFormat);
case DateToStringLong:
@@ -776,7 +909,7 @@ QVariant QSystemLocale::query(QueryType type, QVariant in) const
case CurrencySymbol:
return d->currencySymbol(QLocale::CurrencySymbolFormat(in.toUInt()));
case CurrencyToString:
- return d->toCurrencyString(in.value<QSystemLocale::CurrencyToStringArgument>());
+ return d->toCurrencyString(in.value<CurrencyToStringArgument>());
case UILanguages:
return d->uiLanguages();
case LocaleChanged:
@@ -798,8 +931,18 @@ struct WindowsToISOListElt {
char iso_name[6];
};
-/* NOTE: This array should be sorted by the first column! */
-static const WindowsToISOListElt windows_to_iso_list[] = {
+namespace {
+struct ByWindowsCode {
+ constexpr bool operator()(int lhs, WindowsToISOListElt rhs) const noexcept
+ { return lhs < int(rhs.windows_code); }
+ constexpr bool operator()(WindowsToISOListElt lhs, int rhs) const noexcept
+ { return int(lhs.windows_code) < rhs; }
+ constexpr bool operator()(WindowsToISOListElt lhs, WindowsToISOListElt rhs) const noexcept
+ { return lhs.windows_code < rhs.windows_code; }
+};
+} // unnamed namespace
+
+static constexpr WindowsToISOListElt windows_to_iso_list[] = {
{ 0x0401, "ar_SA" },
{ 0x0402, "bg\0 " },
{ 0x0403, "ca\0 " },
@@ -910,40 +1053,33 @@ static const WindowsToISOListElt windows_to_iso_list[] = {
{ 0x500a, "es_PR" }
};
-static const int windows_to_iso_count
- = sizeof(windows_to_iso_list)/sizeof(WindowsToISOListElt);
+static_assert(q20::is_sorted(std::begin(windows_to_iso_list), std::end(windows_to_iso_list),
+ ByWindowsCode{}));
static const char *winLangCodeToIsoName(int code)
{
int cmp = code - windows_to_iso_list[0].windows_code;
if (cmp < 0)
- return 0;
+ return nullptr;
if (cmp == 0)
return windows_to_iso_list[0].iso_name;
- int begin = 0;
- int end = windows_to_iso_count;
+ const auto it = std::lower_bound(std::begin(windows_to_iso_list),
+ std::end(windows_to_iso_list),
+ code,
+ ByWindowsCode{});
+ if (it != std::end(windows_to_iso_list) && !ByWindowsCode{}(code, *it))
+ return it->iso_name;
- while (end - begin > 1) {
- uint mid = (begin + end)/2;
-
- const WindowsToISOListElt *elt = windows_to_iso_list + mid;
- int cmp = code - elt->windows_code;
- if (cmp < 0)
- end = mid;
- else if (cmp > 0)
- begin = mid;
- else
- return elt->iso_name;
- }
-
- return 0;
+ return nullptr;
}
LCID qt_inIsoNametoLCID(const char *name)
{
+ if (!name)
+ return LOCALE_USER_DEFAULT;
// handle norwegian manually, the list above will fail
if (!strncmp(name, "nb", 2))
return 0x0414;
@@ -980,14 +1116,12 @@ static QString winIso639LangName(LCID id)
lang_code = QString::fromWCharArray(out);
if (!lang_code.isEmpty()) {
- const char *endptr;
- bool ok;
- QByteArray latin1_lang_code = std::move(lang_code).toLatin1();
- int i = qstrtoull(latin1_lang_code, &endptr, 16, &ok);
- if (ok && *endptr == '\0') {
+ const QByteArray latin1 = std::move(lang_code).toLatin1();
+ const auto [i, used] = qstrntoull(latin1.data(), latin1.size(), 16);
+ if (used >= latin1.size() || (used > 0 && latin1[used] == '\0')) {
switch (i) {
case 0x814:
- result = QLatin1String("nn"); // Nynorsk
+ result = u"nn"_s; // Nynorsk
break;
default:
break;
@@ -1020,28 +1154,24 @@ static QByteArray getWinLocaleName(LCID id)
{
QByteArray result;
if (id == LOCALE_USER_DEFAULT) {
- static const QByteArray langEnvVar = qgetenv("LANG");
- result = langEnvVar;
- if (result == "C"
- || (!result.isEmpty() && qt_splitLocaleName(QString::fromLocal8Bit(result)))) {
- bool ok = false; // See if we have a Windows locale code instead of a locale name:
- long id = qstrtoll(result.data(), 0, 0, &ok);
- if (!ok || id == 0 || id < INT_MIN || id > INT_MAX) // Assume real locale name
- return result;
- return winLangCodeToIsoName(int(id));
- }
- }
+ const auto [name, lcid] = scanLangEnv();
+ if (!name.isEmpty())
+ return name;
+ if (lcid)
+ return winLangCodeToIsoName(lcid);
- if (id == LOCALE_USER_DEFAULT)
id = GetUserDefaultLCID();
+ }
+
QString resultusage = winIso639LangName(id);
QString country = winIso3116CtryName(id);
if (!country.isEmpty())
- resultusage += QLatin1Char('_') + country;
+ resultusage += u'_' + country;
return std::move(resultusage).toLatin1();
}
+// Helper for plugins/platforms/windows/
Q_CORE_EXPORT QLocale qt_localeFromLCID(LCID id)
{
return QLocale(QString::fromLatin1(getWinLocaleName(id)));