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
318 auto dataToState = [](
const Data &d) {
319 return QDateTimePrivate::ZoneState(d.atMSecsSinceEpoch + d.offsetFromUtc * 1000,
321 d.daylightTimeOffset ? QDateTimePrivate::DaylightTime
322 : QDateTimePrivate::StandardTime);
326
327
328
329
330
331
332
333 std::integral_constant<qint64, 17 * 3600 * 1000> seventeenHoursInMSecs;
334 static_assert(-seventeenHoursInMSecs / 1000 < QTimeZone::MinUtcOffsetSecs
335 && seventeenHoursInMSecs / 1000 > QTimeZone::MaxUtcOffsetSecs);
338 const qint64 recent =
339 qSubOverflow(forLocalMSecs, seventeenHoursInMSecs, &millis) || millis < minMSecs()
340 ? minMSecs() : millis;
342 const qint64 imminent =
343 qAddOverflow(forLocalMSecs, seventeenHoursInMSecs, &millis)
344 ? maxMSecs() : millis;
346 Q_ASSERT(recent < imminent && seventeenHoursInMSecs - 1 <= imminent - recent);
349 const Data past = data(recent), future = data(imminent);
350 if (future.atMSecsSinceEpoch == invalidMSecs()
351 && past.atMSecsSinceEpoch == invalidMSecs()) {
354 return { forLocalMSecs };
357 if (Q_LIKELY(past.offsetFromUtc == future.offsetFromUtc
358 && past.standardTimeOffset == future.standardTimeOffset
360 && past.abbreviation == future.abbreviation)) {
362 data.atMSecsSinceEpoch = forLocalMSecs - future.offsetFromUtc * 1000;
363 return dataToState(data);
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 if (hasTransitions()) {
394
395
396
397
398
399
400
401
402
403
408 Q_ASSERT(forLocalMSecs < 0 ||
409 forLocalMSecs - tran.offsetFromUtc * 1000 >= tran.atMSecsSinceEpoch);
411 Data nextTran = nextTransition(tran.atMSecsSinceEpoch);
413
414
415
416
417
418
419
420 while (nextTran.atMSecsSinceEpoch != invalidMSecs()
421 && forLocalMSecs > nextTran.atMSecsSinceEpoch + nextTran.offsetFromUtc * 1000) {
422 Data newTran = nextTransition(nextTran.atMSecsSinceEpoch);
423 if (newTran.atMSecsSinceEpoch == invalidMSecs()
424 || newTran.atMSecsSinceEpoch + newTran.offsetFromUtc * 1000 > imminent) {
431 const qint64 nextStart = nextTran.atMSecsSinceEpoch;
434 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
436 Q_ASSERT(forLocalMSecs < 0
437 || forLocalMSecs - tran.offsetFromUtc * 1000 > tran.atMSecsSinceEpoch);
439 tran.atMSecsSinceEpoch = forLocalMSecs - tran.offsetFromUtc * 1000;
447 if (nextStart == invalidMSecs() && tran.offsetFromUtc == future.offsetFromUtc)
448 return dataToState(tran);
451 if (tran.atMSecsSinceEpoch != invalidMSecs() && nextStart != invalidMSecs()) {
453
454
455
456
457
458
459
460
461
463 nextTran.atMSecsSinceEpoch = forLocalMSecs - nextTran.offsetFromUtc * 1000;
465 bool fallBack =
false;
466 if (nextStart > nextTran.atMSecsSinceEpoch) {
468 if (nextStart > tran.atMSecsSinceEpoch)
469 return dataToState(tran);
471 Q_ASSERT(tran.offsetFromUtc < nextTran.offsetFromUtc);
473 }
else if (nextStart <= tran.atMSecsSinceEpoch) {
475 return dataToState(nextTran);
477 Q_ASSERT(nextTran.offsetFromUtc < tran.offsetFromUtc);
485 = resolve.testFlag(QDateTimePrivate::FlipForReverseDst)
486 && (fallBack ? !tran.daylightTimeOffset && nextTran.daylightTimeOffset
487 : tran.daylightTimeOffset && !nextTran.daylightTimeOffset);
490 if (resolve.testFlag(flipped
491 ? QDateTimePrivate::FoldUseBefore
492 : QDateTimePrivate::FoldUseAfter)) {
493 return dataToState(nextTran);
495 if (resolve.testFlag(flipped
496 ? QDateTimePrivate::FoldUseAfter
497 : QDateTimePrivate::FoldUseBefore)) {
498 return dataToState(tran);
502
503
504
505
506
507 std::swap(tran.atMSecsSinceEpoch, nextTran.atMSecsSinceEpoch);
508 if (resolve.testFlag(flipped
509 ? QDateTimePrivate::GapUseBefore
510 : QDateTimePrivate::GapUseAfter))
511 return dataToState(nextTran);
512 if (resolve.testFlag(flipped
513 ? QDateTimePrivate::GapUseAfter
514 : QDateTimePrivate::GapUseBefore))
515 return dataToState(tran);
518 return {forLocalMSecs};
525 qint64 utcEpochMSecs;
528 int early = past.offsetFromUtc;
529 int late = future.offsetFromUtc;
530 if (early == late || late == invalidSeconds()) {
531 if (early == invalidSeconds()
532 || qSubOverflow(forLocalMSecs, early * qint64(1000), &utcEpochMSecs)) {
533 return {forLocalMSecs};
537 const qint64 forEarly = forLocalMSecs - early * 1000;
538 const qint64 forLate = forLocalMSecs - late * 1000;
541 const bool earlyOk = offsetFromUtc(forEarly) == early;
542 const bool lateOk = offsetFromUtc(forLate) == late;
546 Q_ASSERT(early > late);
548 if (resolve.testFlag(QDateTimePrivate::FoldUseBefore))
549 utcEpochMSecs = forEarly;
550 else if (resolve.testFlag(QDateTimePrivate::FoldUseAfter))
551 utcEpochMSecs = forLate;
553 return {forLocalMSecs};
556 utcEpochMSecs = forEarly;
560 utcEpochMSecs = forLate;
563 Q_ASSERT(late > early);
564 const int dstStep = (late - early) * 1000;
565 if (resolve.testFlag(QDateTimePrivate::GapUseBefore))
566 utcEpochMSecs = forEarly - dstStep;
567 else if (resolve.testFlag(QDateTimePrivate::GapUseAfter))
568 utcEpochMSecs = forLate + dstStep;
570 return {forLocalMSecs};
574 return dataToState(data(utcEpochMSecs));
577bool QTimeZonePrivate::hasTransitions()
const
582QTimeZonePrivate::Data QTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch)
const
584 Q_UNUSED(afterMSecsSinceEpoch);
588QTimeZonePrivate::Data QTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch)
const
590 Q_UNUSED(beforeMSecsSinceEpoch);
594QTimeZonePrivate::DataList QTimeZonePrivate::transitions(qint64 fromMSecsSinceEpoch,
595 qint64 toMSecsSinceEpoch)
const
598 if (toMSecsSinceEpoch >= fromMSecsSinceEpoch) {
600 Data next = nextTransition(qMax(fromMSecsSinceEpoch, minMSecs()) - 1);
601 while (next.atMSecsSinceEpoch != invalidMSecs()
602 && next.atMSecsSinceEpoch <= toMSecsSinceEpoch) {
604 next = nextTransition(next.atMSecsSinceEpoch);
610QByteArray QTimeZonePrivate::systemTimeZoneId()
const
615template <
typename Pred>
624 name, earlierAliasId);
626 name = data->ianaId();
634 for (
const auto &data : aliasMappingTable) {
635 QByteArrayView alias = data.aliasId();
636 if (data.ianaId() == name && test(alias))
642QByteArrayView QTimeZonePrivate::availableAlias(QByteArrayView ianaId)
const
644 return aliasMatching(ianaId, [
this](QByteArrayView id) {
return isTimeZoneIdAvailable(id); });
647bool QTimeZonePrivate::isTimeZoneIdAvailable(QByteArrayView ianaId)
const
651 const QList<QByteArray> tzIds = availableTimeZoneIds();
652 return std::binary_search(tzIds.begin(), tzIds.end(), ianaId);
658 std::sort(desired.begin(), desired.end());
659 const auto newEnd =
std::unique(desired.begin(), desired.end());
660 const auto newSize =
std::distance(desired.begin(), newEnd);
662 result.reserve(qMin(all.size(), newSize));
663 std::set_intersection(all.begin(), all.end(), desired.cbegin(),
664 std::next(desired.cbegin(), newSize),
std::back_inserter(result));
668QList<QByteArrayView> QTimeZonePrivate::matchingTimeZoneIds(QLocale::Territory territory)
const
671 QList<QByteArrayView> regions;
672#if QT_CONFIG(timezone_locale) && !QT_CONFIG(icu)
673 regions = QtTimeZoneLocale::ianaIdsForTerritory(territory);
676 if (territory == QLocale::World) {
679 for (
const WindowsData &data : windowsDataTable)
680 regions << data.ianaId();
682 for (
const ZoneData &data : zoneDataTable) {
683 if (data.territory == territory) {
684 for (
auto l1 : data.ids())
685 regions << QByteArrayView(l1.data(), l1.size());
692QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(QLocale::Territory territory)
const
694 return selectAvailable(matchingTimeZoneIds(territory), availableTimeZoneIds());
697QList<QByteArrayView> QTimeZonePrivate::matchingTimeZoneIds(
int offsetFromUtc)
const
700 QList<QByteArrayView> offsets;
702 for (
const WindowsData &winData : windowsDataTable) {
703 if (winData.offsetFromUtc == offsetFromUtc) {
704 for (
auto data = zoneStartForWindowsId(winData.windowsIdKey);
705 data != std::end(zoneDataTable) && data->windowsIdKey == winData.windowsIdKey;
707 for (
auto l1 : data->ids())
708 offsets << QByteArrayView(l1.data(), l1.size());
715QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(
int offsetFromUtc)
const
717 return selectAvailable(matchingTimeZoneIds(offsetFromUtc), availableTimeZoneIds());
720QList<QByteArray> QTimeZonePrivate::uniqueSortedAliasPadded(QList<QByteArray> &&zoneIds)
723 const QList<QByteArray> source = zoneIds;
725 for (
const auto &name : source) {
726 const auto zone = aliasToIana(name);
727 if (!zone.isEmpty()) {
728 zoneIds << zone.toByteArray();
729 Q_ASSERT(aliasToIana(zone).isEmpty());
732 std::sort(zoneIds.begin(), zoneIds.end());
733 zoneIds.erase(std::unique(zoneIds.begin(), zoneIds.end()), zoneIds.end());
737QList<QByteArray> QTimeZonePrivate::padSortedWithAliases(QList<QByteArray> &&zoneIds)
740 const QList<QByteArray> source = zoneIds;
741 for (
const auto &name : source) {
742 const auto zone = aliasToIana(name);
743 const auto pos = std::lower_bound(zoneIds.begin(), zoneIds.end(), zone);
744 if (pos != zoneIds.end() && *pos != zone)
745 zoneIds.insert(pos, zone.toByteArray());
750#ifndef QT_NO_DATASTREAM
751void QTimeZonePrivate::serialize(QDataStream &ds)
const
753 ds << QString::fromUtf8(m_id);
759QTimeZone::OffsetData QTimeZonePrivate::invalidOffsetData()
761 return { QString(), QDateTime(),
762 invalidSeconds(), invalidSeconds(), invalidSeconds() };
765QTimeZone::OffsetData QTimeZonePrivate::toOffsetData(
const QTimeZonePrivate::Data &data)
767 if (data.atMSecsSinceEpoch == invalidMSecs())
768 return invalidOffsetData();
772 QDateTime::fromMSecsSinceEpoch(data.atMSecsSinceEpoch, QTimeZone::UTC),
773 data.offsetFromUtc, data.standardTimeOffset, data.daylightTimeOffset };
777bool QTimeZonePrivate::isValidId(QByteArrayView ianaId)
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
815
816
817
820 const int MinSectionLength = 1;
821#if defined(Q_OS_ANDROID) || QT_CONFIG(icu)
824 const int MaxSectionLength = 17;
826 const int MaxSectionLength = 14;
828 int sectionLength = 0;
830 const auto sectionIsSane = [](
int sectionLength,
int sectionDots) {
831 if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength)
833 return sectionLength != sectionDots;
835 for (
const char *it = ianaId.begin(), *
const end = ianaId.end(); it != end; ++it, ++sectionLength) {
838 if (!sectionIsSane(sectionLength, sectionDots))
842 }
else if (ch ==
'-') {
843 if (sectionLength == 0)
845 }
else if (ch ==
'.') {
847 }
else if (!isAsciiLower(ch)
857 return sectionIsSane(sectionLength, sectionDots);
860QString QTimeZonePrivate::isoOffsetFormat(
int offsetFromUtc, QTimeZone::NameType mode)
862 if (mode == QTimeZone::ShortName && !offsetFromUtc)
866 if (offsetFromUtc < 0) {
868 offsetFromUtc = -offsetFromUtc;
870 const int secs = offsetFromUtc % 60;
871 const int mins = (offsetFromUtc / 60) % 60;
872 const int hour = offsetFromUtc / 3600;
873 QString result = QString::asprintf(
"UTC%c%02d", sign, hour);
874 if (mode != QTimeZone::ShortName || secs || mins)
875 result += QString::asprintf(
":%02d", mins);
876 if (mode == QTimeZone::LongName || secs)
877 result += QString::asprintf(
":%02d", secs);
881QList<QByteArray> QTimeZonePrivate::getCanonicalZonesThenAliases()
883 static const QList<QByteArray> canonicalZonesThenAliases = []() {
884 QList<QByteArray> avail = QTimeZone::availableTimeZoneIds();
885 const auto isCanonical = [](
const QByteArray &name) {
887 return QTimeZonePrivate::aliasToIana(name).isEmpty();
889 [[maybe_unused]]
const QList<QByteArray>::const_iterator
890 firstAlias = std::partition(avail.begin(), avail.end(), isCanonical);
893 Q_ASSERT(std::all_of(firstAlias, avail.constEnd(),
894 [from = avail.constBegin(), to = firstAlias,
895 avail](
const QByteArray &alias) {
897 QByteArrayView iana = QTimeZonePrivate::aliasToIana(alias);
898 return std::find_if(from, to, [iana](
const QByteArray &zone) {
900 }) != to || !avail.contains(iana);
905 return canonicalZonesThenAliases;
908#if QT_CONFIG(icu) || !QT_CONFIG(timezone_locale)
909static QTimeZonePrivate::NamePrefixMatch
910findUtcOffsetPrefix(QStringView text,
const QLocale &locale)
914 qsizetype signLen = 0;
916 auto signStart = [&signLen, &sign, locale](QStringView str) {
917 QString signStr = locale.negativeSign();
918 if (str.startsWith(signStr)) {
920 signLen = signStr.size();
924 if (str.startsWith(u'\u2212')) {
929 signStr = locale.positiveSign();
930 if (str.startsWith(signStr)) {
932 signLen = signStr.size();
938 if (!((text.startsWith(u"UTC") || text.startsWith(u"GMT")) && signStart(text.sliced(3))))
941 QStringView offset = text.sliced(3 + signLen);
942 QStringIterator iter(offset);
943 qsizetype hourEnd = 0, hmMid = 0, minEnd = 0;
946 while (digits < 4 && iter.hasNext()) {
948 if (!QChar::isDigit(ch))
954 hourEnd = std::exchange(hmMid, std::exchange(minEnd, iter.index()));
959 QStringView hourStr, minStr;
961 minStr = offset.first(minEnd).sliced(hourEnd);
962 }
else if (digits < 3 && iter.hasNext() && QChar::isPunct(ch)) {
964 hmMid = iter.index();
966 while (mindig < 2 && iter.hasNext() && QChar::isDigit(iter.next())) {
968 minEnd = iter.index();
971 minStr = offset.first(minEnd).sliced(hmMid);
977 hourStr = offset.first(hourEnd);
980 uint hour = 0, minute = 0;
981 if (!hourStr.isEmpty())
982 hour = locale.toUInt(hourStr, &ok);
983 if (ok && !minStr.isEmpty()) {
984 minute = locale.toUInt(minStr, &ok);
986 if (!ok || minute >= 60) {
995 constexpr int MaxOffsetSeconds
996 = qMax(QTimeZone::MaxUtcOffsetSecs, -QTimeZone::MinUtcOffsetSecs);
997 int seconds = (hour * 60 + minute) * 60;
998 if (!ok || seconds > MaxOffsetSeconds)
1010 std::snprintf(buffer,
sizeof(buffer),
"UTC%c%02u:%02u", sign, hour, minute);
1012 std::snprintf(buffer,
sizeof(buffer),
"UTC%c%02u", sign, hour);
1014 return { QByteArray(buffer, qstrnlen(buffer,
sizeof(buffer))),
1015 3 + signLen + minEnd, QTimeZone::GenericTime, seconds };
1018QTimeZonePrivate::NamePrefixMatch
1019QTimeZonePrivate::findLongNamePrefix(QStringView text,
const QLocale &locale,
1020 std::optional<qint64> atEpochMillis)
1024 QTimeZonePrivate::NamePrefixMatch best = findUtcOffsetPrefix(text, locale);
1026 const auto matchLength = [text](QStringView name) -> qsizetype {
1027 qsizetype length = 0;
1028 if (name.size() > 0 && text.startsWith(name, Qt::CaseInsensitive)) {
1029 length = name.size();
1031 while (!text.first(length).startsWith(name, Qt::CaseInsensitive)) {
1033 Q_ASSERT(length <= text.size());
1036 if (length == name.size()) {
1037 while (length > 0 && text.first(length - 1).startsWith(name, Qt::CaseInsensitive))
1043 const auto when = atEpochMillis
1044 ? QDateTime::fromMSecsSinceEpoch(*atEpochMillis, QTimeZone::UTC)
1046 const auto typeFor = [when](QTimeZone zone) {
1047 if (when.isValid() && zone.isDaylightTime(when))
1048 return QTimeZone::DaylightTime;
1050 return QTimeZone::GenericTime;
1052 const auto tryZone = [&](
const QByteArray &iana) {
1053 bool matched =
false;
1054 constexpr QTimeZone::TimeType types[]
1055 = { QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime };
1056 QTimeZone zone(iana);
1057 if (!zone.isValid())
1059 if (when.isValid()) {
1060 const QString name = zone.displayName(when, QTimeZone::LongName, locale);
1061 if (qsizetype match = matchLength(name); match > best.nameLength) {
1062 best = { iana, match, typeFor(zone) };
1066 const bool neverDst = !zone.hasDaylightTime();
1067 for (
const QTimeZone::TimeType type : types) {
1068 if (neverDst && type == QTimeZone::DaylightTime)
1070 const QString name = zone.displayName(type, QTimeZone::LongName, locale);
1071 if (qsizetype match = matchLength(name); match > best.nameLength) {
1072 best = { iana, match, type };
1080 const QList<QByteArray> allZones = getCanonicalZonesThenAliases();
1081 for (
const QByteArray &iana : allZones) {
1083 if (tryZone(iana) && best.nameLength >= text.size())
1098 QDuplicateTracker<QByteArray, std::size(aliasMappingTable)> triedAlready;
1099 for (
const QByteArray &iana : allZones)
1100 (
void) triedAlready.hasSeen(iana);
1101 for (
const auto &data : aliasMappingTable) {
1102 const QByteArray alias = data.aliasId().toByteArray();
1103 if (!triedAlready.hasSeen(alias) && tryZone(alias) && best.nameLength >= text.size())
1111QTimeZonePrivate::NamePrefixMatch
1112QTimeZonePrivate::findNarrowOffsetPrefix(QStringView,
const QLocale &)
1121#if QT_CONFIG(timezone_locale) && !QT_CONFIG(icu)
1124# define BACKEND_PROVIDES_OFFSET_PREFIX
1128#ifdef BACKEND_PROVIDES_OFFSET_PREFIX
1129# undef BACKEND_PROVIDES_OFFSET_PREFIX
1133struct NumericPattern
1135 NumericPattern(QStringView text,
const QLocale &locale);
1139 QList<qsizetype> pattern;
1141 bool digitsAreLocale;
1149 using Sign =
unsigned char;
1151 bool scanForToken(QStringView sought)
1155 if (sought.isEmpty())
1157 qsizetype tokensMatched = 0;
1158 const qsizetype n = sought.size();
1160 while ((idx = given.indexOf(sought, idx + n)) >= 0) {
1161 for (qsizetype i = 0; i < n; ++i)
1162 mask.setBit(idx + i);
1165 return tokensMatched > 0;
1168 Sign scanForSignsImpl(
const QLocale &locale, Sign signs)
1172 if (scanForToken(locale.positiveSign()))
1174 if (scanForToken(locale.negativeSign()))
1183 Scanner(QStringView text) : given(text), mask(text.size()) {}
1185 bool scanForDigits(
const QLocale &locale)
1189 bool matched =
false;
1190 for (
int i = 0; i < 10; ++i) {
1191 if (scanForToken(locale.toString(i)))
1197 Sign scanForSigns(
const QLocale &locale)
1201 Sign signs = scanForSignsImpl(locale,
'\0');
1202 signs = scanForSignsImpl(QLocale::c(), signs);
1203 if (scanForToken(u"\u2212"))
1208 QList<qsizetype> asPattern()
const
1212 QList<qsizetype> res;
1214 for (qsizetype i = 0, n = mask.size(); i < n; ++i) {
1215 if (mask.testBit(i)) {
1236NumericPattern::NumericPattern(QStringView text,
const QLocale &locale)
1241 Scanner scanner(text);
1242 digitsAreLocale = hasDigits = scanner.scanForDigits(locale);
1244 hasDigits = scanner.scanForDigits(QLocale::c());
1246 sign = scanner.scanForSigns(locale);
1248 pattern = scanner.asPattern();
1254 const QList<qsizetype> &txtPat;
1255 const QtTemporalPattern::TemporalFieldFlags options;
1256 qsizetype txtPos = 0, txtInd = 0;
1257 static constexpr uint Hour = 1, Minute = 2, Second = 4;
1258 uint seenFields = 0;
1259 using Digits = QLocaleData::DigitSequence;
1261 bool textMatch(QStringView str, qsizetype strPos, qsizetype slen, qsizetype tlen)
const
1265 if (txt.sliced(txtPos, tlen).compare(str.sliced(strPos, slen), Qt::CaseInsensitive) == 0)
1268 if (txtInd == 0 && slen == 3 && txt.first(3) == u"GMT" && str.first(3) == u"UTC") {
1269 Q_ASSERT(txtPos == 0);
1270 Q_ASSERT(strPos == 0);
1276 bool allowField(uint fieldBit)
const;
1277 bool allowSkipField(Digits &&fmt)
const;
1278 auto readField(QByteArrayView field, uint fieldBit,
int *value);
1279 bool scanExtraFields(QStringView sep,
const QLocaleData *locData,
1280 qsizetype &txtLen,
int &second);
1281 qsizetype scanMatchedFields(
const Digits &fmt,
const Digits &src,
bool allowExtraFields,
1282 int &hour,
int &minute,
int &second,
int &sign);
1291 PatternAligner(QStringView text,
const QList<qsizetype> &textPattern,
1292 QtTemporalPattern::TemporalFieldFlags flags)
1293 : txt(text), txtPat(textPattern), options(flags) {}
1298 static constexpr qint32 OffsetMagnitude = 38245;
1299 static constexpr QByteArrayView hourAscii{
"10"}, minuteAscii{
"37"}, secondAscii{
"25"};
1301 bool compare(QStringView str)
1304 return txt.size() >= str.size() && textMatch(str, 0, str.size(), str.size());
1307 auto match(QStringView str,
const QList<qsizetype> &strPat,
1308 const QLocaleData *locData,
char signChar);
1311bool PatternAligner::allowField(uint fieldBit)
const
1313 if (!fieldBit || (seenFields & fieldBit))
1317 using namespace QtTemporalPattern::FieldGroup;
1318 if (!options.testAnyFlags(WidthMask))
1322 using namespace QtTemporalPattern;
1323 using F = TemporalFieldFlag;
1327 return matchesFlagsWithin(options, WidthMask & ~F::Narrow, WidthMask);
1329 return matchesFlagsWithin(options, F::Wide | F::Short, WidthMask);
1331 Q_UNREACHABLE_RETURN(
false);
1334bool PatternAligner::allowSkipField(Digits &&fmt)
const
1338 if (fmt.digits.startsWith(minuteAscii))
1340 else if (fmt.digits.startsWith(secondAscii))
1345 if (Q_UNLIKELY(seenFields & fieldBit))
1348 if (Q_UNLIKELY(seenFields & Second) && fieldBit == Minute)
1352 if (options.testAnyFlags(QtTemporalPattern::FieldGroup::WidthMask)) {
1353 using F = QtTemporalPattern::TemporalFieldFlag;
1358 return options.testAnyFlags(F::ZeroPad | F::Narrow);
1360 return options.testAnyFlags(F::ZeroPad | F::Narrow | F::Abbreviated);
1366auto PatternAligner::readField(QByteArrayView field, uint fieldBit,
int *value)
1369 constexpr int MaxHourOffset
1370 = qMax(QTimeZone::MaxUtcOffsetSecs, -QTimeZone::MinUtcOffsetSecs) / 3600;
1371 static_assert(MaxHourOffset > 9);
1373 QByteArrayView used;
1375 } res = { field,
false };
1377 if ((fieldBit != Hour && res.used.size() < 2) || !allowField(fieldBit) || !value)
1379 Q_ASSERT(*value == 0);
1380 seenFields |= fieldBit;
1381 if (res.used.size() > 2)
1382 res.used = res.used.first(2);
1383 *value = res.used.toInt(&res.ok);
1384 if (fieldBit == Hour && (!res.ok || *value > MaxHourOffset)) {
1386 *value = res.used.toInt(&res.ok);
1392bool PatternAligner::scanExtraFields(QStringView sep,
const QLocaleData *locData,
1393 qsizetype &txtLen,
int &second)
1397 while (txtInd + 1 < txtPat.size() && txt.sliced(txtPos, -txtLen) == sep) {
1399 txtLen = txtPat.at(++txtInd);
1401 Q_ASSERT((seenFields & Hour) && (seenFields & Minute));
1402 if (seenFields & Second)
1404 Q_ASSERT(second == 0);
1405 const Digits asciiParse
1406 = locData->digitSequence(txt.sliced(txtPos, txtLen));
1407 QByteArrayView found = asciiParse.digits;
1409 second = found.toInt(&ok);
1410 if (!ok || second >= 60)
1412 seenFields |= Second;
1414 txtLen = ++txtInd < txtPat.size() ? txtPat.at(txtInd) : 0;
1421qsizetype PatternAligner::scanMatchedFields(
const Digits &fmt,
const Digits &src,
1422 bool allowExtraFields,
1423 int &hour,
int &minute,
int &second,
int &sign)
1426 if (!fmt.digits.startsWith(hourAscii))
1428 if (sign || !src.sign)
1430 sign = fmt.sign == src.sign ? +1 : -1;
1431 }
else if (src.sign) {
1435 QByteArrayView chosen{fmt.digits}, found{src.digits};
1436 while (chosen.size() && found.size()) {
1437 const uint priorFields = seenFields;
1438 auto read = chosen.startsWith(hourAscii) ? readField(found, Hour, &hour)
1439 : chosen.startsWith(minuteAscii) ? readField(found, Minute, &minute)
1440 : chosen.startsWith(secondAscii) ? readField(found, Second, &second)
1441 : readField(found, 0u,
nullptr);
1445 if (read.used.size() < 2) {
1446 const uint newField = (seenFields ^ priorFields);
1449 if (newField == Hour) {
1456 found = found.sliced(read.used.size());
1457 allowExtraFields =
false;
1461 if (chosen.size() < fmt.digits.size() && (priorFields & Hour)) {
1464 seenFields = priorFields;
1465 Q_ASSERT(newField == Minute || newField == Second);
1466 if (newField == Minute)
1472 allowExtraFields =
false;
1478 Q_ASSERT(chosen.size() >= 2);
1479 chosen = chosen.sliced(2);
1480 found = found.sliced(read.used.size());
1482 if (chosen.size()) {
1483 Q_ASSERT(found.isEmpty());
1485 return (seenFields & Hour) ? 0 : -1;
1488 if (found.size() && (seenFields & Hour)) {
1491 if (allowExtraFields) {
1492 if (!Q_LIKELY(seenFields & Minute)) {
1493 const uint priorFields = seenFields;
1494 auto read = readField(found, Minute, &minute);
1495 if (!read.ok || read.used.size() < 2) {
1497 seenFields = priorFields;
1498 return found.size();
1500 found = found.sliced(read.used.size());
1502 if (!(seenFields & Second)) {
1503 const uint priorFields = seenFields;
1504 auto read = readField(found, Second, &second);
1505 if (!read.ok || read.used.size() < 2) {
1507 seenFields = priorFields;
1508 return found.size();
1510 found = found.sliced(read.used.size());
1514 return found.size();
1517 return found.size() ? -1 : 0;
1520auto PatternAligner::match(QStringView str,
const QList<qsizetype> &strPat,
1521 const QLocaleData *locData,
char signChar)
1523 Q_ASSERT(!str.isEmpty() && !strPat.isEmpty());
1528 constexpr auto AllowSign = Digits::Option::AllowSign;
1531 qsizetype length = 0;
1532 operator
bool()
const {
return length > 0; }
1534 if ((strPat.at(0) < 0) != (txtPat.at(0) < 0))
1538 int hour = 0, minute = 0, second = 0;
1539 int sign = !signChar;
1543 qsizetype skip = 0, strPos = 0, txtSkipped = 0;
1545 for (qsizetype len : strPat) {
1549 if (!allowSkipField(locData->digitSequence(str.sliced(strPos, len))))
1558 qsizetype txtLen = txtInd < txtPat.size() ? txtPat.at(txtInd) : 0;
1560 const bool maybeSep = txtInd > 0 && txtInd + 1 < strPat.size();
1563 if (!sep.isEmpty() && str.sliced(strPos, -len) != sep) {
1564 if (!scanExtraFields(sep, locData, txtLen, second))
1567 if (maybeSep && sep.isEmpty())
1568 sep = str.sliced(strPos, -len);
1570 if (!textMatch(str, strPos, -len, -txtLen)) {
1573 if (locData && !sep.isEmpty() && str.sliced(strPos, -len) == sep) {
1580 if (txtInd + 1 < strPat.size() - txtSkipped || len <= txtLen
1581 || !textMatch(str, strPos, -len, -len)) {
1595 if (txtPos >= txt.size()) {
1596 Q_ASSERT(txtInd >= txtPat.size());
1598 if (!sep.isEmpty() && txtPat.back() == sep.size() && txt.endsWith(sep)
1599 && strPat.back() == -sep.size() && str.endsWith(sep)) {
1604 txtInd = txtPat.size() - 1;
1605 txtPos -= sep.size();
1615 QStringView field = str.sliced(strPos, len);
1616 QStringView toParse = txt.sliced(txtPos, txtPat.at(txtInd));
1619 const Digits asciiField = locData->digitSequence(field, AllowSign);
1620 if (asciiField.endIndex() != field.size())
1622 const Digits asciiParse = locData->digitSequence(toParse, AllowSign);
1623 if (asciiParse.endIndex() != toParse.size())
1625 const uint priorFields = seenFields;
1627 const qsizetype spare
1628 = scanMatchedFields(asciiField, asciiParse,
1629 !txtSkipped && txtInd + 2 >= strPat.size() && sep.isEmpty(),
1630 hour, minute, second, sign);
1639 if (spare == 1 && (seenFields & Hour)) {
1641 }
else if (strPat.back() < 0) {
1642 if (sep.isEmpty() || !(priorFields & Hour)
1643 || strPat.back() != -sep.size() || !str.endsWith(sep)) {
1646 int offset = hour * 60;
1647 if (priorFields & Minute)
1650 if (priorFields & Second)
1652 return R{ sign * offset, txtPos };
1656 txtPos += asciiParse.digitBounds[asciiParse.digits.size() - spare];
1660 strPos += asciiField.endIndex();
1661 txtPos += asciiParse.endIndex();
1666 return R{ sign * (second + 60 * (minute + 60 * hour)), txtPos };
1669QTimeZonePrivate::NamePrefixMatch
1670findOffsetPrefixImpl(QStringView text,
const QLocale &locale,
1671 QtTemporalPattern::TemporalFieldFlags flags)
1673 QTimeZonePrivate::NamePrefixMatch best;
1683 const QUtcTimeZonePrivate greenwich(0);
1685 const QUtcTimeZonePrivate positive(+PatternAligner::OffsetMagnitude);
1686 const QUtcTimeZonePrivate negative(-PatternAligner::OffsetMagnitude);
1688 constexpr QTimeZone::NameType formats[] = {
1689 QTimeZone::OffsetName, QTimeZone::LongName, QTimeZone::ShortName
1691 constexpr QTimeZone::TimeType seasons[] = {
1692 QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime
1695 const auto acceptFormat = [flags](QTimeZone::NameType format) {
1696 using namespace QtTemporalPattern;
1698 if (!flags.testAnyFlags(FieldGroup::WidthMask | FieldGroup::FormMask))
1700 using Flag = TemporalFieldFlag;
1701 constexpr TemporalFieldFlags Textual = Flag::Verbal | Flag::Standalone;
1702 constexpr TemporalFieldFlags Long = Flag::Abbreviated | Flag::Short | Flag::Wide;
1704 case QTimeZone::OffsetName:
1705 return matchesFlagWithin(flags, Flag::Numeric, FieldGroup::FormMask);
1706 case QTimeZone::DefaultName:
1707 case QTimeZone::LongName:
1708 return matchesFlagsWithin(flags, Textual, FieldGroup::FormMask)
1709 && matchesFlagsWithin(flags, Long, FieldGroup::WidthMask);
1710 case QTimeZone::ShortName:
1711 return matchesFlagsWithin(flags, Textual, FieldGroup::FormMask)
1712 && matchesFlagWithin(flags, Flag::Narrow, FieldGroup::WidthMask);
1714 Q_UNREACHABLE_RETURN(
false);
1716 const auto acceptSeason = [flags](QTimeZone::TimeType season) {
1717 using namespace QtTemporalPattern;
1719 if (!flags.testAnyFlags(FieldGroup::SeasonMask))
1721 using Flag = TemporalFieldFlag;
1723 case QTimeZone::GenericTime:
1724 return flags.testFlag(Flag::GenericTime);
1725 case QTimeZone::StandardTime:
1726 return flags.testFlag(Flag::StandardTime);
1727 case QTimeZone::DaylightTime:
1728 return flags.testFlag(Flag::DaylightSavingTime);
1730 Q_UNREACHABLE_RETURN(
false);
1732 QString bestCandidate;
1735
1736
1737
1738
1739
1740 QLocale digitLocale = locale;
1741 for (
int i = 0; i < 2; ++i) {
1746 const NumericPattern textPattern(text, digitLocale);
1747 Q_ASSERT(!textPattern.pattern.isEmpty());
1748 PatternAligner aligner(text, textPattern.pattern, flags);
1752 const auto consider = [&best, &aligner, &bestCandidate,
1753 txtSign = textPattern.sign, digitLocale]
1754 (
const QString &candidate,
char sign, QTimeZone::TimeType season) {
1755 const auto idForOffset = [](
int offsetSeconds) ->
QByteArray {
1758 return QTimeZonePrivate::isoOffsetFormat(offsetSeconds,
1759 QTimeZone::OffsetName).toLatin1();
1761 const auto localeDataFor = [loc = digitLocale] (
const NumericPattern &pat) {
1762 if (pat.hasDigits) {
1763 if (pat.digitsAreLocale)
1764 return QLocalePrivate::get(loc)->m_data;
1765 return QLocaleData::c();
1767 return static_cast<
const QLocaleData *>(
nullptr);
1769 if (
const NumericPattern pat(candidate, digitLocale);
1771 (pat.sign & sign) != sign || (txtSign & sign) == sign) {
1772 const auto parsed = aligner.match(
1773 candidate, pat.pattern, localeDataFor(pat), pat.sign);
1774 if (parsed && parsed.length > best.nameLength) {
1777 const int offset = sign ==
'-' ? -parsed.offset : parsed.offset;
1778 best = { idForOffset(offset), parsed.length, season, offset };
1779 bestCandidate = candidate;
1781 return pat.digitsAreLocale;
1786 bool nativeSeen =
false;
1787 for (
auto season : seasons) {
1788 if (!acceptSeason(season))
1790 for (
auto format : formats) {
1791 if (!acceptFormat(format))
1793 if (
const QString pos = positive.displayName(season, format, locale);
1794 pos.size() > best.nameLength) {
1795 if (pos != bestCandidate && consider(pos,
'+', season))
1798 if (
const QString neg = negative.displayName(season, format, locale);
1799 neg.size() > best.nameLength) {
1800 if (neg != bestCandidate && consider(neg,
'-', season))
1803 if (
const QString nul = greenwich.displayName(season, format, locale);
1804 nul.size() > best.nameLength) {
1805 if (nul != bestCandidate && aligner.compare(nul)) {
1806 best = {
"UTC"_ba, nul.size(), QTimeZone::GenericTime, 0 };
1807 bestCandidate = nul;
1810 if (best.nameLength == text.size())
1817 if (i == 0 && (digitLocale.zeroDigit() == u'0' || !nativeSeen))
1819 digitLocale = QLocale::c();
1826QTimeZonePrivate::NamePrefixMatch
1827QTimeZonePrivate::findOffsetPrefix(QStringView text,
const QLocale &locale,
1828 QtTemporalPattern::TemporalFieldFlags flags)
1830 NamePrefixMatch best;
1831 if (
auto match = findOffsetPrefixImpl(text, locale, flags))
1832 best = std::move(match);
1833 if (
auto match = findOffsetPrefixImpl(text, QLocale::c(), flags);
1834 match.nameLength > best.nameLength) {
1835 best = std::move(match);
1842QTimeZonePrivate::NamePrefixMatch
1843QTimeZonePrivate::findLongUtcPrefix(QStringView text)
1845 if (text.startsWith(u"UTC")) {
1846 if (text.size() > 4 && (text[3] == u'+' || text[3] == u'-')) {
1848 const auto digitAt = [text](qsizetype index) {
1849 using QtMiscUtils::isAsciiDigit;
1850 return index < text.size() && isAsciiDigit(text[index].unicode());
1852 qsizetype length = 3;
1853 constexpr int MaxGroups = 3;
1855 int fields[MaxGroups] = {0, 0, 0};
1858 Q_ASSERT(length < text.size());
1859 if (!digitAt(length + 1))
1861 QStringView tail = text.sliced(length + 1);
1862 qsizetype digits = digitAt(length + 2) ? 2 : 1;
1864 const qsizetype minDigits = groups ? 2 : 1;
1865 while (digits >= minDigits) {
1867 const unsigned short field = tail.first(digits).toUShort(&ok);
1868 if (ok && field < (groups ? 60 : 24)) {
1869 fields[groups] = field;
1875 if (digits < minDigits)
1878 length += 1 + digits;
1879 }
while (++groups < MaxGroups && length < text.size() && text[length] == u':');
1881 int offset = (fields[0] * 60 + fields[1]) * 60 + fields[2];
1882 if (text[3] == u'-')
1884 return { text.first(length).toLatin1(), length, QTimeZone::GenericTime, offset };
1887 return { utcQByteArray(), 3 };
1893QByteArrayView QTimeZonePrivate::aliasToIana(QByteArrayView alias)
1895 const auto data = std::lower_bound(std::begin(aliasMappingTable), std::end(aliasMappingTable),
1896 alias, earlierAliasId);
1897 if (data != std::end(aliasMappingTable) && data->aliasId() == alias)
1898 return data->ianaId();
1905QByteArrayView QTimeZonePrivate::ianaIdToWindowsId(QByteArrayView id)
1907 const auto idUtf8 = QUtf8StringView(id);
1909 for (
const ZoneData &data : zoneDataTable) {
1910 for (
auto l1 : data.ids()) {
1912 return toWindowsIdLiteral(data.windowsIdKey);
1920QByteArrayView QTimeZonePrivate::windowsIdToDefaultIanaId(QByteArrayView windowsId)
1922 const auto data = std::lower_bound(std::begin(windowsDataTable), std::end(windowsDataTable),
1923 windowsId, earlierWindowsId);
1924 if (data != std::end(windowsDataTable) && data->windowsId() == windowsId) {
1925 QByteArrayView id = data->ianaId();
1926 Q_ASSERT(id.indexOf(
' ') == -1);
1932QByteArrayView QTimeZonePrivate::windowsIdToDefaultIanaId(QByteArrayView windowsId,
1933 QLocale::Territory territory)
1936 if (territory == QLocale::World) {
1938 return windowsIdToDefaultIanaId(windowsId);
1941 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1942 const qint16 land =
static_cast<quint16>(territory);
1943 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1944 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1947 if (data->territory == land)
1948 return *data->ids().begin();
1954QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(QByteArrayView windowsId)
1956 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1957 QList<QByteArray> list;
1959 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1960 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1962 for (
auto l1 : data->ids())
1963 list << QByteArray(l1.data(), l1.size());
1970 std::sort(list.begin(), list.end());
1974QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(QByteArrayView windowsId,
1975 QLocale::Territory territory)
1978 QList<QByteArray> list;
1979 if (territory == QLocale::World) {
1981 list << windowsIdToDefaultIanaId(windowsId).toByteArray();
1983 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1984 const qint16 land =
static_cast<quint16>(territory);
1985 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1986 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1989 if (data->territory == land) {
1990 for (
auto l1 : data->ids())
1991 list << QByteArray(l1.data(), l1.size());
2003 while ((cut = ianaIds.indexOf(
' ')) >= 0) {
2004 if (id == ianaIds.first(cut))
2006 ianaIds = ianaIds.sliced(cut + 1);
2008 return id == ianaIds;
2012
2013
2014
2015
2016
2017
2020QUtcTimeZonePrivate::QUtcTimeZonePrivate()
2022 const QString name = utcQString();
2023 init(utcQByteArray(), 0, name, name, QLocale::AnyTerritory, name);
2027QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QByteArray &id)
2030 for (
const UtcData &data : utcDataTable) {
2031 if (isEntryInIanaList(id, data.id())) {
2032 QString name = QString::fromUtf8(id);
2033 init(id, data.offsetFromUtc, name, name, QLocale::AnyTerritory, name);
2041qint64 QUtcTimeZonePrivate::offsetFromUtcString(QByteArrayView id)
2046 if (!id.startsWith(
"UTC") || id.size() < 5)
2047 return invalidSeconds();
2048 const char signChar = id.at(3);
2049 if (signChar !=
'-' && signChar !=
'+')
2050 return invalidSeconds();
2051 const int sign = signChar ==
'-' ? -1 : 1;
2055 for (
auto offset : QLatin1StringView(id.mid(4)).tokenize(
':'_L1)) {
2056 if (offset.size() > 2 || (prior && offset.size() < 2))
2057 return invalidSeconds();
2059 unsigned short field = offset.toUShort(&ok);
2061 if (!ok || field >= (prior ? 60 : 24))
2062 return invalidSeconds();
2063 seconds = seconds * 60 + field;
2065 return invalidSeconds();
2069 return invalidSeconds();
2074 return seconds * sign;
2078QUtcTimeZonePrivate::QUtcTimeZonePrivate(qint32 offsetSeconds)
2083 const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
2084 offsetSeconds, atLowerUtcOffset);
2085 if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
2086 QByteArrayView ianaId = data->id();
2087 qsizetype cut = ianaId.indexOf(
' ');
2088 QByteArrayView cutId = (cut < 0 ? ianaId : ianaId.first(cut));
2089 if (cutId == utcQByteArray()) {
2091 id = utcQByteArray();
2092 name = utcQString();
2095 id = cutId.toByteArray();
2096 name = QString::fromUtf8(id);
2098 Q_ASSERT(!name.isEmpty());
2100 name = isoOffsetFormat(offsetSeconds, QTimeZone::OffsetName);
2103 init(id, offsetSeconds, name, name, QLocale::AnyTerritory, name);
2106QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QByteArray &zoneId,
int offsetSeconds,
2107 const QString &name,
const QString &abbreviation,
2108 QLocale::Territory territory,
const QString &comment)
2110 init(zoneId, offsetSeconds, name, abbreviation, territory, comment);
2113QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QUtcTimeZonePrivate &other)
2114 : QTimeZonePrivate(other), m_name(other.m_name),
2115 m_abbreviation(other.m_abbreviation),
2116 m_comment(other.m_comment),
2117 m_territory(other.m_territory),
2118 m_offsetFromUtc(other.m_offsetFromUtc)
2122QUtcTimeZonePrivate::~QUtcTimeZonePrivate()
2126QUtcTimeZonePrivate *QUtcTimeZonePrivate::clone()
const
2128 return new QUtcTimeZonePrivate(*
this);
2131QTimeZonePrivate::Data QUtcTimeZonePrivate::data(qint64 forMSecsSinceEpoch)
const
2134 d.abbreviation = m_abbreviation;
2135 d.atMSecsSinceEpoch = forMSecsSinceEpoch;
2136 d.standardTimeOffset = d.offsetFromUtc = m_offsetFromUtc;
2137 d.daylightTimeOffset = 0;
2142QTimeZonePrivate::Data QUtcTimeZonePrivate::data(QTimeZone::TimeType timeType)
const
2145 return data(QDateTime::currentMSecsSinceEpoch());
2148bool QUtcTimeZonePrivate::isDataLocale(
const QLocale &locale)
const
2151 return isAnglicLocale(locale);
2154void QUtcTimeZonePrivate::init(
const QByteArray &zoneId,
int offsetSeconds,
const QString &name,
2155 const QString &abbreviation, QLocale::Territory territory,
2156 const QString &comment)
2159 m_offsetFromUtc = offsetSeconds;
2161 m_abbreviation = abbreviation;
2162 m_territory = territory;
2163 m_comment = comment;
2166QLocale::Territory QUtcTimeZonePrivate::territory()
const
2171QString QUtcTimeZonePrivate::comment()
const
2177QString QUtcTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch,
2178 QTimeZone::NameType nameType,
2179 const QLocale &locale)
const
2181 Q_UNUSED(atMSecsSinceEpoch);
2182 return displayName(QTimeZone::StandardTime, nameType, locale);
2185QString QUtcTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
2186 QTimeZone::NameType nameType,
2187 const QLocale &locale)
const
2189#if QT_CONFIG(timezone_locale)
2196 m_offsetFromUtc != 0 ? QString() :
2198 QTimeZonePrivate::displayName(timeType, nameType, locale);
2205 const auto matchesFallback = [](
int offset, QStringView name) {
2207 int seconds = offset % 60;
2208 int rounded = offset
2209 + (seconds > 30 || (seconds == 30 && (offset / 60) % 2)
2211 : (seconds < -30 || (seconds == -30 && (offset / 60) % 2)
2214 const QString avoid = isoOffsetFormat(rounded);
2217 Q_ASSERT(avoid.startsWith(
"UTC"_L1));
2218 Q_ASSERT(avoid.size() == 9);
2221 if (!(name.startsWith(
"GMT"_L1) || name.startsWith(
"UTC"_L1)) || name.size() < 5)
2224 QStringView tail{avoid};
2225 tail = tail.sliced(3);
2226 if (name.sliced(3) == tail)
2228 while (tail.endsWith(
":00"_L1))
2229 tail = tail.chopped(3);
2230 while (name.endsWith(
":00"_L1))
2231 name = name.chopped(3);
2235 const QChar sign = name[3] == u'\u2212' ? u'-' : name[3];
2237 return sign == tail[0] && tail.sliced(tail[1] == u'0' ? 2 : 1) == name.sliced(4);
2239 if (!name.isEmpty() && (m_name.isEmpty() || !matchesFallback(m_offsetFromUtc, name)))
2245 if (nameType == QTimeZone::ShortName)
2246 return m_abbreviation;
2247 if (nameType == QTimeZone::OffsetName)
2248 return isoOffsetFormat(m_offsetFromUtc);
2252QString QUtcTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch)
const
2254 Q_UNUSED(atMSecsSinceEpoch);
2255 return m_abbreviation;
2258qint32 QUtcTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch)
const
2260 Q_UNUSED(atMSecsSinceEpoch);
2261 return m_offsetFromUtc;
2264qint32 QUtcTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch)
const
2266 Q_UNUSED(atMSecsSinceEpoch);
2270QByteArray QUtcTimeZonePrivate::systemTimeZoneId()
const
2273 const emscripten::val date = emscripten::val::global(
"Date").new_();
2274 if (date.isUndefined())
2275 return utcQByteArray();
2278 const int offsetSeconds = -date.call<
int>(
"getTimezoneOffset") * 60;
2279 if (offsetSeconds == 0)
2280 return utcQByteArray();
2281 return isoOffsetFormat(offsetSeconds).toUtf8();
2283 return utcQByteArray();
2287bool QUtcTimeZonePrivate::isTimeZoneIdAvailable(QByteArrayView ianaId)
const
2290 for (
const UtcData &data : utcDataTable) {
2291 if (isEntryInIanaList(ianaId, data.id()))
2300QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds()
const
2303 QList<QByteArray> result;
2304 result.reserve(std::size(utcDataTable));
2305 for (
const UtcData &data : utcDataTable) {
2306 QByteArrayView id = data.id();
2308 while ((cut = id.indexOf(
' ')) >= 0) {
2309 result << id.first(cut).toByteArray();
2310 id = id.sliced(cut + 1);
2312 result << id.toByteArray();
2315 std::sort(result.begin(), result.end());
2320QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(QLocale::Territory country)
const
2323 if (country == QLocale::AnyTerritory)
2324 return availableTimeZoneIds();
2325 return QList<QByteArray>();
2328QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(qint32 offsetSeconds)
const
2332 QList<QByteArray> result;
2333 const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
2334 offsetSeconds, atLowerUtcOffset);
2335 if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
2336 QByteArrayView id = data->id();
2338 while ((cut = id.indexOf(
' ')) >= 0) {
2339 result << id.first(cut).toByteArray();
2340 id = id.sliced(cut + 1);
2342 result << id.toByteArray();
2347 QByteArray isoName = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName).toUtf8();
2348 if (offsetFromUtcString(isoName) == qint64(offsetSeconds) && !result.contains(isoName))
2351 std::sort(result.begin(), result.end());
2356#ifndef QT_NO_DATASTREAM
2357void QUtcTimeZonePrivate::serialize(QDataStream &ds)
const
2359 ds <<
QStringLiteral(
"OffsetFromUtc") << QString::fromUtf8(m_id) << m_offsetFromUtc << m_name
2360 << 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