Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qlocale_mac.mm
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
5#include "qlocale_p.h"
6
7#include "qstringlist.h"
8#include "qvariant.h"
9#include "qdatetime.h"
10
11#include "private/qstringiterator_p.h"
12#include "private/qgregoriancalendar_p.h"
13#ifdef Q_OS_DARWIN
14#include "private/qcore_mac_p.h"
15#include <CoreFoundation/CoreFoundation.h>
16#endif
17
18#include <QtCore/qloggingcategory.h>
19#include <QtCore/qcoreapplication.h>
20
22
23using namespace Qt::StringLiterals;
24
25/******************************************************************************
26** Wrappers for Mac locale system functions
27*/
28
29Q_STATIC_LOGGING_CATEGORY(lcLocale, "qt.core.locale")
30
32{
33 if (!lcLocale().isDebugEnabled())
34 return;
35
36#if defined(Q_OS_MACOS)
37 // Trigger initialization of standard user defaults, so that Foundation picks
38 // up -AppleLanguages and -AppleLocale passed on the command line.
39 Q_UNUSED(NSUserDefaults.standardUserDefaults);
40#endif
41
42 auto singleLineDescription = [](NSArray *array) {
43 NSString *str = [array description];
44 str = [str stringByReplacingOccurrencesOfString:@"\n" withString:@""];
45 return [str stringByReplacingOccurrencesOfString:@" " withString:@""];
46 };
47
48 bool allowMixedLocalizations = [NSBundle.mainBundle.infoDictionary[@"CFBundleAllowMixedLocalizations"] boolValue];
49
50 NSBundle *foundation = [NSBundle bundleForClass:NSBundle.class];
51 qCDebug(lcLocale).nospace() << "Launched with locale \"" << NSLocale.currentLocale.localeIdentifier
52 << "\" based on user's preferred languages " << singleLineDescription(NSLocale.preferredLanguages)
53 << ", main bundle localizations " << singleLineDescription(NSBundle.mainBundle.localizations)
54 << ", and allowing mixed localizations " << allowMixedLocalizations
55 << "; resulting in main bundle preferred localizations "
56 << singleLineDescription(NSBundle.mainBundle.preferredLocalizations)
57 << " and Foundation preferred localizations "
58 << singleLineDescription(foundation.preferredLocalizations);
59 qCDebug(lcLocale) << "Reflected by Qt as system locale"
60 << QLocale::system() << "with UI languges " << QLocale::system().uiLanguages();
61}
63
65{
66 QCFType<CFLocaleRef> l = CFLocaleCopyCurrent();
67 CFStringRef locale = CFLocaleGetIdentifier(l);
68 return QString::fromCFString(locale);
69}
70
71static QVariant macMonthName(int month, QSystemLocale::QueryType type)
72{
73 month -= 1;
74 if (month < 0 || month > 11)
75 return {};
76
77 QCFType<CFDateFormatterRef> formatter
78 = CFDateFormatterCreate(0, QCFType<CFLocaleRef>(CFLocaleCopyCurrent()),
79 kCFDateFormatterNoStyle, kCFDateFormatterNoStyle);
80
81 CFDateFormatterKey formatterType;
82 switch (type) {
83 case QSystemLocale::MonthNameLong:
84 formatterType = kCFDateFormatterMonthSymbols;
85 break;
86 case QSystemLocale::MonthNameShort:
87 formatterType = kCFDateFormatterShortMonthSymbols;
88 break;
89 case QSystemLocale::MonthNameNarrow:
90 formatterType = kCFDateFormatterVeryShortMonthSymbols;
91 break;
92 case QSystemLocale::StandaloneMonthNameLong:
93 formatterType = kCFDateFormatterStandaloneMonthSymbols;
94 break;
95 case QSystemLocale::StandaloneMonthNameShort:
96 formatterType = kCFDateFormatterShortStandaloneMonthSymbols;
97 break;
98 case QSystemLocale::StandaloneMonthNameNarrow:
99 formatterType = kCFDateFormatterVeryShortStandaloneMonthSymbols;
100 break;
101 default:
102 qWarning("macMonthName: Unsupported query type %d", type);
103 return {};
104 }
105 QCFType<CFArrayRef> values
106 = static_cast<CFArrayRef>(CFDateFormatterCopyProperty(formatter, formatterType));
107
108 if (values != 0) {
109 CFStringRef cfstring = static_cast<CFStringRef>(CFArrayGetValueAtIndex(values, month));
110 return QString::fromCFString(cfstring);
111 }
112 return {};
113}
114
115static QVariant macDayName(int day, QSystemLocale::QueryType type)
116{
117 if (day < 1 || day > 7)
118 return {};
119
120 QCFType<CFDateFormatterRef> formatter
121 = CFDateFormatterCreate(0, QCFType<CFLocaleRef>(CFLocaleCopyCurrent()),
122 kCFDateFormatterNoStyle, kCFDateFormatterNoStyle);
123
124 CFDateFormatterKey formatterType;
125 switch (type) {
126 case QSystemLocale::DayNameLong:
127 formatterType = kCFDateFormatterWeekdaySymbols;
128 break;
129 case QSystemLocale::DayNameShort:
130 formatterType = kCFDateFormatterShortWeekdaySymbols;
131 break;
132 case QSystemLocale::DayNameNarrow:
133 formatterType = kCFDateFormatterVeryShortWeekdaySymbols;
134 break;
135 case QSystemLocale::StandaloneDayNameLong:
136 formatterType = kCFDateFormatterStandaloneWeekdaySymbols;
137 break;
138 case QSystemLocale::StandaloneDayNameShort:
139 formatterType = kCFDateFormatterShortStandaloneWeekdaySymbols;
140 break;
141 case QSystemLocale::StandaloneDayNameNarrow:
142 formatterType = kCFDateFormatterVeryShortStandaloneWeekdaySymbols;
143 break;
144 default:
145 qWarning("macDayName: Unsupported query type %d", type);
146 return {};
147 }
148 QCFType<CFArrayRef> values =
149 static_cast<CFArrayRef>(CFDateFormatterCopyProperty(formatter, formatterType));
150
151 if (values != 0) {
152 CFStringRef cfstring = static_cast<CFStringRef>(CFArrayGetValueAtIndex(values, day % 7));
153 return QString::fromCFString(cfstring);
154 }
155 return {};
156}
157
159{
160 static QString cachedZeroDigit;
161
162 if (cachedZeroDigit.isNull()) {
163 QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
164 QCFType<CFNumberFormatterRef> numberFormatter =
165 CFNumberFormatterCreate(nullptr, locale, kCFNumberFormatterNoStyle);
166 const int zeroDigit = 0;
167 QCFType<CFStringRef> value
168 = CFNumberFormatterCreateStringWithValue(nullptr, numberFormatter,
169 kCFNumberIntType, &zeroDigit);
170 cachedZeroDigit = QString::fromCFString(value);
171 }
172
173 static QMacNotificationObserver localeChangeObserver = QMacNotificationObserver(
174 nil, NSCurrentLocaleDidChangeNotification, [&] {
175 qCDebug(lcLocale) << "System locale changed";
176 cachedZeroDigit = QString();
177 });
178
179 return cachedZeroDigit;
180}
181
182static QString zeroPad(QString &&number, qsizetype minDigits, const QString &zero)
183{
184 // Need to pad with zeros, possibly after a sign.
185 qsizetype insert = -1, digits = 0;
186 auto it = QStringIterator(number);
187 while (it.hasNext()) {
188 qsizetype here = it.index();
189 if (QChar::isDigit(it.next())) {
190 if (insert < 0)
191 insert = here;
192 ++digits;
193 } // else: assume we're stepping over a sign (or maybe grouping separator)
194 }
195 Q_ASSERT(digits > 0);
196 Q_ASSERT(insert >= 0);
197 while (digits++ < minDigits)
198 number.insert(insert, zero);
199
200 return std::move(number);
201}
202
203static QString trimTwoDigits(QString &&number)
204{
205 // Retain any sign, but remove all but the last two digits.
206 // We know number has at least four digits - it came from fourDigitYear().
207 // Note that each digit might be a surrogate pair.
208 qsizetype first = -1, prev = -1, last = -1;
209 auto it = QStringIterator(number);
210 while (it.hasNext()) {
211 qsizetype here = it.index();
212 if (QChar::isDigit(it.next())) {
213 if (first == -1)
214 last = first = here;
215 else if (last != -1)
216 prev = std::exchange(last, here);
217 }
218 }
219 Q_ASSERT(first >= 0);
220 Q_ASSERT(prev > first);
221 Q_ASSERT(last > prev);
222 number.remove(first, prev - first);
223 return std::move(number);
224}
225
226static QString fourDigitYear(int year, const QString &zero)
227{
228 // Return year formatted as an (at least) four digit number:
229 QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
230 QCFType<CFNumberFormatterRef> numberFormatter =
231 CFNumberFormatterCreate(nullptr, locale, kCFNumberFormatterNoStyle);
232 QCFType<CFStringRef> value = CFNumberFormatterCreateStringWithValue(nullptr, numberFormatter,
233 kCFNumberIntType, &year);
234 auto text = QString::fromCFString(value);
235 if (year > -1000 && year < 1000)
236 text = zeroPad(std::move(text), 4, zero);
237 return text;
238}
239
240static QString macDateToStringImpl(QDate date, CFDateFormatterStyle style)
241{
242 // Use noon on the given date, to avoid complications that can arise for
243 // dates before 1900 (see QTBUG-54955) using different UTC offset than
244 // QDateTime extrapolates backwards from time_t functions that only work
245 // back to 1900. (Alaska and Phillipines may still be borked, though.)
246 QCFType<CFDateRef> myDate = QDateTime(date, QTime(12, 0)).toCFDate();
247 QCFType<CFLocaleRef> mylocale = CFLocaleCopyCurrent();
248 QCFType<CFDateFormatterRef> myFormatter
249 = CFDateFormatterCreate(kCFAllocatorDefault, mylocale, style,
250 kCFDateFormatterNoStyle);
251 QCFType<CFStringRef> text = CFDateFormatterCreateStringWithDate(nullptr, myFormatter, myDate);
252 return QString::fromCFString(text);
253}
254
255static QVariant macDateToString(QDate date, bool short_format)
256{
257 const int year = date.year();
258 QString fakeYear, trueYear;
259 if (year < 1583) {
260 // System API (in macOS 11.0, at least) discards sign :-(
261 // Simply negating the year won't do as the resulting year typically has
262 // a different pattern of week-days.
263 // Furthermore (see QTBUG-54955), Darwin uses the Julian calendar for
264 // dates before 1582-10-15, leading to discrepancies.
265 int matcher = QGregorianCalendar::yearSharingWeekDays(date);
266 Q_ASSERT(matcher >= 1583);
267 Q_ASSERT(matcher % 100 != date.month());
268 Q_ASSERT(matcher % 100 != date.day());
269 // i.e. there can't be any confusion between the two-digit year and
270 // month or day-of-month in the formatted date.
271 QString zero = macZeroDigit();
272 fakeYear = fourDigitYear(matcher, zero);
273 trueYear = fourDigitYear(year, zero);
274 date = QDate(matcher, date.month(), date.day());
275 }
276 QString text = macDateToStringImpl(date, short_format
277 ? kCFDateFormatterShortStyle
278 : kCFDateFormatterLongStyle);
279 if (year < 1583) {
280 if (text.contains(fakeYear))
281 return std::move(text).replace(fakeYear, trueYear);
282 // Cope with two-digit year:
283 fakeYear = trimTwoDigits(std::move(fakeYear));
284 trueYear = trimTwoDigits(std::move(trueYear));
285 if (text.contains(fakeYear))
286 return std::move(text).replace(fakeYear, trueYear);
287 // That should have worked.
288 qWarning("Failed to fix up year when formatting a date in year %d", year);
289 }
290 return text;
291}
292
293static QVariant macTimeToString(QTime time, bool short_format)
294{
295 QCFType<CFDateRef> myDate = QDateTime(QDate::currentDate(), time).toCFDate();
296 QCFType<CFLocaleRef> mylocale = CFLocaleCopyCurrent();
297 CFDateFormatterStyle style = short_format ? kCFDateFormatterShortStyle : kCFDateFormatterLongStyle;
298 QCFType<CFDateFormatterRef> myFormatter = CFDateFormatterCreate(kCFAllocatorDefault,
299 mylocale,
300 kCFDateFormatterNoStyle,
301 style);
302 QCFType<CFStringRef> text = CFDateFormatterCreateStringWithDate(0, myFormatter, myDate);
303 return QString::fromCFString(text);
304}
305
306// Mac uses the Unicode CLDR format codes
307// http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
308// See also qtbase/util/locale_database/dateconverter.py
309// Makes the assumption that input formats are always well formed and consecutive letters
310// never exceed the maximum for the format code.
311static QVariant macToQtFormat(QStringView sys_fmt)
312{
313 QString result;
314 qsizetype i = 0;
315
316 while (i < sys_fmt.size()) {
317 if (sys_fmt.at(i).unicode() == '\'') {
318 QString text = qt_readEscapedFormatString(sys_fmt, &i);
319 if (text == "'"_L1)
320 result += "''"_L1;
321 else
322 result += u'\'' + text + u'\'';
323 continue;
324 }
325
326 QChar c = sys_fmt.at(i);
327 qsizetype repeat = qt_repeatCount(sys_fmt.sliced(i));
328
329 switch (c.unicode()) {
330 // Qt does not support the following options
331 case 'A': // Milliseconds in Day (1..n): 1..n = padded number
332 case 'C': // Input skeleton symbol.
333 case 'D': // Day of Year (1..3): 1..3 = padded number
334 case 'F': // Day of Week in Month (1): 1 = number
335 case 'g': // Modified Julian Day (1..n): 1..n = padded number
336 case 'G': // Era (1..5): 4 = long, 1..3 = short, 5 = narrow
337 case 'j': // Input skeleton symbol.
338 case 'J': // Input skeleton symbol.
339 case 'l': // Deprecated Chinese leap month indicator.
340 case 'q': // Standalone Quarter (1..4): 4 = long, 3 = short, 1,2 = padded number
341 case 'Q': // Quarter (1..4): 4 = long, 3 = short, 1,2 = padded number
342 case 'U': // Cyclic Year Name (1..5): 4 = long, 1..3 = short, 5 = narrow
343 case 'w': // Week of Year (1,2): 1,2 = padded number
344 case 'W': // Week of Month (1): 1 = number
345 case 'Y': // Year for Week-of-year calendars (1..n): 1..n = padded number
346 break;
347
348 case 'u': // Extended Year (1..n), padded number.
349 // Explicitly has no special case for 'uu' as only the last two digits.
350 result += "yyyy"_L1;
351 break;
352 case 'y': // Year (1..n): 2 = short year, 1 & 3..n = padded number
353 // Qt only supports long (4) or short (2) year, use long for all others
354 if (repeat == 2)
355 result += "yy"_L1;
356 else
357 result += "yyyy"_L1;
358 break;
359 case 'L': // Standalone Month (1..5): 4 = long, 3 = short, 1,2 = number, 5 = narrow
360 case 'M': // Month (1..5): 4 = long, 3 = short, 1,2 = number, 5 = narrow
361 // Qt only supports long, short and number, use short for narrow
362 if (repeat == 5)
363 result += "MMM"_L1;
364 else
365 result += QString(repeat, u'M');
366 break;
367 case 'd': // Day of Month (1,2): 1,2 padded number
368 result += QString(repeat, c);
369 break;
370 case 'c': // Standalone version of 'e'
371 case 'e': // Local Day of Week (1..6): 4 = long, 3 = short, 5,6 = narrow, 1,2 padded number
372 // "Local" only affects numeric form: depends on locale's start-day of the week.
373 case 'E': // Day of Week (1..6): 4 = long, 1..3 = short, 5,6 = narrow
374 // Qt only supports long, short: use short for narrow and padded number.
375 if (repeat == 4)
376 result += "dddd"_L1;
377 else
378 result += "ddd"_L1;
379 break;
380 case 'a': // AM/PM (1..n): Qt supports no distinctions
381 case 'b': // Like a, but also distinguishing noon, midnight (ignore difference).
382 case 'B': // Flexible day period (at night, &c.)
383 // Translate to Qt AM/PM, using locale-appropriate case:
384 result += "Ap"_L1;
385 break;
386 case 'h': // Hour [1..12] (1,2): 1,2 = padded number
387 case 'K': // Hour [0..11] (1,2): 1,2 = padded number
388 result += QString(repeat, 'h'_L1);
389 break;
390 case 'H': // Hour [0..23] (1,2): 1,2 = padded number
391 case 'k': // Hour [1..24] (1,2): 1,2 = padded number
392 // Qt H is 0..23 hour
393 result += QString(repeat, 'H'_L1);
394 break;
395 case 'm': // Minutes (1,2): 1,2 = padded number
396 case 's': // Seconds (1,2): 1,2 = padded number
397 result += QString(repeat, c);
398 break;
399 case 'S': // Fractional second (1..n): 1..n = truncates to decimal places
400 // Qt uses msecs either unpadded or padded to 3 places
401 if (repeat < 3)
402 result += u'z';
403 else
404 result += "zzz"_L1;
405 break;
406 case 'O': // Time Zone (1, 4)
407 result += u't';
408 break;
409 case 'v': // Time Zone (1, 4)
410 case 'V': // Time Zone (1..4)
411 result += "tttt"_L1;
412 break;
413 case 'x': // Time Zone (1..5)
414 case 'X': // Time Zone (1..5)
415 result += (repeat > 1 && (repeat & 1)) ? "ttt"_L1 : "tt"_L1;
416 break;
417 case 'z': // Time Zone (1..4)
418 case 'Z': // Time Zone (1..5)
419 result += repeat < 4 ? "tt"_L1 : repeat > 4 ? "ttt"_L1 : "t"_L1;
420 break;
421 default:
422 // a..z and A..Z are reserved for format codes, so any occurrence of these not
423 // already processed are not known and so unsupported formats to be ignored.
424 // All other chars are allowed as literals.
425 if (c < u'A' || c > u'z' || (c > u'Z' && c < u'a'))
426 result += QString(repeat, c);
427 break;
428 }
429
430 i += repeat;
431 }
432
433 return !result.isEmpty() ? QVariant::fromValue(result) : QVariant();
434}
435
436static QVariant getGroupingSizes()
437{
438 // It does not seem like you can directly query the group sizes from CFLocale as there
439 // is no key that corresponds to it, see:
440 // https://developer.apple.com/documentation/corefoundation/cflocalekey
441 // We have to create a number formatter for the locale and query the data from there.
442 // see: https://developer.apple.com/documentation/corefoundation/1390801-cfnumberformattercopyproperty
443 QLocaleData::GroupSizes sizes;
444 QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
445 QCFType<CFNumberFormatterRef> numberFormatter =
446 CFNumberFormatterCreate(NULL, locale, kCFNumberFormatterDecimalStyle);
447 CFTypeRef numTref =
448 CFNumberFormatterCopyProperty(numberFormatter, kCFNumberFormatterGroupingSize);
449 CFNumberRef num = static_cast<CFNumberRef>(numTref);
450 int value;
451 if (CFNumberGetValue(num, kCFNumberIntType, &value) && value > 0) {
452 sizes.least = value;
453 sizes.higher = value;
454 }
455 return QVariant::fromValue(sizes);
456}
457
458static QVariant getMacDateFormat(CFDateFormatterStyle style)
459{
460 QCFType<CFLocaleRef> l = CFLocaleCopyCurrent();
461 QCFType<CFDateFormatterRef> formatter = CFDateFormatterCreate(kCFAllocatorDefault,
462 l, style, kCFDateFormatterNoStyle);
463 return macToQtFormat(QString::fromCFString(CFDateFormatterGetFormat(formatter)));
464}
465
466static QVariant getMacTimeFormat(CFDateFormatterStyle style)
467{
468 QCFType<CFLocaleRef> l = CFLocaleCopyCurrent();
469 QCFType<CFDateFormatterRef> formatter = CFDateFormatterCreate(kCFAllocatorDefault,
470 l, kCFDateFormatterNoStyle, style);
471 return macToQtFormat(QString::fromCFString(CFDateFormatterGetFormat(formatter)));
472}
473
474static QVariant getCFLocaleValue(CFStringRef key)
475{
476 QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
477 CFTypeRef value = CFLocaleGetValue(locale, key);
478 if (!value)
479 return QVariant();
480 return QString::fromCFString(CFStringRef(static_cast<CFTypeRef>(value)));
481}
482
483static QVariant macMeasurementSystem()
484{
485 QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
486 CFStringRef system = static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleMeasurementSystem));
487 if (QString::fromCFString(system) == "Metric"_L1) {
488 return QLocale::MetricSystem;
489 } else {
490 return QLocale::ImperialSystem;
491 }
492}
493
494
496{
497 QCFType<CFCalendarRef> calendar = CFCalendarCopyCurrent();
498 quint8 day = static_cast<quint8>(CFCalendarGetFirstWeekday(calendar))-1;
499 if (day == 0)
500 day = 7;
501 return day;
502}
503
504static QVariant macCurrencySymbol(QLocale::CurrencySymbolFormat format)
505{
506 QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
507 switch (format) {
508 case QLocale::CurrencyIsoCode:
509 return QString::fromCFString(static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleCurrencyCode)));
510 case QLocale::CurrencySymbol:
511 return QString::fromCFString(static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleCurrencySymbol)));
512 case QLocale::CurrencyDisplayName: {
513 CFStringRef code = static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleCurrencyCode));
514 QCFType<CFStringRef> value = CFLocaleCopyDisplayNameForPropertyValue(locale, kCFLocaleCurrencyCode, code);
515 return QString::fromCFString(value);
516 }
517 default:
518 break;
519 }
520 return {};
521}
522
523#ifndef QT_NO_SYSTEMLOCALE
524static QVariant macFormatCurrency(const QSystemLocale::CurrencyToStringArgument &arg)
525{
526 QCFType<CFNumberRef> value;
527 switch (arg.value.metaType().id()) {
528 case QMetaType::Int:
529 case QMetaType::UInt: {
530 int v = arg.value.toInt();
531 value = CFNumberCreate(NULL, kCFNumberIntType, &v);
532 break;
533 }
534 case QMetaType::Double: {
535 double v = arg.value.toDouble();
536 value = CFNumberCreate(NULL, kCFNumberDoubleType, &v);
537 break;
538 }
539 case QMetaType::LongLong:
540 case QMetaType::ULongLong: {
541 qint64 v = arg.value.toLongLong();
542 value = CFNumberCreate(NULL, kCFNumberLongLongType, &v);
543 break;
544 }
545 default:
546 return {};
547 }
548
549 QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
550 QCFType<CFNumberFormatterRef> currencyFormatter =
551 CFNumberFormatterCreate(NULL, locale, kCFNumberFormatterCurrencyStyle);
552 if (!arg.symbol.isEmpty()) {
553 CFNumberFormatterSetProperty(currencyFormatter, kCFNumberFormatterCurrencySymbol,
554 arg.symbol.toCFString());
555 }
556 QCFType<CFStringRef> result = CFNumberFormatterCreateStringWithNumber(NULL, currencyFormatter, value);
557 return QString::fromCFString(result);
558}
559
560static QVariant macQuoteString(QSystemLocale::QueryType type, QStringView str)
561{
562 QString begin, end;
563 QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
564 switch (type) {
565 case QSystemLocale::StringToStandardQuotation:
566 begin = QString::fromCFString(static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleQuotationBeginDelimiterKey)));
567 end = QString::fromCFString(static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleQuotationEndDelimiterKey)));
568 return QString(begin % str % end);
569 case QSystemLocale::StringToAlternateQuotation:
570 begin = QString::fromCFString(static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleAlternateQuotationBeginDelimiterKey)));
571 end = QString::fromCFString(static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleAlternateQuotationEndDelimiterKey)));
572 return QString(begin % str % end);
573 default:
574 break;
575 }
576 return QVariant();
577}
578#endif //QT_NO_SYSTEMLOCALE
579
580#ifndef QT_NO_SYSTEMLOCALE
581
582QLocale QSystemLocale::fallbackLocale() const
583{
584 return QLocale(getMacLocaleName());
585}
586
587template <auto CodeToValueFunction>
588static QVariant getLocaleValue(CFStringRef key)
589{
590 if (auto code = getCFLocaleValue(key); !code.isNull()) {
591 // If an invalid locale is requested with -AppleLocale, the system APIs
592 // will report invalid or empty locale values back to us, which codeToLanguage()
593 // and friends will fail to parse, resulting in returning QLocale::Any{L/C/S}.
594 // If this is the case, we fall down and return a null-variant, which
595 // QLocale's updateSystemPrivate() will interpret to use fallback logic.
596 if (auto value = CodeToValueFunction(code.toString()))
597 return value;
598 }
599 return QVariant();
600}
601
602static QLocale::Language codeToLanguage(QStringView s)
603{
604 return QLocalePrivate::codeToLanguage(s);
605}
606
607QVariant QSystemLocale::query(QueryType type, QVariant &&in) const
608{
609 QMacAutoReleasePool pool;
610
611 switch(type) {
612 case LanguageId:
613 return getLocaleValue<codeToLanguage>(kCFLocaleLanguageCode);
614 case TerritoryId:
615 return getLocaleValue<QLocalePrivate::codeToTerritory>(kCFLocaleCountryCode);
616 case ScriptId:
617 return getLocaleValue<QLocalePrivate::codeToScript>(kCFLocaleScriptCode);
618 case DecimalPoint:
619 return getCFLocaleValue(kCFLocaleDecimalSeparator);
620 case Grouping:
621 return getGroupingSizes();
622 case GroupSeparator:
623 return getCFLocaleValue(kCFLocaleGroupingSeparator);
624 case DateFormatLong:
625 case DateFormatShort:
626 return getMacDateFormat(type == DateFormatShort
627 ? kCFDateFormatterShortStyle
628 : kCFDateFormatterLongStyle);
629 case TimeFormatLong:
630 case TimeFormatShort:
631 return getMacTimeFormat(type == TimeFormatShort
632 ? kCFDateFormatterShortStyle
633 : kCFDateFormatterLongStyle);
634 case DayNameLong:
635 case DayNameShort:
636 case DayNameNarrow:
637 case StandaloneDayNameLong:
638 case StandaloneDayNameShort:
639 case StandaloneDayNameNarrow:
640 return macDayName(in.toInt(), type);
641 case MonthNameLong:
642 case MonthNameShort:
643 case MonthNameNarrow:
644 case StandaloneMonthNameLong:
645 case StandaloneMonthNameShort:
646 case StandaloneMonthNameNarrow:
647 return macMonthName(in.toInt(), type);
648 case DateToStringShort:
649 case DateToStringLong:
650 return macDateToString(in.toDate(), (type == DateToStringShort));
651 case TimeToStringShort:
652 case TimeToStringLong:
653 return macTimeToString(in.toTime(), (type == TimeToStringShort));
654
655 case NegativeSign:
656 case PositiveSign:
657 break;
658 case ZeroDigit:
659 return macZeroDigit();
660
661 case MeasurementSystem:
662 return macMeasurementSystem();
663
664 case AMText:
665 case PMText: {
666 QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent();
667 QCFType<CFDateFormatterRef> formatter = CFDateFormatterCreate(NULL, locale, kCFDateFormatterLongStyle, kCFDateFormatterLongStyle);
668 QCFType<CFStringRef> value = static_cast<CFStringRef>(CFDateFormatterCopyProperty(formatter,
669 (type == AMText ? kCFDateFormatterAMSymbol : kCFDateFormatterPMSymbol)));
670 return QString::fromCFString(value);
671 }
672 case FirstDayOfWeek:
673 return QVariant(macFirstDayOfWeek());
674 case CurrencySymbol:
675 return macCurrencySymbol(QLocale::CurrencySymbolFormat(in.toUInt()));
676 case CurrencyToString:
677 return macFormatCurrency(in.value<CurrencyToStringArgument>());
678 case UILanguages: {
679 QStringList result;
680 QCFType<CFArrayRef> languages = CFLocaleCopyPreferredLanguages();
681 const CFIndex cnt = CFArrayGetCount(languages);
682 result.reserve(cnt);
683 for (CFIndex i = 0; i < cnt; ++i) {
684 const QString lang = QString::fromCFString(
685 static_cast<CFStringRef>(CFArrayGetValueAtIndex(languages, i)));
686 result.append(lang);
687 }
688 return QVariant(result);
689 }
690 case StringToStandardQuotation:
691 case StringToAlternateQuotation:
692 return macQuoteString(type, in.value<QStringView>());
693 default:
694 break;
695 }
696 return QVariant();
697}
698
699#endif // QT_NO_SYSTEMLOCALE
700
701#if !QT_CONFIG(icu)
702
703static QString localeConvertString(const QByteArray &localeID, const QString &str, bool *ok,
704 bool toLowerCase)
705{
706 QMacAutoReleasePool pool;
707 Q_ASSERT(ok);
708 NSString *localestring = [[NSString alloc] initWithData:localeID.toNSData()
709 encoding:NSUTF8StringEncoding];
710 NSLocale *locale = [NSLocale localeWithLocaleIdentifier:localestring];
711 if (!locale) {
712 *ok = false;
713 return QString();
714 }
715 *ok = true;
716 NSString *nsstring = str.toNSString();
717 if (toLowerCase)
718 nsstring = [nsstring lowercaseStringWithLocale:locale];
719 else
720 nsstring = [nsstring uppercaseStringWithLocale:locale];
721
722 return QString::fromNSString(nsstring);
723}
724
725QString QLocalePrivate::toLower(const QString &str, bool *ok) const
726{
727 return localeConvertString(bcp47Name('-'), str, ok, true);
728}
729
730QString QLocalePrivate::toUpper(const QString &str, bool *ok) const
731{
732 return localeConvertString(bcp47Name('-'), str, ok, false);
733}
734
735#endif
736
737QT_END_NAMESPACE
Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core")
static QVariant getMacTimeFormat(CFDateFormatterStyle style)
static QVariant macDateToString(QDate date, bool short_format)
static QVariant getCFLocaleValue(CFStringRef key)
static void printLocalizationInformation()
static QVariant macDayName(int day, QSystemLocale::QueryType type)
static QVariant macMeasurementSystem()
static QVariant macTimeToString(QTime time, bool short_format)
static QVariant macMonthName(int month, QSystemLocale::QueryType type)
static QVariant macCurrencySymbol(QLocale::CurrencySymbolFormat format)
static QVariant getGroupingSizes()
static QString zeroPad(QString &&number, qsizetype minDigits, const QString &zero)
static QVariant macToQtFormat(QStringView sys_fmt)
static QString fourDigitYear(int year, const QString &zero)
static QVariant macFormatCurrency(const QSystemLocale::CurrencyToStringArgument &arg)
static QString getMacLocaleName()
static QString trimTwoDigits(QString &&number)
static QVariant getMacDateFormat(CFDateFormatterStyle style)
static QString macDateToStringImpl(QDate date, CFDateFormatterStyle style)
static QString macZeroDigit()
static quint8 macFirstDayOfWeek()
Q_COREAPP_STARTUP_FUNCTION(printLocalizationInformation)
static QLocale::Language codeToLanguage(QStringView s)
static QVariant macQuoteString(QSystemLocale::QueryType type, QStringView str)
static QVariant getLocaleValue(CFStringRef key)