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_unix.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 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
8#include "qdatetime.h"
9#include "qstringlist.h"
10#include "qvariant.h"
11#include "qreadwritelock.h"
12
14
15using namespace Qt::StringLiterals;
16
17#ifndef QT_NO_SYSTEMLOCALE
18namespace {
19struct QSystemLocaleData
20{
21 QSystemLocaleData()
22 : lc_numeric(QLocale::C)
23 ,lc_time(QLocale::C)
24 ,lc_monetary(QLocale::C)
25 ,lc_messages(QLocale::C)
26 {
27 initFromEnvironmentUnprotected();
28 }
29
30 void readEnvironment();
31
32 QReadWriteLock lock;
33
34 QLocale lc_numeric;
35 QLocale lc_time;
36 QLocale lc_monetary;
37 QLocale lc_messages;
38 QByteArray lc_messages_var;
39 QByteArray lc_measurement_var;
40 QByteArray lc_collate_var;
41 QStringList uiLanguages;
42
43private:
44 void initFromEnvironmentUnprotected();
45};
46
47void QSystemLocaleData::readEnvironment()
48{
49 QWriteLocker locker(&lock);
50 initFromEnvironmentUnprotected();
51}
52
53void QSystemLocaleData::initFromEnvironmentUnprotected()
54{
55 // See https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_02
56 // for the semantics of each of these:
57 QByteArray all = qgetenv("LC_ALL");
58 QByteArray numeric = all.isEmpty() ? qgetenv("LC_NUMERIC") : all;
59 QByteArray time = all.isEmpty() ? qgetenv("LC_TIME") : all;
60 QByteArray monetary = all.isEmpty() ? qgetenv("LC_MONETARY") : all;
61 lc_messages_var = all.isEmpty() ? qgetenv("LC_MESSAGES") : all;
62 lc_measurement_var = all.isEmpty() ? qgetenv("LC_MEASUREMENT") : all;
63 lc_collate_var = all.isEmpty() ? qgetenv("LC_COLLATE") : all;
64 QByteArray lang = qgetenv("LANG");
65 if (lang.isEmpty())
66 lang = QByteArray("C");
67 if (numeric.isEmpty())
68 numeric = lang;
69 if (time.isEmpty())
70 time = lang;
71 if (monetary.isEmpty())
72 monetary = lang;
73 if (lc_messages_var.isEmpty())
74 lc_messages_var = lang;
75 if (lc_measurement_var.isEmpty())
76 lc_measurement_var = lang;
77 if (lc_collate_var.isEmpty())
78 lc_collate_var = lang;
79 lc_numeric = QLocale(QString::fromLatin1(numeric));
80 lc_time = QLocale(QString::fromLatin1(time));
81 lc_monetary = QLocale(QString::fromLatin1(monetary));
82 lc_messages = QLocale(QString::fromLatin1(lc_messages_var));
83}
84
85Q_GLOBAL_STATIC(QSystemLocaleData, qSystemLocaleData)
86
87} // unnamed namespace
88
89static bool contradicts(QStringView maybe, const QString &known)
90{
91 if (maybe.isEmpty())
92 return false;
93
94 /*
95 If \a known (our current best shot at deciding which language to use)
96 provides more information (e.g. script, country) than \a maybe (a
97 candidate to replace \a known) and \a maybe agrees with \a known in what
98 it does provide, we keep \a known; this happens when \a maybe comes from
99 LANGUAGE (usually a simple language code) and LANG includes script and/or
100 country. A textual comparison won't do because, for example, bn (Bengali)
101 isn't a prefix of ben_IN, but the latter is a refinement of the former.
102 (Meanwhile, bn is a prefix of bnt, Bantu; and a prefix of ben is be,
103 Belarusian. There are many more such prefixings between two- and
104 three-letter codes.)
105 */
106 QLocaleId knownId = QLocaleId::fromName(known);
107 QLocaleId maybeId = QLocaleId::fromName(maybe);
108 return !(maybeId.acceptLanguage(knownId.language_id) && maybeId.acceptScriptTerritory(knownId));
109}
110
111QLocale QSystemLocale::fallbackLocale() const
112{
113 // See man 7 locale for precedence - LC_ALL beats LC_MESSAGES beats LANG:
114 QString lang = qEnvironmentVariable("LC_ALL");
115 if (lang.isEmpty())
116 lang = qEnvironmentVariable("LC_MESSAGES");
117 if (lang.isEmpty())
118 lang = qEnvironmentVariable("LANG");
119 // if the locale is the "C" locale, then we can return the language we found here:
120 if (lang.isEmpty() || lang == "C"_L1 || lang == "POSIX"_L1)
121 return QLocale(lang);
122
123 // ... otherwise, if the first part of LANGUAGE says more than or
124 // contradicts what we have, use that:
125 {
126 QString language = qEnvironmentVariable("LANGUAGE");
127 // We only look at the first entry:
128 if (const auto colon = language.indexOf(u':'); colon >= 0)
129 language.truncate(colon); // unshared QString; this is cheap
130 if (contradicts(language, lang))
131 return QLocale(language);
132 }
133
134 return QLocale(lang);
135}
136
137QVariant QSystemLocale::query(QueryType type, QVariant &&in) const
138{
139 QSystemLocaleData *d = qSystemLocaleData();
140 if (!d)
141 return QVariant();
142
143 if (type == LocaleChanged) {
144 d->readEnvironment();
145 return QVariant();
146 }
147
148 QReadLocker locker(&d->lock);
149
150 const QLocale &lc_numeric = d->lc_numeric;
151 const QLocale &lc_time = d->lc_time;
152 const QLocale &lc_monetary = d->lc_monetary;
153 const QLocale &lc_messages = d->lc_messages;
154
155 switch (type) {
156 case DecimalPoint:
157 return lc_numeric.decimalPoint();
158 case Grouping:
159 return QVariant::fromValue(lc_numeric.d->m_data->groupSizes());
160 case GroupSeparator:
161 return lc_numeric.groupSeparator();
162 case ZeroDigit:
163 return lc_numeric.zeroDigit();
164 case NegativeSign:
165 return lc_numeric.negativeSign();
166 case DateFormatLong:
167 return lc_time.dateFormat(QLocale::LongFormat);
168 case DateFormatShort:
169 return lc_time.dateFormat(QLocale::ShortFormat);
170 case TimeFormatLong:
171 return lc_time.timeFormat(QLocale::LongFormat);
172 case TimeFormatShort:
173 return lc_time.timeFormat(QLocale::ShortFormat);
174 case DayNameLong:
175 return lc_time.dayName(in.toInt(), QLocale::LongFormat);
176 case DayNameShort:
177 return lc_time.dayName(in.toInt(), QLocale::ShortFormat);
178 case DayNameNarrow:
179 return lc_time.dayName(in.toInt(), QLocale::NarrowFormat);
180 case StandaloneDayNameLong:
181 return lc_time.standaloneDayName(in.toInt(), QLocale::LongFormat);
182 case StandaloneDayNameShort:
183 return lc_time.standaloneDayName(in.toInt(), QLocale::ShortFormat);
184 case StandaloneDayNameNarrow:
185 return lc_time.standaloneDayName(in.toInt(), QLocale::NarrowFormat);
186 case MonthNameLong:
187 return lc_time.monthName(in.toInt(), QLocale::LongFormat);
188 case MonthNameShort:
189 return lc_time.monthName(in.toInt(), QLocale::ShortFormat);
190 case MonthNameNarrow:
191 return lc_time.monthName(in.toInt(), QLocale::NarrowFormat);
192 case StandaloneMonthNameLong:
193 return lc_time.standaloneMonthName(in.toInt(), QLocale::LongFormat);
194 case StandaloneMonthNameShort:
195 return lc_time.standaloneMonthName(in.toInt(), QLocale::ShortFormat);
196 case StandaloneMonthNameNarrow:
197 return lc_time.standaloneMonthName(in.toInt(), QLocale::NarrowFormat);
198 case DateToStringLong:
199 return lc_time.toString(in.toDate(), QLocale::LongFormat);
200 case DateToStringShort:
201 return lc_time.toString(in.toDate(), QLocale::ShortFormat);
202 case TimeToStringLong:
203 return lc_time.toString(in.toTime(), QLocale::LongFormat);
204 case TimeToStringShort:
205 return lc_time.toString(in.toTime(), QLocale::ShortFormat);
206 case DateTimeFormatLong:
207 return lc_time.dateTimeFormat(QLocale::LongFormat);
208 case DateTimeFormatShort:
209 return lc_time.dateTimeFormat(QLocale::ShortFormat);
210 case DateTimeToStringLong:
211 return lc_time.toString(in.toDateTime(), QLocale::LongFormat);
212 case DateTimeToStringShort:
213 return lc_time.toString(in.toDateTime(), QLocale::ShortFormat);
214 case PositiveSign:
215 return lc_numeric.positiveSign();
216 case AMText:
217 return lc_time.amText();
218 case PMText:
219 return lc_time.pmText();
220 case FirstDayOfWeek:
221 return lc_time.firstDayOfWeek();
222 case CurrencySymbol:
223 return lc_monetary.currencySymbol(QLocale::CurrencySymbolFormat(in.toUInt()));
224 case CurrencyToString: {
225 switch (in.userType()) {
226 case QMetaType::Int:
227 return lc_monetary.toCurrencyString(in.toInt());
228 case QMetaType::UInt:
229 return lc_monetary.toCurrencyString(in.toUInt());
230 case QMetaType::Double:
231 return lc_monetary.toCurrencyString(in.toDouble());
232 case QMetaType::LongLong:
233 return lc_monetary.toCurrencyString(in.toLongLong());
234 case QMetaType::ULongLong:
235 return lc_monetary.toCurrencyString(in.toULongLong());
236 default:
237 break;
238 }
239 return QString();
240 }
241 case MeasurementSystem: {
242 const QString meas_locale = QString::fromLatin1(d->lc_measurement_var);
243 if (meas_locale.compare("Metric"_L1, Qt::CaseInsensitive) == 0)
244 return QLocale::MetricSystem;
245 if (meas_locale.compare("Other"_L1, Qt::CaseInsensitive) == 0)
246 return QLocale::MetricSystem;
247 return QVariant((int)QLocale(meas_locale).measurementSystem());
248 }
249 case Collation:
250 return QString::fromLatin1(d->lc_collate_var);
251 case UILanguages: {
252 if (!d->uiLanguages.isEmpty())
253 return d->uiLanguages;
254 QString languages = QString::fromLatin1(qgetenv("LANGUAGE"));
255 QStringList lst;
256 if (languages.isEmpty())
257 lst.append(QString::fromLatin1(d->lc_messages_var));
258 else
259 lst = languages.split(u':');
260
261 for (const QString &e : std::as_const(lst)) {
262 QStringView language, script, territory;
263 if (qt_splitLocaleName(e, &language, &script, &territory)) {
264 QString joined = language.isEmpty() ? u"und"_s : language.toString();
265 if (!script.isEmpty())
266 joined += u'-' + script;
267 if (!territory.isEmpty())
268 joined += u'-' + territory;
269 d->uiLanguages.append(joined);
270 }
271 }
272 return d->uiLanguages.isEmpty() ? QVariant() : QVariant(d->uiLanguages);
273 }
274 case StringToStandardQuotation:
275 return lc_messages.quoteString(qvariant_cast<QStringView>(std::move(in)));
276 case StringToAlternateQuotation:
277 return lc_messages.quoteString(qvariant_cast<QStringView>(std::move(in)),
278 QLocale::AlternateQuotation);
279 case ListToSeparatedString:
280 return lc_messages.createSeparatedList(in.toStringList());
281 case LocaleChanged:
282 Q_UNREACHABLE(); // handled before the switch
283 case LanguageId:
284 case TerritoryId:
285 case Weekdays:
286 case ScriptId:
287 case NativeLanguageName:
288 case NativeTerritoryName:
289 break;
290 }
291 return QVariant();
292}
293#endif // QT_NO_SYSTEMLOCALE
294
295QT_END_NAMESPACE