8#if QT_CONFIG(timezone_locale)
9# include "qtimezonelocale_p.h"
13#include <QtCore/qbitarray.h>
14#include <qdatastream.h>
18#include <private/qcalendarmath_p.h>
19#include <private/qduplicatetracker_p.h>
20#include <private/qnumeric_p.h>
21#if QT_CONFIG(icu) || !QT_CONFIG(timezone_locale)
22# include <private/qstringiterator_p.h>
24#include <private/qtools_p.h>
29#include <emscripten/val.h>
34using namespace QtMiscUtils;
42 return less.windowsIdKey < more.windowsIdKey
43 || (less.windowsIdKey == more.windowsIdKey && less.territory < more.territory);
51 return less.windowsIdKey < more.windowsIdKey
52 || less.windowsId().compare(more.windowsId(), Qt::CaseInsensitive) < 0;
58 return entry.offsetFromUtc < offsetSeconds;
63 return entry.windowsIdKey < winIdKey;
68 return entry.aliasId().compare(aliasId, Qt::CaseInsensitive) < 0;
73 return entry.windowsId().compare(winId, Qt::CaseInsensitive) < 0;
78 return entry.windowsIdKey < winIdKey;
86 winId, earlierWindowsId);
88 return data->windowsIdKey;
99 if (Q_LIKELY(data.windowsIdKey == windowsIdKey))
100 return data.windowsId();
104 windowsIdKey, atLowerWindowsKey);
106 return data->windowsId();
116 windowsIdKey, zoneAtLowerWindowsKey);
120
121
123QTimeZonePrivate::QTimeZonePrivate()
127 Q_ASSERT(std::is_sorted(std::begin(zoneDataTable), std::end(zoneDataTable),
129 Q_ASSERT(std::is_sorted(std::begin(windowsDataTable), std::end(windowsDataTable),
133QTimeZonePrivate::~QTimeZonePrivate()
137bool QTimeZonePrivate::operator==(
const QTimeZonePrivate &other)
const
142 return (m_id == other.m_id);
145bool QTimeZonePrivate::operator!=(
const QTimeZonePrivate &other)
const
147 return !(*
this == other);
150bool QTimeZonePrivate::isValid()
const
152 return !m_id.isEmpty();
155QByteArray QTimeZonePrivate::id()
const
160QLocale::Territory QTimeZonePrivate::territory()
const
163 const QLatin1StringView sought(m_id.data(), m_id.size());
164 for (
const ZoneData &data : zoneDataTable) {
165 for (QLatin1StringView token : data.ids()) {
167 return QLocale::Territory(data.territory);
170 return QLocale::AnyTerritory;
173QString QTimeZonePrivate::comment()
const
178QString QTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch,
179 QTimeZone::NameType nameType,
180 const QLocale &locale)
const
182 const Data tran = data(atMSecsSinceEpoch);
183 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
184 if (nameType == QTimeZone::OffsetName && isAnglicLocale(locale))
185 return isoOffsetFormat(tran.offsetFromUtc);
186 if (nameType == QTimeZone::ShortName && isDataLocale(locale))
187 return tran.abbreviation;
189 QTimeZone::TimeType timeType
190 = tran.daylightTimeOffset != 0 ? QTimeZone::DaylightTime : QTimeZone::StandardTime;
191#if QT_CONFIG(timezone_locale)
192 return localeName(atMSecsSinceEpoch, tran.offsetFromUtc, timeType, nameType, locale);
194 return displayName(timeType, nameType, locale);
200QString QTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
201 QTimeZone::NameType nameType,
202 const QLocale &locale)
const
204 const Data tran = data(timeType);
205 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
206#if QT_CONFIG(timezone_locale)
207 return localeName(tran.atMSecsSinceEpoch, tran.offsetFromUtc, timeType, nameType, locale);
209 if (nameType == QTimeZone::OffsetName && isAnglicLocale(locale))
210 return isoOffsetFormat(tran.offsetFromUtc);
216QString QTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch)
const
218 if (QLocale() != QLocale::c()) {
219 const QString name = displayName(atMSecsSinceEpoch, QTimeZone::ShortName, QLocale());
223 return displayName(atMSecsSinceEpoch, QTimeZone::ShortName, QLocale::c());
226int QTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch)
const
228 const int std = standardTimeOffset(atMSecsSinceEpoch);
229 const int dst = daylightTimeOffset(atMSecsSinceEpoch);
230 const int bad = invalidSeconds();
231 return std == bad || dst == bad ? bad : std + dst;
234int QTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch)
const
236 Q_UNUSED(atMSecsSinceEpoch);
237 return invalidSeconds();
240int QTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch)
const
242 Q_UNUSED(atMSecsSinceEpoch);
243 return invalidSeconds();
246bool QTimeZonePrivate::hasDaylightTime()
const
251bool QTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch)
const
253 Q_UNUSED(atMSecsSinceEpoch);
257QTimeZonePrivate::Data QTimeZonePrivate::data(QTimeZone::TimeType timeType)
const
260 const auto validMatch = [timeType](
const Data &tran) {
261 return tran.atMSecsSinceEpoch != invalidMSecs()
262 && ((timeType == QTimeZone::DaylightTime) != (tran.daylightTimeOffset == 0));
266 const qint64 currentMSecs = QDateTime::currentMSecsSinceEpoch();
267 Data tran = data(currentMSecs);
268 if (validMatch(tran))
271 if (hasTransitions()) {
273 tran = nextTransition(currentMSecs);
274 if (validMatch(tran))
281 tran = previousTransition(currentMSecs + 1);
282 while (tran.atMSecsSinceEpoch != invalidMSecs()) {
283 tran = previousTransition(tran.atMSecsSinceEpoch);
284 if (validMatch(tran))
292
293
294
295
296
297
298
299
300
301bool QTimeZonePrivate::isDataLocale(
const QLocale &locale)
const
304 return locale == QLocale::system();
307QTimeZonePrivate::Data QTimeZonePrivate::data(qint64 forMSecsSinceEpoch)
const
309 Q_UNUSED(forMSecsSinceEpoch);
314QDateTimePrivate::ZoneState QTimeZonePrivate::stateAtZoneTime(
315 qint64 forLocalMSecs, QDateTimePrivate::TransitionOptions resolve)
const
317 auto dataToState = [](
const Data &d) {
318 return QDateTimePrivate::ZoneState(d.atMSecsSinceEpoch + d.offsetFromUtc * 1000,
320 d.daylightTimeOffset ? QDateTimePrivate::DaylightTime
321 : QDateTimePrivate::StandardTime);
325
326
327
328
329
330
331
332 std::integral_constant<qint64, 17 * 3600 * 1000> seventeenHoursInMSecs;
333 static_assert(-seventeenHoursInMSecs / 1000 < QTimeZone::MinUtcOffsetSecs
334 && seventeenHoursInMSecs / 1000 > QTimeZone::MaxUtcOffsetSecs);
337 const qint64 recent =
338 qSubOverflow(forLocalMSecs, seventeenHoursInMSecs, &millis) || millis < minMSecs()
339 ? minMSecs() : millis;
341 const qint64 imminent =
342 qAddOverflow(forLocalMSecs, seventeenHoursInMSecs, &millis)
343 ? maxMSecs() : millis;
345 Q_ASSERT(recent < imminent && seventeenHoursInMSecs < imminent - recent + 1);
347 const Data past = data(recent), future = data(imminent);
348 if (future.atMSecsSinceEpoch == invalidMSecs()
349 && past.atMSecsSinceEpoch == invalidMSecs()) {
352 return { forLocalMSecs };
355 if (Q_LIKELY(past.offsetFromUtc == future.offsetFromUtc
356 && past.standardTimeOffset == future.standardTimeOffset
358 && past.abbreviation == future.abbreviation)) {
360 data.atMSecsSinceEpoch = forLocalMSecs - future.offsetFromUtc * 1000;
361 return dataToState(data);
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 if (hasTransitions()) {
392
393
394
395
396
397
398
399
400
401
406 Q_ASSERT(forLocalMSecs < 0 ||
407 forLocalMSecs - tran.offsetFromUtc * 1000 >= tran.atMSecsSinceEpoch);
409 Data nextTran = nextTransition(tran.atMSecsSinceEpoch);
411
412
413
414
415
416
417
418 while (nextTran.atMSecsSinceEpoch != invalidMSecs()
419 && forLocalMSecs > nextTran.atMSecsSinceEpoch + nextTran.offsetFromUtc * 1000) {
420 Data newTran = nextTransition(nextTran.atMSecsSinceEpoch);
421 if (newTran.atMSecsSinceEpoch == invalidMSecs()
422 || newTran.atMSecsSinceEpoch + newTran.offsetFromUtc * 1000 > imminent) {
429 const qint64 nextStart = nextTran.atMSecsSinceEpoch;
432 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
434 Q_ASSERT(forLocalMSecs < 0
435 || forLocalMSecs - tran.offsetFromUtc * 1000 > tran.atMSecsSinceEpoch);
437 tran.atMSecsSinceEpoch = forLocalMSecs - tran.offsetFromUtc * 1000;
445 if (nextStart == invalidMSecs() && tran.offsetFromUtc == future.offsetFromUtc)
446 return dataToState(tran);
449 if (tran.atMSecsSinceEpoch != invalidMSecs() && nextStart != invalidMSecs()) {
451
452
453
454
455
456
457
458
459
461 nextTran.atMSecsSinceEpoch = forLocalMSecs - nextTran.offsetFromUtc * 1000;
463 bool fallBack =
false;
464 if (nextStart > nextTran.atMSecsSinceEpoch) {
466 if (nextStart > tran.atMSecsSinceEpoch)
467 return dataToState(tran);
469 Q_ASSERT(tran.offsetFromUtc < nextTran.offsetFromUtc);
471 }
else if (nextStart <= tran.atMSecsSinceEpoch) {
473 return dataToState(nextTran);
475 Q_ASSERT(nextTran.offsetFromUtc < tran.offsetFromUtc);
483 = resolve.testFlag(QDateTimePrivate::FlipForReverseDst)
484 && (fallBack ? !tran.daylightTimeOffset && nextTran.daylightTimeOffset
485 : tran.daylightTimeOffset && !nextTran.daylightTimeOffset);
488 if (resolve.testFlag(flipped
489 ? QDateTimePrivate::FoldUseBefore
490 : QDateTimePrivate::FoldUseAfter)) {
491 return dataToState(nextTran);
493 if (resolve.testFlag(flipped
494 ? QDateTimePrivate::FoldUseAfter
495 : QDateTimePrivate::FoldUseBefore)) {
496 return dataToState(tran);
500
501
502
503
504
505 std::swap(tran.atMSecsSinceEpoch, nextTran.atMSecsSinceEpoch);
506 if (resolve.testFlag(flipped
507 ? QDateTimePrivate::GapUseBefore
508 : QDateTimePrivate::GapUseAfter))
509 return dataToState(nextTran);
510 if (resolve.testFlag(flipped
511 ? QDateTimePrivate::GapUseAfter
512 : QDateTimePrivate::GapUseBefore))
513 return dataToState(tran);
516 return {forLocalMSecs};
523 qint64 utcEpochMSecs;
526 int early = past.offsetFromUtc;
527 int late = future.offsetFromUtc;
528 if (early == late || late == invalidSeconds()) {
529 if (early == invalidSeconds()
530 || qSubOverflow(forLocalMSecs, early * qint64(1000), &utcEpochMSecs)) {
531 return {forLocalMSecs};
535 const qint64 forEarly = forLocalMSecs - early * 1000;
536 const qint64 forLate = forLocalMSecs - late * 1000;
539 const bool earlyOk = offsetFromUtc(forEarly) == early;
540 const bool lateOk = offsetFromUtc(forLate) == late;
544 Q_ASSERT(early > late);
546 if (resolve.testFlag(QDateTimePrivate::FoldUseBefore))
547 utcEpochMSecs = forEarly;
548 else if (resolve.testFlag(QDateTimePrivate::FoldUseAfter))
549 utcEpochMSecs = forLate;
551 return {forLocalMSecs};
554 utcEpochMSecs = forEarly;
558 utcEpochMSecs = forLate;
561 Q_ASSERT(late > early);
562 const int dstStep = (late - early) * 1000;
563 if (resolve.testFlag(QDateTimePrivate::GapUseBefore))
564 utcEpochMSecs = forEarly - dstStep;
565 else if (resolve.testFlag(QDateTimePrivate::GapUseAfter))
566 utcEpochMSecs = forLate + dstStep;
568 return {forLocalMSecs};
572 return dataToState(data(utcEpochMSecs));
575bool QTimeZonePrivate::hasTransitions()
const
580QTimeZonePrivate::Data QTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch)
const
582 Q_UNUSED(afterMSecsSinceEpoch);
586QTimeZonePrivate::Data QTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch)
const
588 Q_UNUSED(beforeMSecsSinceEpoch);
592QTimeZonePrivate::DataList QTimeZonePrivate::transitions(qint64 fromMSecsSinceEpoch,
593 qint64 toMSecsSinceEpoch)
const
596 if (toMSecsSinceEpoch >= fromMSecsSinceEpoch) {
598 Data next = nextTransition(fromMSecsSinceEpoch - 1);
599 while (next.atMSecsSinceEpoch != invalidMSecs()
600 && next.atMSecsSinceEpoch <= toMSecsSinceEpoch) {
602 next = nextTransition(next.atMSecsSinceEpoch);
608QByteArray QTimeZonePrivate::systemTimeZoneId()
const
613template <
typename Pred>
622 name, earlierAliasId);
624 name = data->ianaId();
632 for (
const auto &data : aliasMappingTable) {
633 QByteArrayView alias = data.aliasId();
634 if (data.ianaId() == name && test(alias))
640QByteArrayView QTimeZonePrivate::availableAlias(QByteArrayView ianaId)
const
642 return aliasMatching(ianaId, [
this](QByteArrayView id) {
return isTimeZoneIdAvailable(id); });
645bool QTimeZonePrivate::isTimeZoneIdAvailable(QByteArrayView ianaId)
const
649 const QList<QByteArray> tzIds = availableTimeZoneIds();
650 return std::binary_search(tzIds.begin(), tzIds.end(), ianaId);
656 std::sort(desired.begin(), desired.end());
657 const auto newEnd =
std::unique(desired.begin(), desired.end());
658 const auto newSize =
std::distance(desired.begin(), newEnd);
660 result.reserve(qMin(all.size(), newSize));
661 std::set_intersection(all.begin(), all.end(), desired.cbegin(),
662 std::next(desired.cbegin(), newSize),
std::back_inserter(result));
666QList<QByteArrayView> QTimeZonePrivate::matchingTimeZoneIds(QLocale::Territory territory)
const
669 QList<QByteArrayView> regions;
670#if QT_CONFIG(timezone_locale) && !QT_CONFIG(icu)
671 regions = QtTimeZoneLocale::ianaIdsForTerritory(territory);
674 if (territory == QLocale::World) {
677 for (
const WindowsData &data : windowsDataTable)
678 regions << data.ianaId();
680 for (
const ZoneData &data : zoneDataTable) {
681 if (data.territory == territory) {
682 for (
auto l1 : data.ids())
683 regions << QByteArrayView(l1.data(), l1.size());
690QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(QLocale::Territory territory)
const
692 return selectAvailable(matchingTimeZoneIds(territory), availableTimeZoneIds());
695QList<QByteArrayView> QTimeZonePrivate::matchingTimeZoneIds(
int offsetFromUtc)
const
698 QList<QByteArrayView> offsets;
700 for (
const WindowsData &winData : windowsDataTable) {
701 if (winData.offsetFromUtc == offsetFromUtc) {
702 for (
auto data = zoneStartForWindowsId(winData.windowsIdKey);
703 data != std::end(zoneDataTable) && data->windowsIdKey == winData.windowsIdKey;
705 for (
auto l1 : data->ids())
706 offsets << QByteArrayView(l1.data(), l1.size());
713QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(
int offsetFromUtc)
const
715 return selectAvailable(matchingTimeZoneIds(offsetFromUtc), availableTimeZoneIds());
718QList<QByteArray> QTimeZonePrivate::uniqueSortedAliasPadded(QList<QByteArray> &&zoneIds)
721 const QList<QByteArray> source = zoneIds;
723 for (
const auto &name : source) {
724 const auto zone = aliasToIana(name);
725 if (!zone.isEmpty()) {
726 zoneIds << zone.toByteArray();
727 Q_ASSERT(aliasToIana(zone).isEmpty());
730 std::sort(zoneIds.begin(), zoneIds.end());
731 zoneIds.erase(std::unique(zoneIds.begin(), zoneIds.end()), zoneIds.end());
735QList<QByteArray> QTimeZonePrivate::padSortedWithAliases(QList<QByteArray> &&zoneIds)
738 const QList<QByteArray> source = zoneIds;
739 for (
const auto &name : source) {
740 const auto zone = aliasToIana(name);
741 const auto pos = std::lower_bound(zoneIds.begin(), zoneIds.end(), zone);
742 if (pos != zoneIds.end() && *pos != zone)
743 zoneIds.insert(pos, zone.toByteArray());
748#ifndef QT_NO_DATASTREAM
749void QTimeZonePrivate::serialize(QDataStream &ds)
const
751 ds << QString::fromUtf8(m_id);
757QTimeZone::OffsetData QTimeZonePrivate::invalidOffsetData()
759 return { QString(), QDateTime(),
760 invalidSeconds(), invalidSeconds(), invalidSeconds() };
763QTimeZone::OffsetData QTimeZonePrivate::toOffsetData(
const QTimeZonePrivate::Data &data)
765 if (data.atMSecsSinceEpoch == invalidMSecs())
766 return invalidOffsetData();
770 QDateTime::fromMSecsSinceEpoch(data.atMSecsSinceEpoch, QTimeZone::UTC),
771 data.offsetFromUtc, data.standardTimeOffset, data.daylightTimeOffset };
775bool QTimeZonePrivate::isValidId(QByteArrayView ianaId)
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
817 const int MinSectionLength = 1;
818#if defined(Q_OS_ANDROID) || QT_CONFIG(icu)
821 const int MaxSectionLength = 17;
823 const int MaxSectionLength = 14;
825 int sectionLength = 0;
826 for (
const char *it = ianaId.begin(), *
const end = ianaId.end(); it != end; ++it, ++sectionLength) {
829 if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength)
832 }
else if (ch ==
'-') {
833 if (sectionLength == 0)
835 }
else if (!isAsciiLower(ch)
846 if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength)
851QString QTimeZonePrivate::isoOffsetFormat(
int offsetFromUtc, QTimeZone::NameType mode)
853 if (mode == QTimeZone::ShortName && !offsetFromUtc)
857 if (offsetFromUtc < 0) {
859 offsetFromUtc = -offsetFromUtc;
861 const int secs = offsetFromUtc % 60;
862 const int mins = (offsetFromUtc / 60) % 60;
863 const int hour = offsetFromUtc / 3600;
864 QString result = QString::asprintf(
"UTC%c%02d", sign, hour);
865 if (mode != QTimeZone::ShortName || secs || mins)
866 result += QString::asprintf(
":%02d", mins);
867 if (mode == QTimeZone::LongName || secs)
868 result += QString::asprintf(
":%02d", secs);
872#if QT_CONFIG(icu) || !QT_CONFIG(timezone_locale)
873static QTimeZonePrivate::NamePrefixMatch
874findUtcOffsetPrefix(QStringView text,
const QLocale &locale)
878 qsizetype signLen = 0;
880 auto signStart = [&signLen, &sign, locale](QStringView str) {
881 QString signStr = locale.negativeSign();
882 if (str.startsWith(signStr)) {
884 signLen = signStr.size();
888 if (str.startsWith(u'\u2212')) {
893 signStr = locale.positiveSign();
894 if (str.startsWith(signStr)) {
896 signLen = signStr.size();
902 if (!((text.startsWith(u"UTC") || text.startsWith(u"GMT")) && signStart(text.sliced(3))))
905 QStringView offset = text.sliced(3 + signLen);
906 QStringIterator iter(offset);
907 qsizetype hourEnd = 0, hmMid = 0, minEnd = 0;
910 while (digits < 4 && iter.hasNext()) {
912 if (!QChar::isDigit(ch))
918 hourEnd = std::exchange(hmMid, std::exchange(minEnd, iter.index()));
923 QStringView hourStr, minStr;
925 minStr = offset.first(minEnd).sliced(hourEnd);
926 }
else if (digits < 3 && iter.hasNext() && QChar::isPunct(ch)) {
928 hmMid = iter.index();
930 while (mindig < 2 && iter.hasNext() && QChar::isDigit(iter.next())) {
932 minEnd = iter.index();
935 minStr = offset.first(minEnd).sliced(hmMid);
941 hourStr = offset.first(hourEnd);
944 uint hour = 0, minute = 0;
945 if (!hourStr.isEmpty())
946 hour = locale.toUInt(hourStr, &ok);
947 if (ok && !minStr.isEmpty()) {
948 minute = locale.toUInt(minStr, &ok);
950 if ((!ok || minute >= 60) && minEnd > hourEnd + minStr.size()) {
959 constexpr int MaxOffsetSeconds
960 = qMax(QTimeZone::MaxUtcOffsetSecs, -QTimeZone::MinUtcOffsetSecs);
961 if (!ok || (hour * 60 + minute) * 60 > MaxOffsetSeconds)
971 std::snprintf(buffer,
sizeof(buffer),
"UTC%c%02u:%02u", sign, hour, minute);
973 std::snprintf(buffer,
sizeof(buffer),
"UTC%c%02u", sign, hour);
975 return { QByteArray(buffer, qstrnlen(buffer,
sizeof(buffer))),
976 3 + signLen + minEnd,
977 QTimeZone::GenericTime };
980QTimeZonePrivate::NamePrefixMatch
981QTimeZonePrivate::findLongNamePrefix(QStringView text,
const QLocale &locale,
982 std::optional<qint64> atEpochMillis)
986 QTimeZonePrivate::NamePrefixMatch best = findUtcOffsetPrefix(text, locale);
988 const auto matchLength = [text](QStringView name) -> qsizetype {
989 qsizetype length = 0;
990 if (name.size() > 0 && text.startsWith(name, Qt::CaseInsensitive)) {
991 length = name.size();
993 while (!text.first(length).startsWith(name, Qt::CaseInsensitive)) {
995 Q_ASSERT(length <= text.size());
998 if (length == name.size()) {
999 while (length > 0 && text.first(length - 1).startsWith(name, Qt::CaseInsensitive))
1005 const auto when = atEpochMillis
1006 ? QDateTime::fromMSecsSinceEpoch(*atEpochMillis, QTimeZone::UTC)
1008 const auto typeFor = [when](QTimeZone zone) {
1009 if (when.isValid() && zone.isDaylightTime(when))
1010 return QTimeZone::DaylightTime;
1012 return QTimeZone::GenericTime;
1014 const auto tryZone = [&](
const QByteArray &iana) {
1015 bool matched =
false;
1016 constexpr QTimeZone::TimeType types[]
1017 = { QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime };
1018 QTimeZone zone(iana);
1019 if (!zone.isValid())
1021 if (when.isValid()) {
1022 const QString name = zone.displayName(when, QTimeZone::LongName, locale);
1023 if (qsizetype match = matchLength(name); match > best.nameLength) {
1024 best = { iana, match, typeFor(zone) };
1028 const bool neverDst = !zone.hasDaylightTime();
1029 for (
const QTimeZone::TimeType type : types) {
1030 if (neverDst && type == QTimeZone::DaylightTime)
1032 const QString name = zone.displayName(type, QTimeZone::LongName, locale);
1033 if (qsizetype match = matchLength(name); match > best.nameLength) {
1034 best = { iana, match, type };
1042 const QList<QByteArray> allZones = []() {
1043 QList<QByteArray> avail = QTimeZone::availableTimeZoneIds();
1044 const auto isCanonical = [](
const QByteArray &name) {
1046 return QTimeZonePrivate::aliasToIana(name).isEmpty();
1048 [[maybe_unused]]
const QList<QByteArray>::const_iterator
1049 firstAlias = std::partition(avail.begin(), avail.end(), isCanonical);
1052 Q_ASSERT(std::all_of(firstAlias, avail.constEnd(),
1053 [from = avail.constBegin(), to = firstAlias,
1054 avail](
const QByteArray &alias) {
1056 QByteArrayView iana = QTimeZonePrivate::aliasToIana(alias);
1057 return std::find_if(from, to, [iana](
const QByteArray &zone) {
1058 return zone == iana;
1059 }) != to || !avail.contains(iana);
1065 for (
const QByteArray &iana : allZones) {
1067 if (tryZone(iana) && best.nameLength >= text.size())
1082 QDuplicateTracker<QByteArray, std::size(aliasMappingTable)> triedAlready;
1083 for (
const QByteArray &iana : allZones)
1084 (
void) triedAlready.hasSeen(iana);
1085 for (
const auto &data : aliasMappingTable) {
1086 const QByteArray alias = data.aliasId().toByteArray();
1087 if (!triedAlready.hasSeen(alias) && tryZone(alias) && best.nameLength >= text.size())
1095QTimeZonePrivate::NamePrefixMatch
1096QTimeZonePrivate::findNarrowOffsetPrefix(QStringView,
const QLocale &)
1105#if QT_CONFIG(timezone_locale) && !QT_CONFIG(icu)
1108# define BACKEND_PROVIDES_OFFSET_PREFIX
1112#ifdef BACKEND_PROVIDES_OFFSET_PREFIX
1113# undef BACKEND_PROVIDES_OFFSET_PREFIX
1117struct NumericPattern
1119 NumericPattern(QStringView text,
const QLocale &locale);
1123 QList<qsizetype> pattern;
1125 bool digitsAreLocale;
1133 using Sign =
unsigned char;
1135 bool scanForToken(QStringView sought)
1139 if (sought.isEmpty())
1141 qsizetype tokensMatched = 0;
1142 const qsizetype n = sought.size();
1144 while ((idx = given.indexOf(sought, idx + n)) >= 0) {
1145 for (qsizetype i = 0; i < n; ++i)
1146 mask.setBit(idx + i);
1149 return tokensMatched > 0;
1152 Sign scanForSignsImpl(
const QLocale &locale, Sign signs)
1156 if (scanForToken(locale.positiveSign()))
1158 if (scanForToken(locale.negativeSign()))
1167 Scanner(QStringView text) : given(text), mask(text.size()) {}
1169 bool scanForDigits(
const QLocale &locale)
1173 bool matched =
false;
1174 for (
int i = 0; i < 10; ++i) {
1175 if (scanForToken(locale.toString(i)))
1181 Sign scanForSigns(
const QLocale &locale)
1185 Sign signs = scanForSignsImpl(locale,
'\0');
1186 signs = scanForSignsImpl(QLocale::c(), signs);
1187 if (scanForToken(u"\u2212"))
1192 QList<qsizetype> asPattern()
const
1196 QList<qsizetype> res;
1198 for (qsizetype i = 0, n = mask.size(); i < n; ++i) {
1199 if (mask.testBit(i)) {
1220NumericPattern::NumericPattern(QStringView text,
const QLocale &locale)
1225 Scanner scanner(text);
1226 digitsAreLocale = hasDigits = scanner.scanForDigits(locale);
1228 hasDigits = scanner.scanForDigits(QLocale::c());
1230 sign = scanner.scanForSigns(locale);
1232 pattern = scanner.asPattern();
1238 const QList<qsizetype> &txtPat;
1239 const QtTemporalPattern::TemporalFieldFlags options;
1240 qsizetype txtPos = 0, txtInd = 0;
1241 static constexpr uint Hour = 1, Minute = 2, Second = 4;
1242 uint seenFields = 0;
1243 using Digits = QLocaleData::DigitSequence;
1245 bool textMatch(QStringView str, qsizetype strPos, qsizetype slen, qsizetype tlen)
const
1249 if (txt.sliced(txtPos, tlen).compare(str.sliced(strPos, slen), Qt::CaseInsensitive) == 0)
1252 if (txtInd == 0 && slen == 3 && txt.first(3) == u"GMT" && str.first(3) == u"UTC") {
1253 Q_ASSERT(txtPos == 0);
1254 Q_ASSERT(strPos == 0);
1260 bool allowField(uint fieldBit)
const;
1261 bool allowSkipField(Digits &&fmt)
const;
1262 auto readField(QByteArrayView field, uint fieldBit,
int *value);
1263 bool scanExtraFields(QStringView sep,
const QLocaleData *locData,
1264 qsizetype &txtLen,
int &second);
1265 qsizetype scanMatchedFields(
const Digits &fmt,
const Digits &src,
bool allowExtraFields,
1266 int &hour,
int &minute,
int &second,
int &sign);
1275 PatternAligner(QStringView text,
const QList<qsizetype> &textPattern,
1276 QtTemporalPattern::TemporalFieldFlags flags)
1277 : txt(text), txtPat(textPattern), options(flags) {}
1282 static constexpr qint32 OffsetMagnitude = 38245;
1283 static constexpr QByteArrayView hourAscii{
"10"}, minuteAscii{
"37"}, secondAscii{
"25"};
1285 auto match(QStringView str,
const QList<qsizetype> &strPat,
1286 const QLocaleData *locData,
char signChar);
1289bool PatternAligner::allowField(uint fieldBit)
const
1291 if (!fieldBit || (seenFields & fieldBit))
1295 using namespace QtTemporalPattern::FieldGroup;
1296 if (!options.testAnyFlags(WidthMask))
1300 using namespace QtTemporalPattern;
1301 using F = TemporalFieldFlag;
1305 return matchesFlagsWithin(options, WidthMask & ~F::Narrow, WidthMask);
1307 return matchesFlagsWithin(options, F::Wide | F::Short, WidthMask);
1309 Q_UNREACHABLE_RETURN(
false);
1312bool PatternAligner::allowSkipField(Digits &&fmt)
const
1316 if (fmt.digits.startsWith(minuteAscii))
1318 else if (fmt.digits.startsWith(secondAscii))
1323 if (Q_UNLIKELY(seenFields & fieldBit))
1326 if (Q_UNLIKELY(seenFields & Second) && fieldBit == Minute)
1330 if (options.testAnyFlags(QtTemporalPattern::FieldGroup::WidthMask)) {
1331 using F = QtTemporalPattern::TemporalFieldFlag;
1336 return options.testAnyFlags(F::ZeroPad | F::Narrow);
1338 return options.testAnyFlags(F::ZeroPad | F::Narrow | F::Abbreviated);
1344auto PatternAligner::readField(QByteArrayView field, uint fieldBit,
int *value)
1347 constexpr int MaxHourOffset
1348 = qMax(QTimeZone::MaxUtcOffsetSecs, -QTimeZone::MinUtcOffsetSecs) / 3600;
1349 static_assert(MaxHourOffset > 9);
1351 QByteArrayView used;
1353 } res = { field,
false };
1355 if ((fieldBit != Hour && res.used.size() < 2) || !allowField(fieldBit) || !value)
1357 Q_ASSERT(*value == 0);
1358 seenFields |= fieldBit;
1359 if (res.used.size() > 2)
1360 res.used = res.used.first(2);
1361 *value = res.used.toInt(&res.ok);
1362 if (fieldBit == Hour && (!res.ok || *value > MaxHourOffset)) {
1364 *value = res.used.toInt(&res.ok);
1370bool PatternAligner::scanExtraFields(QStringView sep,
const QLocaleData *locData,
1371 qsizetype &txtLen,
int &second)
1375 while (txtInd + 1 < txtPat.size() && txt.sliced(txtPos, -txtLen) == sep) {
1377 txtLen = txtPat.at(++txtInd);
1379 Q_ASSERT((seenFields & Hour) && (seenFields & Minute));
1380 if (seenFields & Second)
1382 Q_ASSERT(second == 0);
1383 const Digits asciiParse
1384 = locData->digitSequence(txt.sliced(txtPos, txtLen));
1385 QByteArrayView found = asciiParse.digits;
1387 second = found.toInt(&ok);
1388 if (!ok || second >= 60)
1390 seenFields |= Second;
1392 txtLen = ++txtInd < txtPat.size() ? txtPat.at(txtInd) : 0;
1399qsizetype PatternAligner::scanMatchedFields(
const Digits &fmt,
const Digits &src,
1400 bool allowExtraFields,
1401 int &hour,
int &minute,
int &second,
int &sign)
1404 if (!fmt.digits.startsWith(hourAscii))
1406 if (sign || !src.sign)
1408 sign = fmt.sign == src.sign ? +1 : -1;
1409 }
else if (src.sign) {
1413 QByteArrayView chosen{fmt.digits}, found{src.digits};
1414 while (chosen.size() && found.size()) {
1415 const uint priorFields = seenFields;
1416 auto read = chosen.startsWith(hourAscii) ? readField(found, Hour, &hour)
1417 : chosen.startsWith(minuteAscii) ? readField(found, Minute, &minute)
1418 : chosen.startsWith(secondAscii) ? readField(found, Second, &second)
1419 : readField(found, 0u,
nullptr);
1423 if (read.used.size() < 2) {
1424 const uint newField = (seenFields ^ priorFields);
1427 if (newField == Hour) {
1434 found = found.sliced(read.used.size());
1435 allowExtraFields =
false;
1439 if (chosen.size() < fmt.digits.size() && (priorFields & Hour)) {
1442 seenFields = priorFields;
1443 Q_ASSERT(newField == Minute || newField == Second);
1444 if (newField == Minute)
1450 allowExtraFields =
false;
1456 Q_ASSERT(chosen.size() >= 2);
1457 chosen = chosen.sliced(2);
1458 found = found.sliced(read.used.size());
1460 if (chosen.size()) {
1461 Q_ASSERT(found.isEmpty());
1463 return (seenFields & Hour) ? 0 : -1;
1466 if (found.size() && (seenFields & Hour)) {
1469 if (allowExtraFields) {
1470 if (!Q_LIKELY(seenFields & Minute)) {
1471 const uint priorFields = seenFields;
1472 auto read = readField(found, Minute, &minute);
1473 if (!read.ok || read.used.size() < 2) {
1475 seenFields = priorFields;
1476 return found.size();
1478 found = found.sliced(read.used.size());
1480 if (!(seenFields & Second)) {
1481 const uint priorFields = seenFields;
1482 auto read = readField(found, Second, &second);
1483 if (!read.ok || read.used.size() < 2) {
1485 seenFields = priorFields;
1486 return found.size();
1488 found = found.sliced(read.used.size());
1492 return found.size();
1495 return found.size() ? -1 : 0;
1498auto PatternAligner::match(QStringView str,
const QList<qsizetype> &strPat,
1499 const QLocaleData *locData,
char signChar)
1501 Q_ASSERT(!str.isEmpty() && !strPat.isEmpty());
1506 constexpr auto AllowSign = Digits::Option::AllowSign;
1509 qsizetype length = 0;
1510 operator
bool()
const {
return length > 0; }
1512 if ((strPat.at(0) < 0) != (txtPat.at(0) < 0))
1516 int hour = 0, minute = 0, second = 0;
1517 int sign = !signChar;
1521 qsizetype skip = 0, strPos = 0, txtSkipped = 0;
1523 for (qsizetype len : strPat) {
1527 if (!allowSkipField(locData->digitSequence(str.sliced(strPos, len))))
1536 qsizetype txtLen = txtInd < txtPat.size() ? txtPat.at(txtInd) : 0;
1537 const bool maybeSep = txtInd > 0 && txtInd + 1 < strPat.size();
1540 if (!sep.isEmpty() && str.sliced(strPos, -len) != sep) {
1541 if (!scanExtraFields(sep, locData, txtLen, second))
1544 if (maybeSep && sep.isEmpty())
1545 sep = str.sliced(strPos, -len);
1547 if (!textMatch(str, strPos, -len, -txtLen)) {
1550 if (locData && !sep.isEmpty() && str.sliced(strPos, -len) == sep) {
1557 if (txtInd + 1 < strPat.size() - txtSkipped || len <= txtLen
1558 || !textMatch(str, strPos, -len, -len)) {
1572 if (txtPos >= txt.size()) {
1573 Q_ASSERT(txtInd >= txtPat.size());
1575 if (!sep.isEmpty() && txtPat.back() == sep.size() && txt.endsWith(sep)
1576 && strPat.back() == -sep.size() && str.endsWith(sep)) {
1581 txtInd = txtPat.size() - 1;
1582 txtPos -= sep.size();
1592 QStringView field = str.sliced(strPos, len);
1593 QStringView toParse = txt.sliced(txtPos, txtPat.at(txtInd));
1596 const Digits asciiField = locData->digitSequence(field, AllowSign);
1597 if (asciiField.endIndex() != field.size())
1599 const Digits asciiParse = locData->digitSequence(toParse, AllowSign);
1600 if (asciiParse.endIndex() != toParse.size())
1602 const uint priorFields = seenFields;
1604 const qsizetype spare
1605 = scanMatchedFields(asciiField, asciiParse,
1606 !txtSkipped && txtInd + 2 >= strPat.size() && sep.isEmpty(),
1607 hour, minute, second, sign);
1616 if (spare == 1 && (seenFields & Hour)) {
1618 }
else if (strPat.back() < 0) {
1619 if (sep.isEmpty() || !(priorFields & Hour)
1620 || strPat.back() != -sep.size() || !str.endsWith(sep)) {
1623 int offset = hour * 60;
1624 if (priorFields & Minute)
1627 if (priorFields & Second)
1629 return R{ sign * offset, txtPos };
1633 txtPos += asciiParse.digitStart
1634 + (asciiParse.digits.size() - spare) * asciiParse.digitWidth;
1638 strPos += asciiField.endIndex();
1639 txtPos += asciiParse.endIndex();
1644 return R{ sign * (second + 60 * (minute + 60 * hour)), txtPos };
1647QTimeZonePrivate::NamePrefixMatch
1648findOffsetPrefixImpl(QStringView text,
const QLocale &locale,
1649 QtTemporalPattern::TemporalFieldFlags flags)
1651 QTimeZonePrivate::NamePrefixMatch best;
1661 const QUtcTimeZonePrivate greenwich(0);
1663 const QUtcTimeZonePrivate positive(+PatternAligner::OffsetMagnitude);
1664 const QUtcTimeZonePrivate negative(-PatternAligner::OffsetMagnitude);
1666 constexpr QTimeZone::NameType formats[] = {
1667 QTimeZone::OffsetName, QTimeZone::LongName, QTimeZone::ShortName
1669 constexpr QTimeZone::TimeType seasons[] = {
1670 QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime
1673 const auto acceptFormat = [flags](QTimeZone::NameType format) {
1674 using namespace QtTemporalPattern;
1676 if (!flags.testAnyFlags(FieldGroup::WidthMask | FieldGroup::FormMask))
1678 using Flag = TemporalFieldFlag;
1679 constexpr TemporalFieldFlags Textual = Flag::Verbal | Flag::Standalone;
1680 constexpr TemporalFieldFlags Long = Flag::Abbreviated | Flag::Short | Flag::Wide;
1682 case QTimeZone::OffsetName:
1683 return matchesFlagWithin(flags, Flag::Numeric, FieldGroup::FormMask);
1684 case QTimeZone::DefaultName:
1685 case QTimeZone::LongName:
1686 return matchesFlagsWithin(flags, Textual, FieldGroup::FormMask)
1687 && matchesFlagsWithin(flags, Long, FieldGroup::WidthMask);
1688 case QTimeZone::ShortName:
1689 return matchesFlagsWithin(flags, Textual, FieldGroup::FormMask)
1690 && matchesFlagWithin(flags, Flag::Narrow, FieldGroup::WidthMask);
1692 Q_UNREACHABLE_RETURN(
false);
1694 const auto acceptSeason = [flags](QTimeZone::TimeType season) {
1695 using namespace QtTemporalPattern;
1697 if (!flags.testAnyFlags(FieldGroup::SeasonMask))
1699 using Flag = TemporalFieldFlag;
1701 case QTimeZone::GenericTime:
1702 return flags.testFlag(Flag::GenericTime);
1703 case QTimeZone::StandardTime:
1704 return flags.testFlag(Flag::StandardTime);
1705 case QTimeZone::DaylightTime:
1706 return flags.testFlag(Flag::DaylightSavingTime);
1708 Q_UNREACHABLE_RETURN(
false);
1712
1713
1714
1715
1716
1717 QLocale digitLocale = locale;
1718 for (
int i = 0; i < 2; ++i) {
1723 const NumericPattern textPattern(text, digitLocale);
1724 Q_ASSERT(!textPattern.pattern.isEmpty());
1725 PatternAligner aligner(text, textPattern.pattern, flags);
1729 const auto consider = [&best, &aligner, txtSign = textPattern.sign, digitLocale]
1730 (QStringView candidate,
char sign, QTimeZone::TimeType season) {
1731 const auto idForOffset = [sign](
int offsetSeconds) ->
QByteArray {
1735 offsetSeconds = -offsetSeconds;
1736 return QTimeZonePrivate::isoOffsetFormat(offsetSeconds,
1737 QTimeZone::OffsetName).toLatin1();
1739 const auto localeDataFor = [loc = digitLocale] (
const NumericPattern &pat) {
1740 if (pat.hasDigits) {
1741 if (pat.digitsAreLocale)
1742 return QLocalePrivate::get(loc)->m_data;
1743 return QLocaleData::c();
1745 return static_cast<
const QLocaleData *>(
nullptr);
1747 if (
const NumericPattern pat(candidate, digitLocale);
1749 (pat.sign & sign) != sign || (txtSign & sign) == sign) {
1750 const auto parsed = aligner.match(
1751 candidate, pat.pattern, localeDataFor(pat), pat.sign);
1752 if (parsed && parsed.length > best.nameLength)
1753 best = { idForOffset(parsed.offset), parsed.length, season };
1754 return pat.digitsAreLocale;
1759 bool nativeSeen =
false;
1760 for (
auto season : seasons) {
1761 if (!acceptSeason(season))
1763 for (
auto format : formats) {
1764 if (!acceptFormat(format))
1766 if (
const QString pos = positive.displayName(season, format, locale);
1767 pos.size() > best.nameLength) {
1768 if (consider(pos,
'+', season))
1771 if (
const QString neg = negative.displayName(season, format, locale);
1772 neg.size() > best.nameLength) {
1773 if (consider(neg,
'-', season))
1776 if (
const QString nul = greenwich.displayName(season, format, locale);
1777 nul.size() > best.nameLength) {
1778 if (text.startsWith(nul))
1779 best = {
"UTC"_ba, nul.size() };
1781 if (best.nameLength == text.size())
1788 if (i == 0 && (digitLocale.zeroDigit() == u'0' || !nativeSeen))
1790 digitLocale = QLocale::c();
1797QTimeZonePrivate::NamePrefixMatch
1798QTimeZonePrivate::findOffsetPrefix(QStringView text,
const QLocale &locale,
1799 QtTemporalPattern::TemporalFieldFlags flags)
1801 NamePrefixMatch best;
1802 if (
auto match = findOffsetPrefixImpl(text, locale, flags))
1803 best = std::move(match);
1804 if (
auto match = findOffsetPrefixImpl(text, QLocale::c(), flags);
1805 match.nameLength > best.nameLength) {
1806 best = std::move(match);
1813QTimeZonePrivate::NamePrefixMatch
1814QTimeZonePrivate::findLongUtcPrefix(QStringView text)
1816 if (text.startsWith(u"UTC")) {
1817 if (text.size() > 4 && (text[3] == u'+' || text[3] == u'-')) {
1819 const auto digitAt = [text](qsizetype index) {
1820 using QtMiscUtils::isAsciiDigit;
1821 return index < text.size() && isAsciiDigit(text[index].unicode());
1823 qsizetype length = 3;
1827 Q_ASSERT(length < text.size());
1828 if (!digitAt(length + 1) || (groups && !digitAt(length + 2)))
1830 length += digitAt(length + 2) ? 3 : 2;
1831 }
while (++groups < 3 && length < text.size() && text[length] == u':');
1833 return { text.first(length).toLatin1(), length, QTimeZone::GenericTime };
1835 return { utcQByteArray(), 3, QTimeZone::GenericTime };
1841QByteArrayView QTimeZonePrivate::aliasToIana(QByteArrayView alias)
1843 const auto data = std::lower_bound(std::begin(aliasMappingTable), std::end(aliasMappingTable),
1844 alias, earlierAliasId);
1845 if (data != std::end(aliasMappingTable) && data->aliasId() == alias)
1846 return data->ianaId();
1853QByteArrayView QTimeZonePrivate::ianaIdToWindowsId(QByteArrayView id)
1855 const auto idUtf8 = QUtf8StringView(id);
1857 for (
const ZoneData &data : zoneDataTable) {
1858 for (
auto l1 : data.ids()) {
1860 return toWindowsIdLiteral(data.windowsIdKey);
1868QByteArrayView QTimeZonePrivate::windowsIdToDefaultIanaId(QByteArrayView windowsId)
1870 const auto data = std::lower_bound(std::begin(windowsDataTable), std::end(windowsDataTable),
1871 windowsId, earlierWindowsId);
1872 if (data != std::end(windowsDataTable) && data->windowsId() == windowsId) {
1873 QByteArrayView id = data->ianaId();
1874 Q_ASSERT(id.indexOf(
' ') == -1);
1880QByteArrayView QTimeZonePrivate::windowsIdToDefaultIanaId(QByteArrayView windowsId,
1881 QLocale::Territory territory)
1884 if (territory == QLocale::World) {
1886 return windowsIdToDefaultIanaId(windowsId);
1889 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1890 const qint16 land =
static_cast<quint16>(territory);
1891 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1892 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1895 if (data->territory == land)
1896 return *data->ids().begin();
1902QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(QByteArrayView windowsId)
1904 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1905 QList<QByteArray> list;
1907 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1908 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1910 for (
auto l1 : data->ids())
1911 list << QByteArray(l1.data(), l1.size());
1918 std::sort(list.begin(), list.end());
1922QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(QByteArrayView windowsId,
1923 QLocale::Territory territory)
1926 QList<QByteArray> list;
1927 if (territory == QLocale::World) {
1929 list << windowsIdToDefaultIanaId(windowsId).toByteArray();
1931 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1932 const qint16 land =
static_cast<quint16>(territory);
1933 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1934 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1937 if (data->territory == land) {
1938 for (
auto l1 : data->ids())
1939 list << QByteArray(l1.data(), l1.size());
1951 while ((cut = ianaIds.indexOf(
' ')) >= 0) {
1952 if (id == ianaIds.first(cut))
1954 ianaIds = ianaIds.sliced(cut + 1);
1956 return id == ianaIds;
1960
1961
1962
1963
1964
1965
1968QUtcTimeZonePrivate::QUtcTimeZonePrivate()
1970 const QString name = utcQString();
1971 init(utcQByteArray(), 0, name, name, QLocale::AnyTerritory, name);
1975QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QByteArray &id)
1978 for (
const UtcData &data : utcDataTable) {
1979 if (isEntryInIanaList(id, data.id())) {
1980 QString name = QString::fromUtf8(id);
1981 init(id, data.offsetFromUtc, name, name, QLocale::AnyTerritory, name);
1989qint64 QUtcTimeZonePrivate::offsetFromUtcString(QByteArrayView id)
1994 if (!id.startsWith(
"UTC") || id.size() < 5)
1995 return invalidSeconds();
1996 const char signChar = id.at(3);
1997 if (signChar !=
'-' && signChar !=
'+')
1998 return invalidSeconds();
1999 const int sign = signChar ==
'-' ? -1 : 1;
2003 for (
auto offset : QLatin1StringView(id.mid(4)).tokenize(
':'_L1)) {
2004 if (offset.size() > 2 || (prior && offset.size() < 2))
2005 return invalidSeconds();
2007 unsigned short field = offset.toUShort(&ok);
2009 if (!ok || field >= (prior ? 60 : 24))
2010 return invalidSeconds();
2011 seconds = seconds * 60 + field;
2013 return invalidSeconds();
2017 return invalidSeconds();
2022 return seconds * sign;
2026QUtcTimeZonePrivate::QUtcTimeZonePrivate(qint32 offsetSeconds)
2031 const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
2032 offsetSeconds, atLowerUtcOffset);
2033 if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
2034 QByteArrayView ianaId = data->id();
2035 qsizetype cut = ianaId.indexOf(
' ');
2036 QByteArrayView cutId = (cut < 0 ? ianaId : ianaId.first(cut));
2037 if (cutId == utcQByteArray()) {
2039 id = utcQByteArray();
2040 name = utcQString();
2043 id = cutId.toByteArray();
2044 name = QString::fromUtf8(id);
2046 Q_ASSERT(!name.isEmpty());
2048 name = isoOffsetFormat(offsetSeconds, QTimeZone::OffsetName);
2051 init(id, offsetSeconds, name, name, QLocale::AnyTerritory, name);
2054QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QByteArray &zoneId,
int offsetSeconds,
2055 const QString &name,
const QString &abbreviation,
2056 QLocale::Territory territory,
const QString &comment)
2058 init(zoneId, offsetSeconds, name, abbreviation, territory, comment);
2061QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QUtcTimeZonePrivate &other)
2062 : QTimeZonePrivate(other), m_name(other.m_name),
2063 m_abbreviation(other.m_abbreviation),
2064 m_comment(other.m_comment),
2065 m_territory(other.m_territory),
2066 m_offsetFromUtc(other.m_offsetFromUtc)
2070QUtcTimeZonePrivate::~QUtcTimeZonePrivate()
2074QUtcTimeZonePrivate *QUtcTimeZonePrivate::clone()
const
2076 return new QUtcTimeZonePrivate(*
this);
2079QTimeZonePrivate::Data QUtcTimeZonePrivate::data(qint64 forMSecsSinceEpoch)
const
2082 d.abbreviation = m_abbreviation;
2083 d.atMSecsSinceEpoch = forMSecsSinceEpoch;
2084 d.standardTimeOffset = d.offsetFromUtc = m_offsetFromUtc;
2085 d.daylightTimeOffset = 0;
2090QTimeZonePrivate::Data QUtcTimeZonePrivate::data(QTimeZone::TimeType timeType)
const
2093 return data(QDateTime::currentMSecsSinceEpoch());
2096bool QUtcTimeZonePrivate::isDataLocale(
const QLocale &locale)
const
2099 return isAnglicLocale(locale);
2102void QUtcTimeZonePrivate::init(
const QByteArray &zoneId,
int offsetSeconds,
const QString &name,
2103 const QString &abbreviation, QLocale::Territory territory,
2104 const QString &comment)
2107 m_offsetFromUtc = offsetSeconds;
2109 m_abbreviation = abbreviation;
2110 m_territory = territory;
2111 m_comment = comment;
2114QLocale::Territory QUtcTimeZonePrivate::territory()
const
2119QString QUtcTimeZonePrivate::comment()
const
2125QString QUtcTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch,
2126 QTimeZone::NameType nameType,
2127 const QLocale &locale)
const
2129 Q_UNUSED(atMSecsSinceEpoch);
2130 return displayName(QTimeZone::StandardTime, nameType, locale);
2133QString QUtcTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
2134 QTimeZone::NameType nameType,
2135 const QLocale &locale)
const
2137#if QT_CONFIG(timezone_locale)
2144 m_offsetFromUtc != 0 ? QString() :
2146 QTimeZonePrivate::displayName(timeType, nameType, locale);
2153 const auto matchesFallback = [](
int offset, QStringView name) {
2155 int seconds = offset % 60;
2156 int rounded = offset
2157 + (seconds > 30 || (seconds == 30 && (offset / 60) % 2)
2159 : (seconds < -30 || (seconds == -30 && (offset / 60) % 2)
2162 const QString avoid = isoOffsetFormat(rounded);
2165 Q_ASSERT(avoid.startsWith(
"UTC"_L1));
2166 Q_ASSERT(avoid.size() == 9);
2169 if (!(name.startsWith(
"GMT"_L1) || name.startsWith(
"UTC"_L1)) || name.size() < 5)
2172 QStringView tail{avoid};
2173 tail = tail.sliced(3);
2174 if (name.sliced(3) == tail)
2176 while (tail.endsWith(
":00"_L1))
2177 tail = tail.chopped(3);
2178 while (name.endsWith(
":00"_L1))
2179 name = name.chopped(3);
2183 const QChar sign = name[3] == u'\u2212' ? u'-' : name[3];
2185 return sign == tail[0] && tail.sliced(tail[1] == u'0' ? 2 : 1) == name.sliced(4);
2187 if (!name.isEmpty() && (m_name.isEmpty() || !matchesFallback(m_offsetFromUtc, name)))
2193 if (nameType == QTimeZone::ShortName)
2194 return m_abbreviation;
2195 if (nameType == QTimeZone::OffsetName)
2196 return isoOffsetFormat(m_offsetFromUtc);
2200QString QUtcTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch)
const
2202 Q_UNUSED(atMSecsSinceEpoch);
2203 return m_abbreviation;
2206qint32 QUtcTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch)
const
2208 Q_UNUSED(atMSecsSinceEpoch);
2209 return m_offsetFromUtc;
2212qint32 QUtcTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch)
const
2214 Q_UNUSED(atMSecsSinceEpoch);
2218QByteArray QUtcTimeZonePrivate::systemTimeZoneId()
const
2221 const emscripten::val date = emscripten::val::global(
"Date").new_();
2222 if (date.isUndefined())
2223 return utcQByteArray();
2226 const int offsetSeconds = -date.call<
int>(
"getTimezoneOffset") * 60;
2227 if (offsetSeconds == 0)
2228 return utcQByteArray();
2229 return isoOffsetFormat(offsetSeconds).toUtf8();
2231 return utcQByteArray();
2235bool QUtcTimeZonePrivate::isTimeZoneIdAvailable(QByteArrayView ianaId)
const
2238 for (
const UtcData &data : utcDataTable) {
2239 if (isEntryInIanaList(ianaId, data.id()))
2248QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds()
const
2251 QList<QByteArray> result;
2252 result.reserve(std::size(utcDataTable));
2253 for (
const UtcData &data : utcDataTable) {
2254 QByteArrayView id = data.id();
2256 while ((cut = id.indexOf(
' ')) >= 0) {
2257 result << id.first(cut).toByteArray();
2258 id = id.sliced(cut + 1);
2260 result << id.toByteArray();
2263 std::sort(result.begin(), result.end());
2268QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(QLocale::Territory country)
const
2271 if (country == QLocale::AnyTerritory)
2272 return availableTimeZoneIds();
2273 return QList<QByteArray>();
2276QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(qint32 offsetSeconds)
const
2280 QList<QByteArray> result;
2281 const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
2282 offsetSeconds, atLowerUtcOffset);
2283 if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
2284 QByteArrayView id = data->id();
2286 while ((cut = id.indexOf(
' ')) >= 0) {
2287 result << id.first(cut).toByteArray();
2288 id = id.sliced(cut + 1);
2290 result << id.toByteArray();
2295 QByteArray isoName = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName).toUtf8();
2296 if (offsetFromUtcString(isoName) == qint64(offsetSeconds) && !result.contains(isoName))
2299 std::sort(result.begin(), result.end());
2304#ifndef QT_NO_DATASTREAM
2305void QUtcTimeZonePrivate::serialize(QDataStream &ds)
const
2307 ds <<
QStringLiteral(
"OffsetFromUtc") << QString::fromUtf8(m_id) << m_offsetFromUtc << m_name
2308 << m_abbreviation <<
static_cast<qint32>(m_territory) << m_comment;
static constexpr WindowsData windowsDataTable[]
static constexpr ZoneData zoneDataTable[]
static constexpr AliasData aliasMappingTable[]
#define QStringLiteral(str)
constexpr bool atLowerWindowsKey(WindowsData entry, qint16 winIdKey) noexcept
static bool earlierAliasId(AliasData entry, QByteArrayView aliasId) noexcept
static QByteArrayView aliasMatching(QByteArrayView name, Pred test)
static bool isEntryInIanaList(QByteArrayView id, QByteArrayView ianaIds)
static bool earlierWinData(WindowsData less, WindowsData more) noexcept
static auto zoneStartForWindowsId(quint16 windowsIdKey) noexcept
constexpr bool zoneAtLowerWindowsKey(ZoneData entry, qint16 winIdKey) noexcept
static quint16 toWindowsIdKey(QByteArrayView winId)
static QList< QByteArray > selectAvailable(QList< QByteArrayView > &&desired, const QList< QByteArray > &all)
static QByteArrayView toWindowsIdLiteral(quint16 windowsIdKey)
constexpr bool atLowerUtcOffset(UtcData entry, qint32 offsetSeconds) noexcept
constexpr bool earlierZoneData(ZoneData less, ZoneData more) noexcept
static bool earlierWindowsId(WindowsData entry, QByteArrayView winId) noexcept