summaryrefslogtreecommitdiffstats
path: root/src/corelib/text/qlocale_mac.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/text/qlocale_mac.mm')
-rw-r--r--src/corelib/text/qlocale_mac.mm186
1 files changed, 103 insertions, 83 deletions
diff --git a/src/corelib/text/qlocale_mac.mm b/src/corelib/text/qlocale_mac.mm
index 7d75db31d0..ed4399e6a3 100644
--- a/src/corelib/text/qlocale_mac.mm
+++ b/src/corelib/text/qlocale_mac.mm
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2021 The Qt Company Ltd.
-** 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.
+// 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"
@@ -50,12 +14,52 @@
#include <CoreFoundation/CoreFoundation.h>
#endif
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qcoreapplication.h>
+
QT_BEGIN_NAMESPACE
+using namespace Qt::StringLiterals;
+
/******************************************************************************
** Wrappers for Mac locale system functions
*/
+Q_LOGGING_CATEGORY(lcLocale, "qt.core.locale")
+
+static void printLocalizationInformation()
+{
+ if (!lcLocale().isDebugEnabled())
+ return;
+
+#if defined(Q_OS_MACOS)
+ // Trigger initialization of standard user defaults, so that Foundation picks
+ // up -AppleLanguages and -AppleLocale passed on the command line.
+ Q_UNUSED(NSUserDefaults.standardUserDefaults);
+#endif
+
+ auto singleLineDescription = [](NSArray *array) {
+ NSString *str = [array description];
+ str = [str stringByReplacingOccurrencesOfString:@"\n" withString:@""];
+ return [str stringByReplacingOccurrencesOfString:@" " withString:@""];
+ };
+
+ bool allowMixedLocalizations = [NSBundle.mainBundle.infoDictionary[@"CFBundleAllowMixedLocalizations"] boolValue];
+
+ NSBundle *foundation = [NSBundle bundleForClass:NSBundle.class];
+ qCDebug(lcLocale).nospace() << "Launched with locale \"" << NSLocale.currentLocale.localeIdentifier
+ << "\" based on user's preferred languages " << singleLineDescription(NSLocale.preferredLanguages)
+ << ", main bundle localizations " << singleLineDescription(NSBundle.mainBundle.localizations)
+ << ", and allowing mixed localizations " << allowMixedLocalizations
+ << "; resulting in main bundle preferred localizations "
+ << singleLineDescription(NSBundle.mainBundle.preferredLocalizations)
+ << " and Foundation preferred localizations "
+ << singleLineDescription(foundation.preferredLocalizations);
+ qCDebug(lcLocale) << "Reflected by Qt as system locale"
+ << QLocale::system() << "with UI languges " << QLocale::system().uiLanguages();
+}
+Q_COREAPP_STARTUP_FUNCTION(printLocalizationInformation);
+
static QString getMacLocaleName()
{
QCFType<CFLocaleRef> l = CFLocaleCopyCurrent();
@@ -152,23 +156,35 @@ static QVariant macDayName(int day, QSystemLocale::QueryType type)
static QString macZeroDigit()
{
- QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
- QCFType<CFNumberFormatterRef> numberFormatter =
- CFNumberFormatterCreate(nullptr, locale, kCFNumberFormatterNoStyle);
- const int zeroDigit = 0;
- QCFType<CFStringRef> value
- = CFNumberFormatterCreateStringWithValue(nullptr, numberFormatter,
- kCFNumberIntType, &zeroDigit);
- return QString::fromCFString(value);
+ static QString cachedZeroDigit;
+
+ if (cachedZeroDigit.isNull()) {
+ QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
+ QCFType<CFNumberFormatterRef> numberFormatter =
+ CFNumberFormatterCreate(nullptr, locale, kCFNumberFormatterNoStyle);
+ const int zeroDigit = 0;
+ QCFType<CFStringRef> value
+ = CFNumberFormatterCreateStringWithValue(nullptr, numberFormatter,
+ kCFNumberIntType, &zeroDigit);
+ cachedZeroDigit = QString::fromCFString(value);
+ }
+
+ static QMacNotificationObserver localeChangeObserver = QMacNotificationObserver(
+ nil, NSCurrentLocaleDidChangeNotification, [&] {
+ qCDebug(lcLocale) << "System locale changed";
+ cachedZeroDigit = QString();
+ });
+
+ return cachedZeroDigit;
}
-static QString zeroPad(QString &&number, int minDigits, const QString &zero)
+static QString zeroPad(QString &&number, qsizetype minDigits, const QString &zero)
{
// Need to pad with zeros, possibly after a sign.
- int insert = -1, digits = 0;
+ qsizetype insert = -1, digits = 0;
auto it = QStringIterator(number);
while (it.hasNext()) {
- int here = it.index();
+ qsizetype here = it.index();
if (QChar::isDigit(it.next())) {
if (insert < 0)
insert = here;
@@ -188,10 +204,10 @@ static QString trimTwoDigits(QString &&number)
// Retain any sign, but remove all but the last two digits.
// We know number has at least four digits - it came from fourDigitYear().
// Note that each digit might be a surrogate pair.
- int first = -1, prev = -1, last = -1;
+ qsizetype first = -1, prev = -1, last = -1;
auto it = QStringIterator(number);
while (it.hasNext()) {
- int here = it.index();
+ qsizetype here = it.index();
if (QChar::isDigit(it.next())) {
if (first == -1)
last = first = here;
@@ -222,12 +238,16 @@ static QString fourDigitYear(int year, const QString &zero)
static QString macDateToStringImpl(QDate date, CFDateFormatterStyle style)
{
- QCFType<CFDateRef> myDate = date.startOfDay().toCFDate();
+ // Use noon on the given date, to avoid complications that can arise for
+ // dates before 1900 (see QTBUG-54955) using different UTC offset than
+ // QDateTime extrapolates backwards from time_t functions that only work
+ // back to 1900. (Alaska and Phillipines may still be borked, though.)
+ QCFType<CFDateRef> myDate = QDateTime(date, QTime(12, 0)).toCFDate();
QCFType<CFLocaleRef> mylocale = CFLocaleCopyCurrent();
QCFType<CFDateFormatterRef> myFormatter
= CFDateFormatterCreate(kCFAllocatorDefault, mylocale, style,
kCFDateFormatterNoStyle);
- QCFType<CFStringRef> text = CFDateFormatterCreateStringWithDate(0, myFormatter, myDate);
+ QCFType<CFStringRef> text = CFDateFormatterCreateStringWithDate(nullptr, myFormatter, myDate);
return QString::fromCFString(text);
}
@@ -235,12 +255,14 @@ static QVariant macDateToString(QDate date, bool short_format)
{
const int year = date.year();
QString fakeYear, trueYear;
- if (year < 0) {
+ if (year < 1583) {
// System API (in macOS 11.0, at least) discards sign :-(
// Simply negating the year won't do as the resulting year typically has
// a different pattern of week-days.
+ // Furthermore (see QTBUG-54955), Darwin uses the Julian calendar for
+ // dates before 1582-10-15, leading to discrepancies.
int matcher = QGregorianCalendar::yearSharingWeekDays(date);
- Q_ASSERT(matcher > 0);
+ Q_ASSERT(matcher >= 1583);
Q_ASSERT(matcher % 100 != date.month());
Q_ASSERT(matcher % 100 != date.day());
// i.e. there can't be any confusion between the two-digit year and
@@ -253,7 +275,7 @@ static QVariant macDateToString(QDate date, bool short_format)
QString text = macDateToStringImpl(date, short_format
? kCFDateFormatterShortStyle
: kCFDateFormatterLongStyle);
- if (year < 0) {
+ if (year < 1583) {
if (text.contains(fakeYear))
return std::move(text).replace(fakeYear, trueYear);
// Cope with two-digit year:
@@ -288,20 +310,20 @@ static QVariant macTimeToString(QTime time, bool short_format)
static QVariant macToQtFormat(QStringView sys_fmt)
{
QString result;
- int i = 0;
+ qsizetype i = 0;
while (i < sys_fmt.size()) {
if (sys_fmt.at(i).unicode() == '\'') {
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()) {
// Qt does not support the following options
@@ -322,17 +344,17 @@ static QVariant macToQtFormat(QStringView sys_fmt)
case 'u': // Extended Year (1..n): 2 = short year, 1 & 3..n = padded number
// Qt only supports long (4) or short (2) year, use long for all others
if (repeat == 2)
- result += QLatin1String("yy");
+ result += "yy"_L1;
else
- result += QLatin1String("yyyy");
+ result += "yyyy"_L1;
break;
case 'M': // Month (1..5): 4 = long, 3 = short, 1..2 = number, 5 = narrow
case 'L': // Standalone Month (1..5): 4 = long, 3 = short, 1..2 = number, 5 = narrow
// Qt only supports long, short and number, use short for narrow
if (repeat == 5)
- result += QLatin1String("MMM");
+ result += "MMM"_L1;
else
- result += QString(repeat, QLatin1Char('M'));
+ result += QString(repeat, u'M');
break;
case 'd': // Day of Month (1..2): 1..2 padded number
result += QString(repeat, c);
@@ -340,32 +362,32 @@ static QVariant macToQtFormat(QStringView sys_fmt)
case 'E': // Day of Week (1..6): 4 = long, 1..3 = short, 5..6 = narrow
// Qt only supports long, short and padded number, use short for narrow
if (repeat == 4)
- result += QLatin1String("dddd");
+ result += "dddd"_L1;
else
- result += QLatin1String("ddd");
+ result += "ddd"_L1;
break;
case 'e': // Local Day of Week (1..6): 4 = long, 3 = short, 5..6 = narrow, 1..2 padded number
case 'c': // Standalone Local Day of Week (1..6): 4 = long, 3 = short, 5..6 = narrow, 1..2 padded number
// Qt only supports long, short and padded number, use short for narrow
if (repeat >= 5)
- result += QLatin1String("ddd");
+ result += "ddd"_L1;
else
- result += QString(repeat, QLatin1Char('d'));
+ result += QString(repeat, 'd'_L1);
break;
case 'a': // AM/PM (1): 1 = short
// Translate to Qt uppercase AM/PM
- result += QLatin1String("AP");
+ result += "AP"_L1;
break;
case 'h': // Hour [1..12] (1..2): 1..2 = padded number
case 'K': // Hour [0..11] (1..2): 1..2 = padded number
case 'j': // Local Hour [12 or 24] (1..2): 1..2 = padded number
// Qt h is local hour
- result += QString(repeat, QLatin1Char('h'));
+ result += QString(repeat, 'h'_L1);
break;
case 'H': // Hour [0..23] (1..2): 1..2 = padded number
case 'k': // Hour [1..24] (1..2): 1..2 = padded number
// Qt H is 0..23 hour
- result += QString(repeat, QLatin1Char('H'));
+ result += QString(repeat, 'H'_L1);
break;
case 'm': // Minutes (1..2): 1..2 = padded number
case 's': // Seconds (1..2): 1..2 = padded number
@@ -374,9 +396,9 @@ static QVariant macToQtFormat(QStringView sys_fmt)
case 'S': // Fractional second (1..n): 1..n = truncates to decimal places
// Qt uses msecs either unpadded or padded to 3 places
if (repeat < 3)
- result += QLatin1Char('z');
+ result += u'z';
else
- result += QLatin1String("zzz");
+ result += "zzz"_L1;
break;
case 'z': // Time Zone (1..4)
case 'Z': // Time Zone (1..5)
@@ -385,16 +407,14 @@ static QVariant macToQtFormat(QStringView sys_fmt)
case 'V': // Time Zone (1..4)
case 'X': // Time Zone (1..5)
case 'x': // Time Zone (1..5)
- result += QLatin1Char('t');
+ result += u't';
break;
default:
// a..z and A..Z are reserved for format codes, so any occurrence of these not
// already processed are not known and so unsupported formats to be ignored.
// All other chars are allowed as literals.
- if (c < QLatin1Char('A') || c > QLatin1Char('z') ||
- (c > QLatin1Char('Z') && c < QLatin1Char('a'))) {
+ if (c < u'A' || c > u'z' || (c > u'Z' && c < u'a'))
result += QString(repeat, c);
- }
break;
}
@@ -433,7 +453,7 @@ static QVariant macMeasurementSystem()
{
QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
CFStringRef system = static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleMeasurementSystem));
- if (QString::fromCFString(system) == QLatin1String("Metric")) {
+ if (QString::fromCFString(system) == "Metric"_L1) {
return QLocale::MetricSystem;
} else {
return QLocale::ImperialSystem;
@@ -553,7 +573,7 @@ static QLocale::Language codeToLanguage(QStringView s)
return QLocalePrivate::codeToLanguage(s);
}
-QVariant QSystemLocale::query(QueryType type, QVariant in) const
+QVariant QSystemLocale::query(QueryType type, QVariant &&in) const
{
QMacAutoReleasePool pool;
@@ -621,13 +641,13 @@ QVariant QSystemLocale::query(QueryType type, QVariant in) const
case CurrencySymbol:
return macCurrencySymbol(QLocale::CurrencySymbolFormat(in.toUInt()));
case CurrencyToString:
- return macFormatCurrency(in.value<QSystemLocale::CurrencyToStringArgument>());
+ return macFormatCurrency(in.value<CurrencyToStringArgument>());
case UILanguages: {
QStringList result;
QCFType<CFArrayRef> languages = CFLocaleCopyPreferredLanguages();
- const int cnt = CFArrayGetCount(languages);
+ const CFIndex cnt = CFArrayGetCount(languages);
result.reserve(cnt);
- for (int i = 0; i < cnt; ++i) {
+ for (CFIndex i = 0; i < cnt; ++i) {
const QString lang = QString::fromCFString(
static_cast<CFStringRef>(CFArrayGetValueAtIndex(languages, i)));
result.append(lang);