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]] QStringView tail)
29 using namespace QtParseTimeZone;
32 const auto isBetter = [
33#if QT_CONFIG(timezone)
36 newAddr = &match] (
const ParsedZone &left,
const ParsedZone &right) {
37 Q_ASSERT(left.startIndex == right.startIndex);
38 if (left.endIndex > right.endIndex)
40 if (left.endIndex < right.endIndex)
45 const auto preferTruer = [](
bool goodL,
bool goodR) -> std::optional<
bool> {
50 const Qt::TimeSpec leftSpec = left.zone.timeSpec(), rightSpec = right.zone.timeSpec();
52 if (
const auto pref = preferTruer(QTimeZone::isUtcOrFixedOffset(leftSpec),
53 QTimeZone::isUtcOrFixedOffset(rightSpec))) {
58 if (
const auto pref = preferTruer(leftSpec == Qt::LocalTime, rightSpec == Qt::LocalTime))
60#if QT_CONFIG(timezone)
62 if (
const auto pref = preferTruer(leftSpec == Qt::TimeZone
63 && tail.startsWith(QLatin1String(left.zone.id())),
64 rightSpec == Qt::TimeZone
65 && tail.startsWith(QLatin1String(right.zone.id())))) {
70 return &right == newAddr;
72 const auto pos = std::upper_bound(matches.begin(), matches.end(), match, isBetter);
76 matches.insert(pos, match);
77 return std::move(matches);
80#if QT_CONFIG(timezone)
81constexpr char zoneNamePunctuation[] =
"+-./:_";
83QDateTimePrivate::DaylightStatus timeTypeToStatus(QTimeZone::TimeType type) {
84 using QDTP = QDateTimePrivate;
86 case QTimeZone::GenericTime:
return QDTP::UnknownDaylightTime;
87 case QTimeZone::StandardTime:
return QDTP::StandardTime;
88 case QTimeZone::DaylightTime:
return QDTP::DaylightTime;
90 Q_UNREACHABLE_RETURN(QDTP::UnknownDaylightTime);
93auto matchIanaId(QStringView text)
98 operator
bool()
const noexcept {
return length > 0; }
102 const auto invalidZoneNameCharacter = [] (
const QChar &c) {
103 static constexpr auto matcher = QtPrivate::makeCharacterSetMatch<zoneNamePunctuation>();
104 const auto cu = c.unicode();
105 return cu >= 127u || !(matcher.matches(uchar(cu)) || c.isLetterOrNumber());
107 qsizetype index = std::distance(text.cbegin(), std::find_if(text.cbegin(), text.cend(),
108 invalidZoneNameCharacter));
111 Q_ASSERT(index <= text.size());
112 text.truncate(index);
116 constexpr qsizetype MaxFragmentLength = 20;
118 constexpr int MaxFragmentIndex = 5;
119 qsizetype lastSlash = -1;
121 while (lastSlash < index) {
122 const qsizetype newToken = lastSlash + 1;
123 qsizetype slash = text.indexOf(u'/', newToken);
126 else if (++fragment > MaxFragmentIndex)
128 if (slash - newToken > MaxFragmentLength)
129 index = newToken + MaxFragmentLength;
134 QByteArray name = text.first(index).toLatin1();
140 for (; index >= 3; name.truncate(--index)) {
141 QTimeZone zone(name);
143 return R{zone, index};
151auto matchSystemName(QStringView text,
const QLocale &locale)
153 using QDTP = QDateTimePrivate;
155 qsizetype length = 0;
156 QDTP::DaylightStatus season = QDTP::UnknownDaylightTime;
157 operator
bool()
const noexcept {
return length > 0; }
161 for (
int i = 0; i < 2; ++i) {
162 const QString zone(qTzName(i));
163 if (zone.size() > best.length && text.startsWith(zone))
164 best = { zone.size(), i ? QDTP::DaylightTime : QDTP::StandardTime };
166#if QT_CONFIG(timezone)
168 const auto consider = [text, &best](QStringView zone, QDTP::DaylightStatus season) {
169 if (text.startsWith(zone)) {
171 constexpr qsizetype utcSignHourWidth = 6, withMinutesWidth = 9;
172 if (withMinutesWidth > best.length && zone.size() == utcSignHourWidth
173 && zone.startsWith(
"UTC"_L1)
174 && text.sliced(utcSignHourWidth).startsWith(
":00"_L1)) {
175 best = { withMinutesWidth, QDTP::UnknownDaylightTime };
176 }
else if (zone.size() > best.length) {
177 best = { zone.size(), season };
182
183
184 if (
const QTimeZone sys = QTimeZone::systemTimeZone(); sys.hasDaylightTime()) {
185 constexpr QTimeZone::TimeType types[] = {
186 QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime };
187 for (
const auto timeType : types) {
188 consider(sys.displayName(timeType, QTimeZone::ShortName, locale),
189 timeTypeToStatus(timeType));
192 consider(sys.displayName(QTimeZone::GenericTime, QTimeZone::ShortName, locale),
193 QDTP::UnknownDaylightTime);
202 qsizetype length = 0;
204 constexpr SizeOffset(qsizetype size,
int offset) : length(size), secondsEast(offset) {}
208QList<SizeOffset> matchIso8601(QStringView text, QtTemporalPattern::TemporalFieldFlags flags)
210 constexpr int MaxOffsetHours
211 = (
std::max)(-QTimeZone::MinUtcOffsetSecs, QTimeZone::MaxUtcOffsetSecs) / 3600;
212 QList<SizeOffset> matches;
213 using namespace QtTemporalPattern;
214 using namespace FieldGroup;
215 using Flag = TemporalFieldFlag;
217 if (flags.testFlag(Flag::AllowZSuffix) && text.startsWith(QLatin1Char(
'Z'))) {
218 matches.emplace_back(1, 0);
224 QStringView tail = text;
225 if (tail.startsWith(u"UTC")) {
226 if (!matchesFlagWithin(flags, Flag::AcceptUtcPrefix, UtcPrefixMask))
229 tail = tail.sliced(3);
230 }
else if (!matchesFlagWithin(flags, Flag::NeedNoUtcPrefix, UtcPrefixMask)) {
233 const bool negate = tail.startsWith(u'-');
234 if (!negate && !tail.startsWith(u'+')) {
236 matches.emplace_back(used, 0);
240 tail = tail.sliced(1);
245 const auto extend = [&matches, negate](qsizetype length,
int secondsEast) {
247 secondsEast = -secondsEast;
248 if (secondsEast >= QTimeZone::MinUtcOffsetSecs
249 && secondsEast <= QTimeZone::MaxUtcOffsetSecs) {
250 matches.emplace_back(length, secondsEast);
254 int hours = 0, minutes = 0, seconds = 0;
255 const bool zeroPad = flags.testFlag(Flag::ZeroPad);
256 constexpr TemporalFieldFlags WithColon = Flag::Verbal | Flag::Standalone;
257 qsizetype colon = tail.indexOf(u':');
261 if (!matchesFlagsWithin(flags, WithColon, FormMask)) {
264 tail = tail.first(colon);
267 }
else if (!matchesFlagWithin(flags, Flag::Numeric, FormMask)) {
271 tail = tail.first(2);
273 }
else if (colon < 0) {
276 tail = tail.first(2);
282 const bool hasColon = colon > 0 && colon <= 2;
284 qsizetype fieldUsed = qMin(2, hasColon ? colon : tail.size());
285 hours = tail.first(fieldUsed).toInt(&ok);
286 if (!ok || hours > MaxOffsetHours || (zeroPad && fieldUsed < 2)) {
289 hours = tail.first(1).toInt(&ok);
296 tail = tail.sliced(fieldUsed);
299 qsizetype fieldEnd[3] = { used, 0, 0 };
302 if ((flags & WidthMask) != QtTemporalPattern::TemporalFieldFlags{Flag::Narrow}) {
303 for (
int i = 0; i < 2 && fieldUsed && !tail.isEmpty(); ++i) {
304 QStringView digits = tail;
305 qsizetype sepLen = 0;
306 if (hasColon || fieldUsed == 1) {
307 if (fieldUsed != colon)
309 Q_ASSERT(tail.startsWith(u':'));
310 digits = digits.sliced(1);
313 int &field = i ? seconds : minutes;
314 colon = hasColon && !i ? digits.indexOf(u':') : -1;
317 if ((colon == -1 ? digits.size() : colon) < 2)
319 field = digits.first(2).toInt(&ok);
323 tail = tail.sliced(sepLen + fieldUsed);
324 used += sepLen + fieldUsed;
325 fieldEnd[fieldsSeen++] = used;
327 if (!i && !matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask))
332 switch (fieldsSeen) {
334 Q_ASSERT(matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask));
336 extend(fieldEnd[--fieldsSeen], (hours * 60 + minutes) * 60 + seconds);
339 if (!zeroPad || matchesFlagWithin(flags, Flag::Abbreviated, WidthMask))
340 extend(fieldEnd[fieldsSeen - 1], (hours * 60 + minutes) * 60);
344 if (zeroPad && !matchesFlagWithin(flags, Flag::Narrow, WidthMask))
346 extend(fieldEnd[--fieldsSeen], hours * 60 * 60);
356
357
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
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
424
425
426
427
428
429
431 QtTemporalPattern::TemporalFieldFlags flags)
433 using QDTP = QDateTimePrivate;
434 QList<ParsedZone> matches;
435 if (from < 0 || from >= text.size())
438 QStringView tail = text.sliced(from);
439 const auto includeMatch = [&matches, from, tail]
440 (qsizetype used, QTimeZone &&zone, QDTP::DaylightStatus type) {
441 Q_ASSERT(zone.isValid());
442 matches = addMatch(std::move(matches), {{from, from + used}, zone, type}, tail);
445 using namespace QtTemporalPattern;
446 using namespace FieldGroup;
447 using Flag = TemporalFieldFlag;
449 if (matchesFlagWithin(flags, Flag::Iso8601, FieldGroup::LocalizationMask)) {
451 const auto matches = matchIso8601(tail, flags);
452 for (
const auto &match : matches) {
453 includeMatch(match.length,
454 QTimeZone::fromSecondsAheadOfUtc(match.secondsEast),
455 QDTP::UnknownDaylightTime);
460#if QT_CONFIG(timezone)
461 if (matchesFlagWithin(flags, Flag::LocalizedZone, FieldGroup::LocalizationMask)) {
462 const auto addPrefixIfMatch = [includeMatch] (QTimeZonePrivate::NamePrefixMatch &&prefix) {
464 includeMatch(prefix.nameLength, QTimeZone(prefix.ianaId),
465 timeTypeToStatus(prefix.timeType));
468 bool checkOffsetFallbacks =
false;
470 if (matchesFlagWithin(flags, Flag::Numeric, FormMask)
471 && matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask)) {
473 addPrefixIfMatch(QTimeZonePrivate::findOffsetPrefix(tail, locale, flags));
474 checkOffsetFallbacks =
true;
479 if (matchesFlagWithin(flags, Flag::Standalone, FormMask)
480 && matchesFlagWithin(flags, Flag::Short, WidthMask)) {
481 if (
auto match = matchIanaId(tail))
482 includeMatch(match.length, std::move(match.zone), QDTP::UnknownDaylightTime);
488 if (matchesFlagWithin(flags, Flag::Verbal, FormMask)
489 && matchesFlagsWithin(flags, Flag::Wide | Flag::Short, WidthMask)) {
491 addPrefixIfMatch(QTimeZonePrivate::findLongNamePrefix(tail, locale));
495 checkOffsetFallbacks =
true;
498 if (checkOffsetFallbacks) {
499 addPrefixIfMatch(QTimeZonePrivate::findNarrowOffsetPrefix(tail, locale));
500 addPrefixIfMatch(QTimeZonePrivate::findLongUtcPrefix(tail));
505 if (flags.testFlag(Flag::LocalTimeName)) {
506 if (
const auto sys = matchSystemName(tail, locale))
507 includeMatch(sys.length, QTimeZone(QTimeZone::LocalTime), sys.season);
509 if (text.sliced(from).startsWith(u"LMT")) {
513 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)