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());
103 qsizetype lastSlash = -1;
105 while (lastSlash < index) {
106 const qsizetype newToken = lastSlash + 1;
107 qsizetype 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();
124 for (; index >= 3; name.truncate(--index)) {
125 QTimeZone zone(name);
127 return R{zone, index};
135auto matchSystemName(QStringView text,
const QLocale &locale)
137 using QDTP = QDateTimePrivate;
139 qsizetype length = 0;
140 QDTP::DaylightStatus season = QDTP::UnknownDaylightTime;
141 operator
bool()
const noexcept {
return length > 0; }
145 for (
int i = 0; i < 2; ++i) {
146 const QString zone(qTzName(i));
147 if (zone.size() > best.length && text.startsWith(zone))
148 best = { zone.size(), i ? QDTP::DaylightTime : QDTP::StandardTime };
150#if QT_CONFIG(timezone)
152 const auto consider = [text, &best](QStringView zone, QDTP::DaylightStatus season) {
153 if (text.startsWith(zone)) {
155 constexpr qsizetype utcSignHourWidth = 6, withMinutesWidth = 9;
156 if (withMinutesWidth > best.length && zone.size() == utcSignHourWidth
157 && zone.startsWith(
"UTC"_L1)
158 && text.sliced(utcSignHourWidth).startsWith(
":00"_L1)) {
159 best = { withMinutesWidth, QDTP::UnknownDaylightTime };
160 }
else if (zone.size() > best.length) {
161 best = { zone.size(), season };
166
167
168 if (
const QTimeZone sys = QTimeZone::systemTimeZone(); sys.hasDaylightTime()) {
169 constexpr QTimeZone::TimeType types[] = {
170 QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime };
171 for (
const auto timeType : types) {
172 consider(sys.displayName(timeType, QTimeZone::ShortName, locale),
173 timeTypeToStatus(timeType));
176 consider(sys.displayName(QTimeZone::GenericTime, QTimeZone::ShortName, locale),
177 QDTP::UnknownDaylightTime);
186 qsizetype length = 0;
188 constexpr SizeOffset(qsizetype size,
int offset) : length(size), secondsEast(offset) {}
192QList<SizeOffset> matchIso8601(QStringView text, QtTemporalPattern::TemporalFieldFlags flags)
194 constexpr int MaxOffsetHours
195 = (
std::max)(-QTimeZone::MinUtcOffsetSecs, QTimeZone::MaxUtcOffsetSecs) / 3600;
196 QList<SizeOffset> matches;
197 using namespace QtTemporalPattern;
198 using namespace FieldGroup;
199 using Flag = TemporalFieldFlag;
201 if (flags.testFlag(Flag::AllowZSuffix) && text.startsWith(QLatin1Char(
'Z'))) {
202 matches.emplace_back(1, 0);
208 QStringView tail = text;
209 if (tail.startsWith(u"UTC")) {
210 if (!matchesFlagWithin(flags, Flag::AcceptUtcPrefix, UtcPrefixMask))
213 tail = tail.sliced(3);
214 }
else if (!matchesFlagWithin(flags, Flag::NeedNoUtcPrefix, UtcPrefixMask)) {
217 const bool negate = tail.startsWith(u'-');
218 if (!negate && !tail.startsWith(u'+'))
221 tail = tail.sliced(1);
223 const auto extend = [&matches, negate](qsizetype length,
int secondsEast) {
225 secondsEast = -secondsEast;
226 if (secondsEast >= QTimeZone::MinUtcOffsetSecs
227 && secondsEast <= QTimeZone::MaxUtcOffsetSecs) {
228 matches.emplace_back(length, secondsEast);
232 int hours = 0, minutes = 0, seconds = 0;
233 const bool zeroPad = flags.testFlag(Flag::ZeroPad);
234 constexpr TemporalFieldFlags WithColon = Flag::Verbal | Flag::Standalone;
235 qsizetype colon = tail.indexOf(u':');
239 if (!matchesFlagsWithin(flags, WithColon, FormMask)) {
242 tail = tail.first(colon);
245 }
else if (!matchesFlagWithin(flags, Flag::Numeric, FormMask)) {
249 tail = tail.first(2);
251 }
else if (colon < 0) {
254 tail = tail.first(2);
260 const bool hasColon = colon > 0 && colon <= 2;
262 qsizetype fieldUsed = qMin(2, hasColon ? colon : tail.size());
263 hours = tail.first(fieldUsed).toInt(&ok);
264 if (!ok || hours > MaxOffsetHours || (zeroPad && fieldUsed < 2)) {
267 hours = tail.first(1).toInt(&ok);
274 tail = tail.sliced(fieldUsed);
277 qsizetype fieldEnd[3] = { used, 0, 0 };
280 if ((flags & WidthMask) != QtTemporalPattern::TemporalFieldFlags{Flag::Narrow}) {
281 for (
int i = 0; i < 2 && fieldUsed && !tail.isEmpty(); ++i) {
282 QStringView digits = tail;
283 qsizetype sepLen = 0;
284 if (hasColon || fieldUsed == 1) {
285 if (fieldUsed != colon)
287 Q_ASSERT(tail.startsWith(u':'));
288 digits = digits.sliced(1);
291 int &field = i ? seconds : minutes;
292 colon = hasColon && !i ? digits.indexOf(u':') : -1;
295 if ((colon == -1 ? digits.size() : colon) < 2)
297 field = digits.first(2).toInt(&ok);
301 tail = tail.sliced(sepLen + fieldUsed);
302 used += sepLen + fieldUsed;
303 fieldEnd[fieldsSeen++] = used;
305 if (!i && !matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask))
310 switch (fieldsSeen) {
312 Q_ASSERT(matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask));
314 extend(fieldEnd[--fieldsSeen], (hours * 60 + minutes) * 60 + seconds);
317 if (!zeroPad || matchesFlagWithin(flags, Flag::Abbreviated, WidthMask))
318 extend(fieldEnd[fieldsSeen - 1], (hours * 60 + minutes) * 60);
322 if (zeroPad && !matchesFlagWithin(flags, Flag::Narrow, WidthMask))
324 extend(fieldEnd[--fieldsSeen], hours * 60 * 60);
334
335
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
367
368
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
402
403
404
405
406
407
409 QtTemporalPattern::TemporalFieldFlags flags)
411 using QDTP = QDateTimePrivate;
412 QList<ParsedZone> matches;
413 if (from < 0 || from >= text.size())
416 QStringView tail = text.sliced(from);
417 const auto includeMatch = [&matches, from, gmtStart = tail.startsWith(u"GMT")]
418 (qsizetype used, QTimeZone &&zone, QDTP::DaylightStatus type) {
419 Q_ASSERT(zone.isValid());
420 matches = addMatch(std::move(matches), {{from, from + used}, zone, type}, gmtStart);
423 using namespace QtTemporalPattern;
424 using namespace FieldGroup;
425 using Flag = TemporalFieldFlag;
427 if (matchesFlagWithin(flags, Flag::Iso8601, FieldGroup::LocalizationMask)) {
429 const auto matches = matchIso8601(tail, flags);
430 for (
const auto &match : matches) {
431 includeMatch(match.length,
432 QTimeZone::fromSecondsAheadOfUtc(match.secondsEast),
433 QDTP::UnknownDaylightTime);
438#if QT_CONFIG(timezone)
439 if (matchesFlagWithin(flags, Flag::LocalizedZone, FieldGroup::LocalizationMask)) {
440 const auto addPrefixIfMatch = [includeMatch] (QTimeZonePrivate::NamePrefixMatch &&prefix) {
442 includeMatch(prefix.nameLength, QTimeZone(prefix.ianaId),
443 timeTypeToStatus(prefix.timeType));
446 bool checkOffsetFallbacks =
false;
448 if (matchesFlagWithin(flags, Flag::Numeric, FormMask)
449 && matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask)) {
451 addPrefixIfMatch(QTimeZonePrivate::findOffsetPrefix(tail, locale, flags));
452 checkOffsetFallbacks =
true;
457 if (matchesFlagWithin(flags, Flag::Standalone, FormMask)
458 && matchesFlagWithin(flags, Flag::Short, WidthMask)) {
459 if (
auto match = matchIanaId(tail))
460 includeMatch(match.length, std::move(match.zone), QDTP::UnknownDaylightTime);
466 if (matchesFlagWithin(flags, Flag::Verbal, FormMask)
467 && matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask)) {
469 addPrefixIfMatch(QTimeZonePrivate::findLongNamePrefix(tail, locale));
473 checkOffsetFallbacks =
true;
476 if (checkOffsetFallbacks) {
477 addPrefixIfMatch(QTimeZonePrivate::findNarrowOffsetPrefix(tail, locale));
478 addPrefixIfMatch(QTimeZonePrivate::findLongUtcPrefix(tail));
483 if (flags.testFlag(Flag::LocalTimeName)) {
484 if (
const auto sys = matchSystemName(tail, locale))
485 includeMatch(sys.length, QTimeZone(QTimeZone::LocalTime), sys.season);
487 if (text.sliced(from).startsWith(u"LMT")) {
491 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)