diff options
Diffstat (limited to 'src/corelib/text/qlocale_mac.mm')
-rw-r--r-- | src/corelib/text/qlocale_mac.mm | 272 |
1 files changed, 168 insertions, 104 deletions
diff --git a/src/corelib/text/qlocale_mac.mm b/src/corelib/text/qlocale_mac.mm index f3ca942845..89339be2eb 100644 --- a/src/corelib/text/qlocale_mac.mm +++ b/src/corelib/text/qlocale_mac.mm @@ -14,6 +14,9 @@ #include <CoreFoundation/CoreFoundation.h> #endif +#include <QtCore/qloggingcategory.h> +#include <QtCore/qcoreapplication.h> + QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; @@ -22,6 +25,41 @@ 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(); @@ -118,14 +156,26 @@ 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, qsizetype minDigits, const QString &zero) @@ -188,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); } @@ -201,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 @@ -219,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: @@ -267,99 +323,107 @@ static QVariant macToQtFormat(QStringView sys_fmt) } QChar c = sys_fmt.at(i); - qsizetype repeat = qt_repeatCount(sys_fmt.mid(i)); + qsizetype repeat = qt_repeatCount(sys_fmt.sliced(i)); switch (c.unicode()) { // Qt does not support the following options - case 'G': // Era (1..5): 4 = long, 1..3 = short, 5 = narrow - case 'Y': // Year of Week (1..n): 1..n = padded number - case 'U': // Cyclic Year Name (1..5): 4 = long, 1..3 = short, 5 = narrow - case 'Q': // Quarter (1..4): 4 = long, 3 = short, 1..2 = padded number - case 'q': // Standalone Quarter (1..4): 4 = long, 3 = short, 1..2 = padded number - case 'w': // Week of Year (1..2): 1..2 = padded number - case 'W': // Week of Month (1): 1 = number - case 'D': // Day of Year (1..3): 1..3 = padded number - case 'F': // Day of Week in Month (1): 1 = number - case 'g': // Modified Julian Day (1..n): 1..n = padded number - case 'A': // Milliseconds in Day (1..n): 1..n = padded number - break; - - case 'y': // Year (1..n): 2 = short year, 1 & 3..n = padded number - 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 += "yy"_L1; - else - 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 += "MMM"_L1; - else - result += QString(repeat, u'M'); - break; - case 'd': // Day of Month (1..2): 1..2 padded number - result += QString(repeat, c); - break; - 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 += "dddd"_L1; - else - 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 += "ddd"_L1; - else - result += QString(repeat, 'd'_L1); - break; - case 'a': // AM/PM (1): 1 = short - // Translate to Qt uppercase AM/PM - 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, '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, 'H'_L1); - break; - case 'm': // Minutes (1..2): 1..2 = padded number - case 's': // Seconds (1..2): 1..2 = padded number + case 'A': // Milliseconds in Day (1..n): 1..n = padded number + case 'C': // Input skeleton symbol. + case 'D': // Day of Year (1..3): 1..3 = padded number + case 'F': // Day of Week in Month (1): 1 = number + case 'g': // Modified Julian Day (1..n): 1..n = padded number + case 'G': // Era (1..5): 4 = long, 1..3 = short, 5 = narrow + case 'j': // Input skeleton symbol. + case 'J': // Input skeleton symbol. + case 'l': // Deprecated Chinese leap month indicator. + case 'q': // Standalone Quarter (1..4): 4 = long, 3 = short, 1,2 = padded number + case 'Q': // Quarter (1..4): 4 = long, 3 = short, 1,2 = padded number + case 'U': // Cyclic Year Name (1..5): 4 = long, 1..3 = short, 5 = narrow + case 'w': // Week of Year (1,2): 1,2 = padded number + case 'W': // Week of Month (1): 1 = number + case 'Y': // Year for Week-of-year calendars (1..n): 1..n = padded number + break; + + case 'u': // Extended Year (1..n), padded number. + // Explicitly has no special case for 'uu' as only the last two digits. + result += "yyyy"_L1; + break; + case 'y': // 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 += "yy"_L1; + else + result += "yyyy"_L1; + break; + case 'L': // Standalone Month (1..5): 4 = long, 3 = short, 1,2 = number, 5 = narrow + case 'M': // 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 += "MMM"_L1; + else + result += QString(repeat, u'M'); + break; + case 'd': // Day of Month (1,2): 1,2 padded number + result += QString(repeat, c); + break; + case 'c': // Standalone version of 'e' + case 'e': // Local Day of Week (1..6): 4 = long, 3 = short, 5,6 = narrow, 1,2 padded number + // "Local" only affects numeric form: depends on locale's start-day of the week. + case 'E': // Day of Week (1..6): 4 = long, 1..3 = short, 5,6 = narrow + // Qt only supports long, short: use short for narrow and padded number. + if (repeat == 4) + result += "dddd"_L1; + else + result += "ddd"_L1; + break; + case 'a': // AM/PM (1..n): Qt supports no distinctions + case 'b': // Like a, but also distinguishing noon, midnight (ignore difference). + case 'B': // Flexible day period (at night, &c.) + // Translate to Qt AM/PM, using locale-appropriate case: + 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 + 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, 'H'_L1); + break; + case 'm': // Minutes (1,2): 1,2 = padded number + case 's': // Seconds (1,2): 1,2 = padded number + result += QString(repeat, c); + break; + 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 += u'z'; + else + result += "zzz"_L1; + break; + case 'O': // Time Zone (1, 4) + result += u't'; + break; + case 'v': // Time Zone (1, 4) + case 'V': // Time Zone (1..4) + result += "tttt"_L1; + break; + case 'x': // Time Zone (1..5) + case 'X': // Time Zone (1..5) + result += (repeat > 1 && (repeat & 1)) ? "ttt"_L1 : "tt"_L1; + break; + case 'z': // Time Zone (1..4) + case 'Z': // Time Zone (1..5) + result += repeat < 4 ? "tt"_L1 : repeat > 4 ? "ttt"_L1 : "t"_L1; + 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 < u'A' || c > u'z' || (c > u'Z' && c < u'a')) result += QString(repeat, c); - break; - 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 += u'z'; - else - result += "zzz"_L1; - break; - case 'z': // Time Zone (1..4) - case 'Z': // Time Zone (1..5) - case 'O': // Time Zone (1, 4) - case 'v': // Time Zone (1, 4) - case 'V': // Time Zone (1..4) - case 'X': // Time Zone (1..5) - case 'x': // Time Zone (1..5) - 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 < u'A' || c > u'z' || (c > u'Z' && c < u'a')) - result += QString(repeat, c); - break; + break; } i += repeat; @@ -517,7 +581,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; @@ -585,7 +649,7 @@ 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(); |