4#include "private/qtparsetimezone_p.h"
8#include "private/qlocale_p.h"
9#include <QtCore/qloggingcategory.h>
11#include "private/qtenvironmentvariables_p.h"
13#if QT_CONFIG(timezone)
14# include "private/qtimezoneprivate_p.h"
19using namespace Qt::StringLiterals;
23QList<QtParseTimeZone::ParsedZone>
24addMatch(QList<QtParseTimeZone::ParsedZone> &&matches,
25 QtParseTimeZone::ParsedZone &&match,
bool gmtStart)
29 using namespace QtParseTimeZone;
32 const auto isBetter = [
33#if QT_CONFIG(timezone)
37 newIsBackendGmt = gmtStart && match.size() == 3
38 && match.zone.timeSpec() == Qt::TimeZone && match.zone.id() ==
"GMT",
40 newAddr = &match] (
const ParsedZone &left,
const ParsedZone &right) {
41 Q_ASSERT(left.startIndex == right.startIndex);
42 if (left.endIndex > right.endIndex)
44 if (left.endIndex < right.endIndex)
48 if (left.zone.timeSpec() == Qt::LocalTime && right.zone.timeSpec() != Qt::LocalTime)
50 if (left.zone.timeSpec() != Qt::LocalTime && right.zone.timeSpec() == Qt::LocalTime)
52#if QT_CONFIG(timezone)
54 return right.zone.timeSpec() != Qt::TimeZone || right.zone.id() !=
"GMT";
56 return &right == newAddr;
58 const auto pos = std::upper_bound(matches.begin(), matches.end(), match, isBetter);
62 matches.insert(pos, match);
63 return std::move(matches);
66#if QT_CONFIG(timezone)
67constexpr char zoneNamePunctuation[] =
"+-./:_";
69auto matchIanaId(QStringView text)
74 operator
bool()
const noexcept {
return length > 0; }
78 const auto invalidZoneNameCharacter = [] (
const QChar &c) {
79 constexpr auto matcher = QtPrivate::makeCharacterSetMatch<zoneNamePunctuation>();
80 const auto cu = c.unicode();
81 return cu >= 127u || !(matcher.matches(uchar(cu)) || c.isLetterOrNumber());
83 int index = std::distance(text.cbegin(),
84 std::find_if(text.cbegin(), text.cend(), invalidZoneNameCharacter));
85 Q_ASSERT(index <= text.size());
93 while (lastSlash < index) {
94 const int newToken = lastSlash + 1;
95 int slash = text.indexOf(u'/', newToken);
100 if (slash - newToken > 20)
101 index = newToken + 20;
106 QByteArray name = text.first(index).toLatin1();
111 for (; index > 3; name.truncate(--index)) {
112 QTimeZone zone(name);
114 return R{zone, index};
118 if (index == 3 && name ==
"GMT") {
119 QTimeZone zone(name);
121 return R{zone, index};
129auto matchSystemName(QStringView text,
const QLocale &locale)
132 qsizetype length = 0;
133 QTimeZone::TimeType season = QTimeZone::GenericTime;
134 operator
bool()
const noexcept {
return length > 0; }
138 for (
int i = 0; i < 2; ++i) {
139 const QString zone(qTzName(i));
140 if (zone.size() > best.length && text.startsWith(zone))
141 best = { zone.size(), i ? QTimeZone::DaylightTime : QTimeZone::StandardTime };
143#if QT_CONFIG(timezone)
145 const auto consider = [text, &best](QStringView zone, QTimeZone::TimeType season) {
146 if (text.startsWith(zone)) {
148 constexpr qsizetype utcSignHourWidth = 6, withMinutesWidth = 9;
149 if (withMinutesWidth > best.length && zone.size() == utcSignHourWidth
150 && zone.startsWith(
"UTC"_L1)
151 && text.sliced(utcSignHourWidth).startsWith(
":00"_L1)) {
152 best = { withMinutesWidth, QTimeZone::GenericTime };
153 }
else if (zone.size() > best.length) {
154 best = { zone.size(), season };
159
160
161 if (
const QTimeZone sys = QTimeZone::systemTimeZone(); sys.hasDaylightTime()) {
162 constexpr QTimeZone::TimeType types[] = {
163 QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime };
164 for (
const auto timeType : types)
165 consider(sys.displayName(timeType, QTimeZone::ShortName, locale), timeType);
167 consider(sys.displayName(QTimeZone::GenericTime, QTimeZone::ShortName, locale),
168 QTimeZone::GenericTime);
177 qsizetype length = 0;
179 constexpr SizeOffset(qsizetype size,
int offset) : length(size), secondsEast(offset) {}
183QList<SizeOffset> matchIso8601(QStringView text, QtTemporalPattern::TemporalFieldFlags flags)
185 constexpr int MaxOffsetHours
186 = (
std::max)(-QTimeZone::MinUtcOffsetSecs, QTimeZone::MaxUtcOffsetSecs) / 3600;
187 QList<SizeOffset> matches;
188 using namespace QtTemporalPattern;
189 using namespace FieldGroup;
190 using Flag = TemporalFieldFlag;
192 if (flags.testFlag(Flag::AllowZSuffix) && text.startsWith(QLatin1Char(
'Z'))) {
193 matches.emplace_back(1, 0);
199 QStringView tail = text;
200 if (tail.startsWith(u"UTC")) {
201 if (!matchesFlagWithin(flags, Flag::AcceptUtcPrefix, UtcPrefixMask))
204 tail = tail.sliced(3);
205 }
else if (!matchesFlagWithin(flags, Flag::NeedNoUtcPrefix, UtcPrefixMask)) {
208 const bool negate = tail.startsWith(u'-');
209 if (!negate && !tail.startsWith(u'+'))
212 tail = tail.sliced(1);
214 const auto extend = [&matches, negate](qsizetype length,
int secondsEast) {
216 secondsEast = -secondsEast;
217 if (secondsEast >= QTimeZone::MinUtcOffsetSecs
218 && secondsEast <= QTimeZone::MaxUtcOffsetSecs) {
219 matches.emplace_back(length, secondsEast);
223 int hours = 0, minutes = 0, seconds = 0;
224 const bool zeroPad = flags.testFlag(Flag::ZeroPad);
225 constexpr TemporalFieldFlags WithColon = Flag::Verbal | Flag::Standalone;
226 qsizetype colon = tail.indexOf(u':');
230 if (!matchesFlagsWithin(flags, WithColon, FormMask)) {
233 tail = tail.first(colon);
236 }
else if (!matchesFlagWithin(flags, Flag::Numeric, FormMask)) {
240 tail = tail.first(2);
242 }
else if (colon < 0) {
245 tail = tail.first(2);
251 const bool hasColon = colon > 0 && colon <= 2;
253 qsizetype fieldUsed = qMin(2, hasColon ? colon : tail.size());
254 hours = tail.first(fieldUsed).toInt(&ok);
255 if (!ok || hours > MaxOffsetHours || (zeroPad && fieldUsed < 2)) {
258 hours = tail.first(1).toInt(&ok);
265 tail = tail.sliced(fieldUsed);
268 qsizetype fieldEnd[3] = { used, 0, 0 };
271 if ((flags & WidthMask) != QtTemporalPattern::TemporalFieldFlags{Flag::Narrow}) {
272 for (
int i = 0; i < 2 && fieldUsed && !tail.isEmpty(); ++i) {
273 QStringView digits = tail;
274 qsizetype sepLen = 0;
275 if (hasColon || fieldUsed == 1) {
276 if (fieldUsed != colon)
278 Q_ASSERT(tail.startsWith(u':'));
279 digits = digits.sliced(1);
282 int &field = i ? seconds : minutes;
283 colon = hasColon && !i ? digits.indexOf(u':') : -1;
286 if ((colon == -1 ? digits.size() : colon) < 2)
288 field = digits.first(2).toInt(&ok);
292 tail = tail.sliced(sepLen + fieldUsed);
293 used += sepLen + fieldUsed;
294 fieldEnd[fieldsSeen++] = used;
296 if (!i && !matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask))
301 switch (fieldsSeen) {
303 Q_ASSERT(matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask));
305 extend(fieldEnd[--fieldsSeen], (hours * 60 + minutes) * 60 + seconds);
308 if (!zeroPad || matchesFlagWithin(flags, Flag::Abbreviated, WidthMask))
309 extend(fieldEnd[fieldsSeen - 1], (hours * 60 + minutes) * 60);
313 if (zeroPad && !matchesFlagWithin(flags, Flag::Narrow, WidthMask))
315 extend(fieldEnd[--fieldsSeen], hours * 60 * 60);
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
393
394
395
396
397
398
400 QtTemporalPattern::TemporalFieldFlags flags)
402 QList<ParsedZone> matches;
403 if (from < 0 || from >= text.size())
406 QStringView tail = text.sliced(from);
407 const auto includeMatch = [&matches, from, gmtStart = tail.startsWith(u"GMT")]
408 (qsizetype used, QTimeZone &&zone, QTimeZone::TimeType type) {
409 Q_ASSERT(zone.isValid());
410 matches = addMatch(std::move(matches), {{from, from + used}, zone, type}, gmtStart);
413 using namespace QtTemporalPattern;
414 using namespace FieldGroup;
415 using Flag = TemporalFieldFlag;
417 if (matchesFlagWithin(flags, Flag::Iso8601, FieldGroup::LocalizationMask)) {
419 const auto matches = matchIso8601(tail, flags);
420 for (
const auto &match : matches) {
421 includeMatch(match.length,
422 QTimeZone::fromSecondsAheadOfUtc(match.secondsEast),
423 QTimeZone::GenericTime);
428#if QT_CONFIG(timezone)
429 if (matchesFlagWithin(flags, Flag::LocalizedZone, FieldGroup::LocalizationMask)) {
430 const auto addPrefixIfMatch = [includeMatch] (QTimeZonePrivate::NamePrefixMatch &&prefix) {
432 includeMatch(prefix.nameLength, QTimeZone(prefix.ianaId), prefix.timeType);
434 bool checkOffsetFallbacks =
false;
436 if (matchesFlagWithin(flags, Flag::Numeric, FormMask)
437 && matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask)) {
439 addPrefixIfMatch(QTimeZonePrivate::findOffsetPrefix(tail, locale, flags));
440 checkOffsetFallbacks =
true;
445 if (matchesFlagWithin(flags, Flag::Standalone, FormMask)
446 && matchesFlagWithin(flags, Flag::Short, WidthMask)) {
447 if (
auto match = matchIanaId(tail))
448 includeMatch(match.length, std::move(match.zone), QTimeZone::GenericTime);
454 if (matchesFlagWithin(flags, Flag::Verbal, FormMask)
455 && matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask)) {
457 addPrefixIfMatch(QTimeZonePrivate::findLongNamePrefix(tail, locale));
461 checkOffsetFallbacks =
true;
464 if (checkOffsetFallbacks) {
465 addPrefixIfMatch(QTimeZonePrivate::findNarrowOffsetPrefix(tail, locale));
466 addPrefixIfMatch(QTimeZonePrivate::findLongUtcPrefix(tail));
471 if (flags.testFlag(Flag::LocalTimeName)) {
472 if (
const auto sys = matchSystemName(tail, locale))
473 includeMatch(sys.length, QTimeZone(QTimeZone::LocalTime), sys.season);
Combined button and popup list for selecting options.
A toolset for parsing time zone identification strings.
QList< ParsedZone > prefix(QStringView text, const QLocale &locale, qsizetype from, QtTemporalPattern::TemporalFieldFlags flags)