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 qsizetype index = std::distance(text.cbegin(), std::find_if(text.cbegin(), text.cend(),
94 invalidZoneNameCharacter));
97 Q_ASSERT(index <= text.size());
102 constexpr qsizetype MaxFragmentLength = 20;
104 constexpr int MaxFragmentIndex = 5;
105 qsizetype lastSlash = -1;
107 while (lastSlash < index) {
108 const qsizetype newToken = lastSlash + 1;
109 qsizetype slash = text.indexOf(u'/', newToken);
112 else if (++fragment > MaxFragmentIndex)
114 if (slash - newToken > MaxFragmentLength)
115 index = newToken + MaxFragmentLength;
120 QByteArray name = text.first(index).toLatin1();
126 for (; index >= 3; name.truncate(--index)) {
127 QTimeZone zone(name);
129 return R{zone, index};
137auto matchSystemName(QStringView text,
const QLocale &locale)
139 using QDTP = QDateTimePrivate;
141 qsizetype length = 0;
142 QDTP::DaylightStatus season = QDTP::UnknownDaylightTime;
143 operator
bool()
const noexcept {
return length > 0; }
147 for (
int i = 0; i < 2; ++i) {
148 const QString zone(qTzName(i));
149 if (zone.size() > best.length && text.startsWith(zone))
150 best = { zone.size(), i ? QDTP::DaylightTime : QDTP::StandardTime };
152#if QT_CONFIG(timezone)
154 const auto consider = [text, &best](QStringView zone, QDTP::DaylightStatus season) {
155 if (text.startsWith(zone)) {
157 constexpr qsizetype utcSignHourWidth = 6, withMinutesWidth = 9;
158 if (withMinutesWidth > best.length && zone.size() == utcSignHourWidth
159 && zone.startsWith(
"UTC"_L1)
160 && text.sliced(utcSignHourWidth).startsWith(
":00"_L1)) {
161 best = { withMinutesWidth, QDTP::UnknownDaylightTime };
162 }
else if (zone.size() > best.length) {
163 best = { zone.size(), season };
168
169
170 if (
const QTimeZone sys = QTimeZone::systemTimeZone(); sys.hasDaylightTime()) {
171 constexpr QTimeZone::TimeType types[] = {
172 QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime };
173 for (
const auto timeType : types) {
174 consider(sys.displayName(timeType, QTimeZone::ShortName, locale),
175 timeTypeToStatus(timeType));
178 consider(sys.displayName(QTimeZone::GenericTime, QTimeZone::ShortName, locale),
179 QDTP::UnknownDaylightTime);
188 qsizetype length = 0;
190 constexpr SizeOffset(qsizetype size,
int offset) : length(size), secondsEast(offset) {}
194QList<SizeOffset> matchIso8601(QStringView text, QtTemporalPattern::TemporalFieldFlags flags)
196 constexpr int MaxOffsetHours
197 = (
std::max)(-QTimeZone::MinUtcOffsetSecs, QTimeZone::MaxUtcOffsetSecs) / 3600;
198 QList<SizeOffset> matches;
199 using namespace QtTemporalPattern;
200 using namespace FieldGroup;
201 using Flag = TemporalFieldFlag;
203 if (flags.testFlag(Flag::AllowZSuffix) && text.startsWith(QLatin1Char(
'Z'))) {
204 matches.emplace_back(1, 0);
210 QStringView tail = text;
211 if (tail.startsWith(u"UTC")) {
212 if (!matchesFlagWithin(flags, Flag::AcceptUtcPrefix, UtcPrefixMask))
215 tail = tail.sliced(3);
216 }
else if (!matchesFlagWithin(flags, Flag::NeedNoUtcPrefix, UtcPrefixMask)) {
219 const bool negate = tail.startsWith(u'-');
220 if (!negate && !tail.startsWith(u'+'))
223 tail = tail.sliced(1);
225 const auto extend = [&matches, negate](qsizetype length,
int secondsEast) {
227 secondsEast = -secondsEast;
228 if (secondsEast >= QTimeZone::MinUtcOffsetSecs
229 && secondsEast <= QTimeZone::MaxUtcOffsetSecs) {
230 matches.emplace_back(length, secondsEast);
234 int hours = 0, minutes = 0, seconds = 0;
235 const bool zeroPad = flags.testFlag(Flag::ZeroPad);
236 constexpr TemporalFieldFlags WithColon = Flag::Verbal | Flag::Standalone;
237 qsizetype colon = tail.indexOf(u':');
241 if (!matchesFlagsWithin(flags, WithColon, FormMask)) {
244 tail = tail.first(colon);
247 }
else if (!matchesFlagWithin(flags, Flag::Numeric, FormMask)) {
251 tail = tail.first(2);
253 }
else if (colon < 0) {
256 tail = tail.first(2);
262 const bool hasColon = colon > 0 && colon <= 2;
264 qsizetype fieldUsed = qMin(2, hasColon ? colon : tail.size());
265 hours = tail.first(fieldUsed).toInt(&ok);
266 if (!ok || hours > MaxOffsetHours || (zeroPad && fieldUsed < 2)) {
269 hours = tail.first(1).toInt(&ok);
276 tail = tail.sliced(fieldUsed);
279 qsizetype fieldEnd[3] = { used, 0, 0 };
282 if ((flags & WidthMask) != QtTemporalPattern::TemporalFieldFlags{Flag::Narrow}) {
283 for (
int i = 0; i < 2 && fieldUsed && !tail.isEmpty(); ++i) {
284 QStringView digits = tail;
285 qsizetype sepLen = 0;
286 if (hasColon || fieldUsed == 1) {
287 if (fieldUsed != colon)
289 Q_ASSERT(tail.startsWith(u':'));
290 digits = digits.sliced(1);
293 int &field = i ? seconds : minutes;
294 colon = hasColon && !i ? digits.indexOf(u':') : -1;
297 if ((colon == -1 ? digits.size() : colon) < 2)
299 field = digits.first(2).toInt(&ok);
303 tail = tail.sliced(sepLen + fieldUsed);
304 used += sepLen + fieldUsed;
305 fieldEnd[fieldsSeen++] = used;
307 if (!i && !matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask))
312 switch (fieldsSeen) {
314 Q_ASSERT(matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask));
316 extend(fieldEnd[--fieldsSeen], (hours * 60 + minutes) * 60 + seconds);
319 if (!zeroPad || matchesFlagWithin(flags, Flag::Abbreviated, WidthMask))
320 extend(fieldEnd[fieldsSeen - 1], (hours * 60 + minutes) * 60);
324 if (zeroPad && !matchesFlagWithin(flags, Flag::Narrow, WidthMask))
326 extend(fieldEnd[--fieldsSeen], hours * 60 * 60);
336
337
338
339
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
369
370
371
372
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
404
405
406
407
408
409
411 QtTemporalPattern::TemporalFieldFlags flags)
413 using QDTP = QDateTimePrivate;
414 QList<ParsedZone> matches;
415 if (from < 0 || from >= text.size())
418 QStringView tail = text.sliced(from);
419 const auto includeMatch = [&matches, from, gmtStart = tail.startsWith(u"GMT")]
420 (qsizetype used, QTimeZone &&zone, QDTP::DaylightStatus type) {
421 Q_ASSERT(zone.isValid());
422 matches = addMatch(std::move(matches), {{from, from + used}, zone, type}, gmtStart);
425 using namespace QtTemporalPattern;
426 using namespace FieldGroup;
427 using Flag = TemporalFieldFlag;
429 if (matchesFlagWithin(flags, Flag::Iso8601, FieldGroup::LocalizationMask)) {
431 const auto matches = matchIso8601(tail, flags);
432 for (
const auto &match : matches) {
433 includeMatch(match.length,
434 QTimeZone::fromSecondsAheadOfUtc(match.secondsEast),
435 QDTP::UnknownDaylightTime);
440#if QT_CONFIG(timezone)
441 if (matchesFlagWithin(flags, Flag::LocalizedZone, FieldGroup::LocalizationMask)) {
442 const auto addPrefixIfMatch = [includeMatch] (QTimeZonePrivate::NamePrefixMatch &&prefix) {
444 includeMatch(prefix.nameLength, QTimeZone(prefix.ianaId),
445 timeTypeToStatus(prefix.timeType));
448 bool checkOffsetFallbacks =
false;
450 if (matchesFlagWithin(flags, Flag::Numeric, FormMask)
451 && matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask)) {
453 addPrefixIfMatch(QTimeZonePrivate::findOffsetPrefix(tail, locale, flags));
454 checkOffsetFallbacks =
true;
459 if (matchesFlagWithin(flags, Flag::Standalone, FormMask)
460 && matchesFlagWithin(flags, Flag::Short, WidthMask)) {
461 if (
auto match = matchIanaId(tail))
462 includeMatch(match.length, std::move(match.zone), QDTP::UnknownDaylightTime);
468 if (matchesFlagWithin(flags, Flag::Verbal, FormMask)
469 && matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask)) {
471 addPrefixIfMatch(QTimeZonePrivate::findLongNamePrefix(tail, locale));
475 checkOffsetFallbacks =
true;
478 if (checkOffsetFallbacks) {
479 addPrefixIfMatch(QTimeZonePrivate::findNarrowOffsetPrefix(tail, locale));
480 addPrefixIfMatch(QTimeZonePrivate::findLongUtcPrefix(tail));
485 if (flags.testFlag(Flag::LocalTimeName)) {
486 if (
const auto sys = matchSystemName(tail, locale))
487 includeMatch(sys.length, QTimeZone(QTimeZone::LocalTime), sys.season);
489 if (text.sliced(from).startsWith(u"LMT")) {
493 includeMatch(3, QTimeZone(QTimeZone::LocalTime), QDTP::UnknownDaylightTime);
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)