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