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, [[maybe_unused]]
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[] =
"+-./:_";
69QDateTimePrivate::DaylightStatus timeTypeToStatus(QTimeZone::TimeType type) {
70 using QDTP = QDateTimePrivate;
72 case QTimeZone::GenericTime:
return QDTP::UnknownDaylightTime;
73 case QTimeZone::StandardTime:
return QDTP::StandardTime;
74 case QTimeZone::DaylightTime:
return QDTP::DaylightTime;
76 Q_UNREACHABLE_RETURN(QDTP::UnknownDaylightTime);
79auto matchIanaId(QStringView text)
84 operator
bool()
const noexcept {
return length > 0; }
88 const auto invalidZoneNameCharacter = [] (
const QChar &c) {
89 static constexpr auto matcher = QtPrivate::makeCharacterSetMatch<zoneNamePunctuation>();
90 const auto cu = c.unicode();
91 return cu >= 127u || !(matcher.matches(uchar(cu)) || c.isLetterOrNumber());
93 int index = std::distance(text.cbegin(),
94 std::find_if(text.cbegin(), text.cend(), invalidZoneNameCharacter));
97 Q_ASSERT(index <= text.size());
105 while (lastSlash < index) {
106 const int newToken = lastSlash + 1;
107 int slash = text.indexOf(u'/', newToken);
110 else if (++count > 5)
112 if (slash - newToken > 20)
113 index = newToken + 20;
118 QByteArray name = text.first(index).toLatin1();
123 for (; index > 3; name.truncate(--index)) {
124 QTimeZone zone(name);
126 return R{zone, index};
130 if (index == 3 && name ==
"GMT") {
131 QTimeZone zone(name);
133 return R{zone, index};
141auto matchSystemName(QStringView text,
const QLocale &locale)
143 using QDTP = QDateTimePrivate;
145 qsizetype length = 0;
146 QDTP::DaylightStatus season = QDTP::UnknownDaylightTime;
147 operator
bool()
const noexcept {
return length > 0; }
151 for (
int i = 0; i < 2; ++i) {
152 const QString zone(qTzName(i));
153 if (zone.size() > best.length && text.startsWith(zone))
154 best = { zone.size(), i ? QDTP::DaylightTime : QDTP::StandardTime };
156#if QT_CONFIG(timezone)
158 const auto consider = [text, &best](QStringView zone, QDTP::DaylightStatus season) {
159 if (text.startsWith(zone)) {
161 constexpr qsizetype utcSignHourWidth = 6, withMinutesWidth = 9;
162 if (withMinutesWidth > best.length && zone.size() == utcSignHourWidth
163 && zone.startsWith(
"UTC"_L1)
164 && text.sliced(utcSignHourWidth).startsWith(
":00"_L1)) {
165 best = { withMinutesWidth, QDTP::UnknownDaylightTime };
166 }
else if (zone.size() > best.length) {
167 best = { zone.size(), season };
172
173
174 if (
const QTimeZone sys = QTimeZone::systemTimeZone(); sys.hasDaylightTime()) {
175 constexpr QTimeZone::TimeType types[] = {
176 QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime };
177 for (
const auto timeType : types) {
178 consider(sys.displayName(timeType, QTimeZone::ShortName, locale),
179 timeTypeToStatus(timeType));
182 consider(sys.displayName(QTimeZone::GenericTime, QTimeZone::ShortName, locale),
183 QDTP::UnknownDaylightTime);
192 qsizetype length = 0;
194 constexpr SizeOffset(qsizetype size,
int offset) : length(size), secondsEast(offset) {}
198QList<SizeOffset> matchIso8601(QStringView text, QtTemporalPattern::TemporalFieldFlags flags)
200 constexpr int MaxOffsetHours
201 = (
std::max)(-QTimeZone::MinUtcOffsetSecs, QTimeZone::MaxUtcOffsetSecs) / 3600;
202 QList<SizeOffset> matches;
203 using namespace QtTemporalPattern;
204 using namespace FieldGroup;
205 using Flag = TemporalFieldFlag;
207 if (flags.testFlag(Flag::AllowZSuffix) && text.startsWith(QLatin1Char(
'Z'))) {
208 matches.emplace_back(1, 0);
214 QStringView tail = text;
215 if (tail.startsWith(u"UTC")) {
216 if (!matchesFlagWithin(flags, Flag::AcceptUtcPrefix, UtcPrefixMask))
219 tail = tail.sliced(3);
220 }
else if (!matchesFlagWithin(flags, Flag::NeedNoUtcPrefix, UtcPrefixMask)) {
223 const bool negate = tail.startsWith(u'-');
224 if (!negate && !tail.startsWith(u'+'))
227 tail = tail.sliced(1);
229 const auto extend = [&matches, negate](qsizetype length,
int secondsEast) {
231 secondsEast = -secondsEast;
232 if (secondsEast >= QTimeZone::MinUtcOffsetSecs
233 && secondsEast <= QTimeZone::MaxUtcOffsetSecs) {
234 matches.emplace_back(length, secondsEast);
238 int hours = 0, minutes = 0, seconds = 0;
239 const bool zeroPad = flags.testFlag(Flag::ZeroPad);
240 constexpr TemporalFieldFlags WithColon = Flag::Verbal | Flag::Standalone;
241 qsizetype colon = tail.indexOf(u':');
245 if (!matchesFlagsWithin(flags, WithColon, FormMask)) {
248 tail = tail.first(colon);
251 }
else if (!matchesFlagWithin(flags, Flag::Numeric, FormMask)) {
255 tail = tail.first(2);
257 }
else if (colon < 0) {
260 tail = tail.first(2);
266 const bool hasColon = colon > 0 && colon <= 2;
268 qsizetype fieldUsed = qMin(2, hasColon ? colon : tail.size());
269 hours = tail.first(fieldUsed).toInt(&ok);
270 if (!ok || hours > MaxOffsetHours || (zeroPad && fieldUsed < 2)) {
273 hours = tail.first(1).toInt(&ok);
280 tail = tail.sliced(fieldUsed);
283 qsizetype fieldEnd[3] = { used, 0, 0 };
286 if ((flags & WidthMask) != QtTemporalPattern::TemporalFieldFlags{Flag::Narrow}) {
287 for (
int i = 0; i < 2 && fieldUsed && !tail.isEmpty(); ++i) {
288 QStringView digits = tail;
289 qsizetype sepLen = 0;
290 if (hasColon || fieldUsed == 1) {
291 if (fieldUsed != colon)
293 Q_ASSERT(tail.startsWith(u':'));
294 digits = digits.sliced(1);
297 int &field = i ? seconds : minutes;
298 colon = hasColon && !i ? digits.indexOf(u':') : -1;
301 if ((colon == -1 ? digits.size() : colon) < 2)
303 field = digits.first(2).toInt(&ok);
307 tail = tail.sliced(sepLen + fieldUsed);
308 used += sepLen + fieldUsed;
309 fieldEnd[fieldsSeen++] = used;
311 if (!i && !matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask))
316 switch (fieldsSeen) {
318 Q_ASSERT(matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask));
320 extend(fieldEnd[--fieldsSeen], (hours * 60 + minutes) * 60 + seconds);
323 if (!zeroPad || matchesFlagWithin(flags, Flag::Abbreviated, WidthMask))
324 extend(fieldEnd[fieldsSeen - 1], (hours * 60 + minutes) * 60);
328 if (zeroPad && !matchesFlagWithin(flags, Flag::Narrow, WidthMask))
330 extend(fieldEnd[--fieldsSeen], hours * 60 * 60);
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
408
409
410
411
412
413
415 QtTemporalPattern::TemporalFieldFlags flags)
417 using QDTP = QDateTimePrivate;
418 QList<ParsedZone> matches;
419 if (from < 0 || from >= text.size())
422 QStringView tail = text.sliced(from);
423 const auto includeMatch = [&matches, from, gmtStart = tail.startsWith(u"GMT")]
424 (qsizetype used, QTimeZone &&zone, QDTP::DaylightStatus type) {
425 Q_ASSERT(zone.isValid());
426 matches = addMatch(std::move(matches), {{from, from + used}, zone, type}, gmtStart);
429 using namespace QtTemporalPattern;
430 using namespace FieldGroup;
431 using Flag = TemporalFieldFlag;
433 if (matchesFlagWithin(flags, Flag::Iso8601, FieldGroup::LocalizationMask)) {
435 const auto matches = matchIso8601(tail, flags);
436 for (
const auto &match : matches) {
437 includeMatch(match.length,
438 QTimeZone::fromSecondsAheadOfUtc(match.secondsEast),
439 QDTP::UnknownDaylightTime);
444#if QT_CONFIG(timezone)
445 if (matchesFlagWithin(flags, Flag::LocalizedZone, FieldGroup::LocalizationMask)) {
446 const auto addPrefixIfMatch = [includeMatch] (QTimeZonePrivate::NamePrefixMatch &&prefix) {
448 includeMatch(prefix.nameLength, QTimeZone(prefix.ianaId),
449 timeTypeToStatus(prefix.timeType));
452 bool checkOffsetFallbacks =
false;
454 if (matchesFlagWithin(flags, Flag::Numeric, FormMask)
455 && matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask)) {
457 addPrefixIfMatch(QTimeZonePrivate::findOffsetPrefix(tail, locale, flags));
458 checkOffsetFallbacks =
true;
463 if (matchesFlagWithin(flags, Flag::Standalone, FormMask)
464 && matchesFlagWithin(flags, Flag::Short, WidthMask)) {
465 if (
auto match = matchIanaId(tail))
466 includeMatch(match.length, std::move(match.zone), QDTP::UnknownDaylightTime);
472 if (matchesFlagWithin(flags, Flag::Verbal, FormMask)
473 && matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask)) {
475 addPrefixIfMatch(QTimeZonePrivate::findLongNamePrefix(tail, locale));
479 checkOffsetFallbacks =
true;
482 if (checkOffsetFallbacks) {
483 addPrefixIfMatch(QTimeZonePrivate::findNarrowOffsetPrefix(tail, locale));
484 addPrefixIfMatch(QTimeZonePrivate::findLongUtcPrefix(tail));
489 if (flags.testFlag(Flag::LocalTimeName)) {
490 if (
const auto sys = matchSystemName(tail, locale))
491 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)