1/**************************************************************************** 2** 3** Copyright (C) 2020 The Qt Company Ltd. 4** Contact: https://www.qt.io/licensing/ 5** 6** This file is part of the QtCore module of the Qt Toolkit. 7** 8** $QT_BEGIN_LICENSE:LGPL$ 9** Commercial License Usage 10** Licensees holding valid commercial Qt licenses may use this file in 11** accordance with the commercial license agreement provided with the 12** Software or, alternatively, in accordance with the terms contained in 13** a written agreement between you and The Qt Company. For licensing terms 14** and conditions see https://www.qt.io/terms-conditions. For further 15** information use the contact form at https://www.qt.io/contact-us. 16** 17** GNU Lesser General Public License Usage 18** Alternatively, this file may be used under the terms of the GNU Lesser 19** General Public License version 3 as published by the Free Software 20** Foundation and appearing in the file LICENSE.LGPL3 included in the 21** packaging of this file. Please review the following information to 22** ensure the GNU Lesser General Public License version 3 requirements 23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. 24** 25** GNU General Public License Usage 26** Alternatively, this file may be used under the terms of the GNU 27** General Public License version 2.0 or (at your option) the GNU General 28** Public license version 3 or any later version approved by the KDE Free 29** Qt Foundation. The licenses are as published by the Free Software 30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 31** included in the packaging of this file. Please review the following 32** information to ensure the GNU General Public License requirements will 33** be met: https://www.gnu.org/licenses/gpl-2.0.html and 34** https://www.gnu.org/licenses/gpl-3.0.html. 35** 36** $QT_END_LICENSE$ 37** 38****************************************************************************/ 39 40#include "qlocale_p.h" 41 42#include "qstringlist.h" 43#include "qvariant.h" 44#include "qdatetime.h" 45 46#ifdef Q_OS_DARWIN 47#include "private/qcore_mac_p.h" 48#include <CoreFoundation/CoreFoundation.h> 49#endif 50 51QT_BEGIN_NAMESPACE 52 53/****************************************************************************** 54** Wrappers for Mac locale system functions 55*/ 56 57static QByteArray envVarLocale() 58{ 59 static QByteArray lang = 0; 60#ifdef Q_OS_UNIX 61 lang = qgetenv("LC_ALL"); 62 if (lang.isEmpty()) 63 lang = qgetenv("LC_NUMERIC"); 64 if (lang.isEmpty()) 65#endif 66 lang = qgetenv("LANG"); 67 return lang; 68} 69 70static QString getMacLocaleName() 71{ 72 QString result = QString::fromLocal8Bit(envVarLocale()); 73 74 QString lang, script, cntry; 75 if (result.isEmpty() 76 || (result != QLatin1String("C") && !qt_splitLocaleName(result, lang, script, cntry))) { 77 QCFType<CFLocaleRef> l = CFLocaleCopyCurrent(); 78 CFStringRef locale = CFLocaleGetIdentifier(l); 79 result = QString::fromCFString(locale); 80 } 81 return result; 82} 83 84static QString macMonthName(int month, QSystemLocale::QueryType type) 85{ 86 month -= 1; 87 if (month < 0 || month > 11) 88 return QString(); 89 90 QCFType<CFDateFormatterRef> formatter 91 = CFDateFormatterCreate(0, QCFType<CFLocaleRef>(CFLocaleCopyCurrent()), 92 kCFDateFormatterNoStyle, kCFDateFormatterNoStyle); 93 94 CFDateFormatterKey formatterType; 95 switch (type) { 96 case QSystemLocale::MonthNameLong: 97 formatterType = kCFDateFormatterMonthSymbols; 98 break; 99 case QSystemLocale::MonthNameShort: 100 formatterType = kCFDateFormatterShortMonthSymbols; 101 break; 102 case QSystemLocale::StandaloneMonthNameLong: 103 formatterType = kCFDateFormatterStandaloneMonthSymbols; 104 break; 105 case QSystemLocale::StandaloneMonthNameShort: 106 formatterType = kCFDateFormatterShortStandaloneMonthSymbols; 107 break; 108 default: 109 qWarning("macMonthName: Unsupported query type %d", type); 110 return QString(); 111 } 112 QCFType<CFArrayRef> values 113 = static_cast<CFArrayRef>(CFDateFormatterCopyProperty(formatter, formatterType)); 114 115 if (values != 0) { 116 CFStringRef cfstring = static_cast<CFStringRef>(CFArrayGetValueAtIndex(values, month)); 117 return QString::fromCFString(cfstring); 118 } 119 return QString(); 120} 121 122static QString macDayName(int day, bool short_format) 123{ 124 if (day < 1 || day > 7) 125 return QString(); 126 127 QCFType<CFDateFormatterRef> formatter 128 = CFDateFormatterCreate(0, QCFType<CFLocaleRef>(CFLocaleCopyCurrent()), 129 kCFDateFormatterNoStyle, kCFDateFormatterNoStyle); 130 QCFType<CFArrayRef> values = static_cast<CFArrayRef>(CFDateFormatterCopyProperty(formatter, 131 short_format ? kCFDateFormatterShortWeekdaySymbols 132 : kCFDateFormatterWeekdaySymbols)); 133 if (values != 0) { 134 CFStringRef cfstring = static_cast<CFStringRef>(CFArrayGetValueAtIndex(values, day % 7)); 135 return QString::fromCFString(cfstring); 136 } 137 return QString(); 138} 139 140static QString macDateToString(QDate date, bool short_format) 141{ 142 QCFType<CFDateRef> myDate = QDateTime(date, QTime()).toCFDate(); 143 QCFType<CFLocaleRef> mylocale = CFLocaleCopyCurrent(); 144 CFDateFormatterStyle style = short_format ? kCFDateFormatterShortStyle : kCFDateFormatterLongStyle; 145 QCFType<CFDateFormatterRef> myFormatter 146 = CFDateFormatterCreate(kCFAllocatorDefault, 147 mylocale, style, 148 kCFDateFormatterNoStyle); 149 return QCFString(CFDateFormatterCreateStringWithDate(0, myFormatter, myDate)); 150} 151 152static QString macTimeToString(QTime time, bool short_format) 153{ 154 QCFType<CFDateRef> myDate = QDateTime(QDate::currentDate(), time).toCFDate(); 155 QCFType<CFLocaleRef> mylocale = CFLocaleCopyCurrent(); 156 CFDateFormatterStyle style = short_format ? kCFDateFormatterShortStyle : kCFDateFormatterLongStyle; 157 QCFType<CFDateFormatterRef> myFormatter = CFDateFormatterCreate(kCFAllocatorDefault, 158 mylocale, 159 kCFDateFormatterNoStyle, 160 style); 161 return QCFString(CFDateFormatterCreateStringWithDate(0, myFormatter, myDate)); 162} 163 164// Mac uses the Unicode CLDR format codes 165// http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table 166// See also qtbase/util/locale_database/dateconverter.py 167// Makes the assumption that input formats are always well formed and consecutive letters 168// never exceed the maximum for the format code. 169static QString macToQtFormat(QStringView sys_fmt) 170{ 171 QString result; 172 int i = 0; 173 174 while (i < sys_fmt.size()) { 175 if (sys_fmt.at(i).unicode() == '\'') { 176 QString text = qt_readEscapedFormatString(sys_fmt, &i); 177 if (text == QLatin1String("'")) 178 result += QLatin1String("''"); 179 else 180 result += QLatin1Char('\'') + text + QLatin1Char('\''); 181 continue; 182 } 183 184 QChar c = sys_fmt.at(i); 185 int repeat = qt_repeatCount(sys_fmt.mid(i)); 186 187 switch (c.unicode()) { 188 // Qt does not support the following options 189 case 'G': // Era (1..5): 4 = long, 1..3 = short, 5 = narrow 190 case 'Y': // Year of Week (1..n): 1..n = padded number 191 case 'U': // Cyclic Year Name (1..5): 4 = long, 1..3 = short, 5 = narrow 192 case 'Q': // Quarter (1..4): 4 = long, 3 = short, 1..2 = padded number 193 case 'q': // Standalone Quarter (1..4): 4 = long, 3 = short, 1..2 = padded number 194 case 'w': // Week of Year (1..2): 1..2 = padded number 195 case 'W': // Week of Month (1): 1 = number 196 case 'D': // Day of Year (1..3): 1..3 = padded number 197 case 'F': // Day of Week in Month (1): 1 = number 198 case 'g': // Modified Julian Day (1..n): 1..n = padded number 199 case 'A': // Milliseconds in Day (1..n): 1..n = padded number 200 break; 201 202 case 'y': // Year (1..n): 2 = short year, 1 & 3..n = padded number 203 case 'u': // Extended Year (1..n): 2 = short year, 1 & 3..n = padded number 204 // Qt only supports long (4) or short (2) year, use long for all others 205 if (repeat == 2) 206 result += QLatin1String("yy"); 207 else 208 result += QLatin1String("yyyy"); 209 break; 210 case 'M': // Month (1..5): 4 = long, 3 = short, 1..2 = number, 5 = narrow 211 case 'L': // Standalone Month (1..5): 4 = long, 3 = short, 1..2 = number, 5 = narrow 212 // Qt only supports long, short and number, use short for narrow 213 if (repeat == 5) 214 result += QLatin1String("MMM"); 215 else 216 result += QString(repeat, QLatin1Char('M')); 217 break; 218 case 'd': // Day of Month (1..2): 1..2 padded number 219 result += QString(repeat, c); 220 break; 221 case 'E': // Day of Week (1..6): 4 = long, 1..3 = short, 5..6 = narrow 222 // Qt only supports long, short and padded number, use short for narrow 223 if (repeat == 4) 224 result += QLatin1String("dddd"); 225 else 226 result += QLatin1String("ddd"); 227 break; 228 case 'e': // Local Day of Week (1..6): 4 = long, 3 = short, 5..6 = narrow, 1..2 padded number 229 case 'c': // Standalone Local Day of Week (1..6): 4 = long, 3 = short, 5..6 = narrow, 1..2 padded number 230 // Qt only supports long, short and padded number, use short for narrow 231 if (repeat >= 5) 232 result += QLatin1String("ddd"); 233 else 234 result += QString(repeat, QLatin1Char('d')); 235 break; 236 case 'a': // AM/PM (1): 1 = short 237 // Translate to Qt uppercase AM/PM 238 result += QLatin1String("AP"); 239 break; 240 case 'h': // Hour [1..12] (1..2): 1..2 = padded number 241 case 'K': // Hour [0..11] (1..2): 1..2 = padded number 242 case 'j': // Local Hour [12 or 24] (1..2): 1..2 = padded number 243 // Qt h is local hour 244 result += QString(repeat, QLatin1Char('h')); 245 break; 246 case 'H': // Hour [0..23] (1..2): 1..2 = padded number 247 case 'k': // Hour [1..24] (1..2): 1..2 = padded number 248 // Qt H is 0..23 hour 249 result += QString(repeat, QLatin1Char('H')); 250 break; 251 case 'm': // Minutes (1..2): 1..2 = padded number 252 case 's': // Seconds (1..2): 1..2 = padded number 253 result += QString(repeat, c); 254 break; 255 case 'S': // Fractional second (1..n): 1..n = truncates to decimal places 256 // Qt uses msecs either unpadded or padded to 3 places 257 if (repeat < 3) 258 result += QLatin1Char('z'); 259 else 260 result += QLatin1String("zzz"); 261 break; 262 case 'z': // Time Zone (1..4) 263 case 'Z': // Time Zone (1..5) 264 case 'O': // Time Zone (1, 4) 265 case 'v': // Time Zone (1, 4) 266 case 'V': // Time Zone (1..4) 267 case 'X': // Time Zone (1..5) 268 case 'x': // Time Zone (1..5) 269 result += QLatin1Char('t'); 270 break; 271 default: 272 // a..z and A..Z are reserved for format codes, so any occurrence of these not 273 // already processed are not known and so unsupported formats to be ignored. 274 // All other chars are allowed as literals. 275 if (c < QLatin1Char('A') || c > QLatin1Char('z') || 276 (c > QLatin1Char('Z') && c < QLatin1Char('a'))) { 277 result += QString(repeat, c); 278 } 279 break; 280 } 281 282 i += repeat; 283 } 284 285 return result; 286} 287 288QString getMacDateFormat(CFDateFormatterStyle style) 289{ 290 QCFType<CFLocaleRef> l = CFLocaleCopyCurrent(); 291 QCFType<CFDateFormatterRef> formatter = CFDateFormatterCreate(kCFAllocatorDefault, 292 l, style, kCFDateFormatterNoStyle); 293 return macToQtFormat(QString::fromCFString(CFDateFormatterGetFormat(formatter))); 294} 295 296static QString getMacTimeFormat(CFDateFormatterStyle style) 297{ 298 QCFType<CFLocaleRef> l = CFLocaleCopyCurrent(); 299 QCFType<CFDateFormatterRef> formatter = CFDateFormatterCreate(kCFAllocatorDefault, 300 l, kCFDateFormatterNoStyle, style); 301 return macToQtFormat(QString::fromCFString(CFDateFormatterGetFormat(formatter))); 302} 303 304static QVariant getCFLocaleValue(CFStringRef key) 305{ 306 QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent(); 307 CFTypeRef value = CFLocaleGetValue(locale, key); 308 if (!value) 309 return QVariant(); 310 return QString::fromCFString(CFStringRef(static_cast<CFTypeRef>(value))); 311} 312 313static QLocale::MeasurementSystem macMeasurementSystem() 314{ 315 QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent(); 316 CFStringRef system = static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleMeasurementSystem)); 317 if (QString::fromCFString(system) == QLatin1String("Metric")) { 318 return QLocale::MetricSystem; 319 } else { 320 return QLocale::ImperialSystem; 321 } 322} 323 324 325static quint8 macFirstDayOfWeek() 326{ 327 QCFType<CFCalendarRef> calendar = CFCalendarCopyCurrent(); 328 quint8 day = static_cast<quint8>(CFCalendarGetFirstWeekday(calendar))-1; 329 if (day == 0) 330 day = 7; 331 return day; 332} 333 334static QString macCurrencySymbol(QLocale::CurrencySymbolFormat format) 335{ 336 QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent(); 337 switch (format) { 338 case QLocale::CurrencyIsoCode: 339 return QString::fromCFString(static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleCurrencyCode))); 340 case QLocale::CurrencySymbol: 341 return QString::fromCFString(static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleCurrencySymbol))); 342 case QLocale::CurrencyDisplayName: { 343 CFStringRef code = static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleCurrencyCode)); 344 QCFType<CFStringRef> value = CFLocaleCopyDisplayNameForPropertyValue(locale, kCFLocaleCurrencyCode, code); 345 return QString::fromCFString(value); 346 } 347 default: 348 break; 349 } 350 return QString(); 351} 352 353static QString macZeroDigit() 354{ 355 QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent(); 356 QCFType<CFNumberFormatterRef> numberFormatter = 357 CFNumberFormatterCreate(nullptr, locale, kCFNumberFormatterNoStyle); 358 static const int zeroDigit = 0; 359 QCFType<CFStringRef> value = CFNumberFormatterCreateStringWithValue(nullptr, numberFormatter, 360 kCFNumberIntType, &zeroDigit); 361 return QString::fromCFString(value); 362} 363 364#ifndef QT_NO_SYSTEMLOCALE 365static QString macFormatCurrency(const QSystemLocale::CurrencyToStringArgument &arg) 366{ 367 QCFType<CFNumberRef> value; 368 switch (arg.value.type()) { 369 case QVariant::Int: 370 case QVariant::UInt: { 371 int v = arg.value.toInt(); 372 value = CFNumberCreate(NULL, kCFNumberIntType, &v); 373 break; 374 } 375 case QVariant::Double: { 376 double v = arg.value.toDouble(); 377 value = CFNumberCreate(NULL, kCFNumberDoubleType, &v); 378 break; 379 } 380 case QVariant::LongLong: 381 case QVariant::ULongLong: { 382 qint64 v = arg.value.toLongLong(); 383 value = CFNumberCreate(NULL, kCFNumberLongLongType, &v); 384 break; 385 } 386 default: 387 return QString(); 388 } 389 390 QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent(); 391 QCFType<CFNumberFormatterRef> currencyFormatter = 392 CFNumberFormatterCreate(NULL, locale, kCFNumberFormatterCurrencyStyle); 393 if (!arg.symbol.isEmpty()) { 394 CFNumberFormatterSetProperty(currencyFormatter, kCFNumberFormatterCurrencySymbol, 395 arg.symbol.toCFString()); 396 } 397 QCFType<CFStringRef> result = CFNumberFormatterCreateStringWithNumber(NULL, currencyFormatter, value); 398 return QString::fromCFString(result); 399} 400 401static QVariant macQuoteString(QSystemLocale::QueryType type, const QStringRef &str) 402{ 403 QString begin, end; 404 QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent(); 405 switch (type) { 406 case QSystemLocale::StringToStandardQuotation: 407 begin = QString::fromCFString(static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleQuotationBeginDelimiterKey))); 408 end = QString::fromCFString(static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleQuotationEndDelimiterKey))); 409 return QString(begin % str % end); 410 case QSystemLocale::StringToAlternateQuotation: 411 begin = QString::fromCFString(static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleAlternateQuotationBeginDelimiterKey))); 412 end = QString::fromCFString(static_cast<CFStringRef>(CFLocaleGetValue(locale, kCFLocaleAlternateQuotationEndDelimiterKey))); 413 return QString(begin % str % end); 414 default: 415 break; 416 } 417 return QVariant(); 418} 419#endif //QT_NO_SYSTEMLOCALE 420 421#ifndef QT_NO_SYSTEMLOCALE 422 423QLocale QSystemLocale::fallbackUiLocale() const 424{ 425 return QLocale(getMacLocaleName()); 426} 427 428QVariant QSystemLocale::query(QueryType type, QVariant in) const 429{ 430 QMacAutoReleasePool pool; 431 switch(type) { 432// case Name: 433// return getMacLocaleName(); 434 case DecimalPoint: 435 return getCFLocaleValue(kCFLocaleDecimalSeparator); 436 case GroupSeparator: 437 return getCFLocaleValue(kCFLocaleGroupingSeparator); 438 case DateFormatLong: 439 case DateFormatShort: 440 return getMacDateFormat(type == DateFormatShort 441 ? kCFDateFormatterShortStyle 442 : kCFDateFormatterLongStyle); 443 case TimeFormatLong: 444 case TimeFormatShort: 445 return getMacTimeFormat(type == TimeFormatShort 446 ? kCFDateFormatterShortStyle 447 : kCFDateFormatterLongStyle); 448 case DayNameLong: 449 case DayNameShort: 450 return macDayName(in.toInt(), (type == DayNameShort)); 451 case MonthNameLong: 452 case MonthNameShort: 453 case StandaloneMonthNameLong: 454 case StandaloneMonthNameShort: 455 return macMonthName(in.toInt(), type); 456 case DateToStringShort: 457 case DateToStringLong: 458 return macDateToString(in.toDate(), (type == DateToStringShort)); 459 case TimeToStringShort: 460 case TimeToStringLong: 461 return macTimeToString(in.toTime(), (type == TimeToStringShort)); 462 463 case NegativeSign: 464 case PositiveSign: 465 break; 466 case ZeroDigit: 467 return QVariant(macZeroDigit()); 468 469 case MeasurementSystem: 470 return QVariant(static_cast<int>(macMeasurementSystem())); 471 472 case AMText: 473 case PMText: { 474 QCFType<CFLocaleRef> locale = CFLocaleCopyCurrent(); 475 QCFType<CFDateFormatterRef> formatter = CFDateFormatterCreate(NULL, locale, kCFDateFormatterLongStyle, kCFDateFormatterLongStyle); 476 QCFType<CFStringRef> value = static_cast<CFStringRef>(CFDateFormatterCopyProperty(formatter, 477 (type == AMText ? kCFDateFormatterAMSymbol : kCFDateFormatterPMSymbol))); 478 return QString::fromCFString(value); 479 } 480 case FirstDayOfWeek: 481 return QVariant(macFirstDayOfWeek()); 482 case CurrencySymbol: 483 return QVariant(macCurrencySymbol(QLocale::CurrencySymbolFormat(in.toUInt()))); 484 case CurrencyToString: 485 return macFormatCurrency(in.value<QSystemLocale::CurrencyToStringArgument>()); 486 case UILanguages: { 487 QCFType<CFPropertyListRef> languages = CFPreferencesCopyValue( 488 CFSTR("AppleLanguages"), 489 kCFPreferencesAnyApplication, 490 kCFPreferencesCurrentUser, 491 kCFPreferencesAnyHost); 492 QStringList result; 493 if (!languages) 494 return QVariant(result); 495 496 CFTypeID typeId = CFGetTypeID(languages); 497 if (typeId == CFArrayGetTypeID()) { 498 const int cnt = CFArrayGetCount(languages.as<CFArrayRef>()); 499 result.reserve(cnt); 500 for (int i = 0; i < cnt; ++i) { 501 const QString lang = QString::fromCFString( 502 static_cast<CFStringRef>(CFArrayGetValueAtIndex(languages.as<CFArrayRef>(), i))); 503 result.append(lang); 504 } 505 } else if (typeId == CFStringGetTypeID()) { 506 result = QStringList(QString::fromCFString(languages.as<CFStringRef>())); 507 } else { 508 qWarning("QLocale::uiLanguages(): CFPreferencesCopyValue returned unhandled type \"%ls\"; please report to http://bugreports.qt.io", 509 qUtf16Printable(QString::fromCFString(CFCopyTypeIDDescription(typeId)))); 510 } 511 return QVariant(result); 512 } 513 case StringToStandardQuotation: 514 case StringToAlternateQuotation: 515 return macQuoteString(type, in.value<QStringRef>()); 516 default: 517 break; 518 } 519 return QVariant(); 520} 521 522#endif // QT_NO_SYSTEMLOCALE 523 524QT_END_NAMESPACE 525