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;
1559 const bool maybeSep = txtInd > 0 && txtInd + 1 < strPat.size();
1562 if (!sep.isEmpty() && str.sliced(strPos, -len) != sep) {
1563 if (!scanExtraFields(sep, locData, txtLen, second))
1566 if (maybeSep && sep.isEmpty())
1567 sep = str.sliced(strPos, -len);
1569 if (!textMatch(str, strPos, -len, -txtLen)) {
1572 if (locData && !sep.isEmpty() && str.sliced(strPos, -len) == sep) {
1579 if (txtInd + 1 < strPat.size() - txtSkipped || len <= txtLen
1580 || !textMatch(str, strPos, -len, -len)) {
1594 if (txtPos >= txt.size()) {
1595 Q_ASSERT(txtInd >= txtPat.size());
1597 if (!sep.isEmpty() && txtPat.back() == sep.size() && txt.endsWith(sep)
1598 && strPat.back() == -sep.size() && str.endsWith(sep)) {
1603 txtInd = txtPat.size() - 1;
1604 txtPos -= sep.size();
1614 QStringView field = str.sliced(strPos, len);
1615 QStringView toParse = txt.sliced(txtPos, txtPat.at(txtInd));
1618 const Digits asciiField = locData->digitSequence(field, AllowSign);
1619 if (asciiField.endIndex() != field.size())
1621 const Digits asciiParse = locData->digitSequence(toParse, AllowSign);
1622 if (asciiParse.endIndex() != toParse.size())
1624 const uint priorFields = seenFields;
1626 const qsizetype spare
1627 = scanMatchedFields(asciiField, asciiParse,
1628 !txtSkipped && txtInd + 2 >= strPat.size() && sep.isEmpty(),
1629 hour, minute, second, sign);
1638 if (spare == 1 && (seenFields & Hour)) {
1640 }
else if (strPat.back() < 0) {
1641 if (sep.isEmpty() || !(priorFields & Hour)
1642 || strPat.back() != -sep.size() || !str.endsWith(sep)) {
1645 int offset = hour * 60;
1646 if (priorFields & Minute)
1649 if (priorFields & Second)
1651 return R{ sign * offset, txtPos };
1655 txtPos += asciiParse.digitBounds[asciiParse.digits.size() - spare];
1659 strPos += asciiField.endIndex();
1660 txtPos += asciiParse.endIndex();
1665 return R{ sign * (second + 60 * (minute + 60 * hour)), txtPos };
1668QTimeZonePrivate::NamePrefixMatch
1669findOffsetPrefixImpl(QStringView text,
const QLocale &locale,
1670 QtTemporalPattern::TemporalFieldFlags flags)
1672 QTimeZonePrivate::NamePrefixMatch best;
1682 const QUtcTimeZonePrivate greenwich(0);
1684 const QUtcTimeZonePrivate positive(+PatternAligner::OffsetMagnitude);
1685 const QUtcTimeZonePrivate negative(-PatternAligner::OffsetMagnitude);
1687 constexpr QTimeZone::NameType formats[] = {
1688 QTimeZone::OffsetName, QTimeZone::LongName, QTimeZone::ShortName
1690 constexpr QTimeZone::TimeType seasons[] = {
1691 QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime
1694 const auto acceptFormat = [flags](QTimeZone::NameType format) {
1695 using namespace QtTemporalPattern;
1697 if (!flags.testAnyFlags(FieldGroup::WidthMask | FieldGroup::FormMask))
1699 using Flag = TemporalFieldFlag;
1700 constexpr TemporalFieldFlags Textual = Flag::Verbal | Flag::Standalone;
1701 constexpr TemporalFieldFlags Long = Flag::Abbreviated | Flag::Short | Flag::Wide;
1703 case QTimeZone::OffsetName:
1704 return matchesFlagWithin(flags, Flag::Numeric, FieldGroup::FormMask);
1705 case QTimeZone::DefaultName:
1706 case QTimeZone::LongName:
1707 return matchesFlagsWithin(flags, Textual, FieldGroup::FormMask)
1708 && matchesFlagsWithin(flags, Long, FieldGroup::WidthMask);
1709 case QTimeZone::ShortName:
1710 return matchesFlagsWithin(flags, Textual, FieldGroup::FormMask)
1711 && matchesFlagWithin(flags, Flag::Narrow, FieldGroup::WidthMask);
1713 Q_UNREACHABLE_RETURN(
false);
1715 const auto acceptSeason = [flags](QTimeZone::TimeType season) {
1716 using namespace QtTemporalPattern;
1718 if (!flags.testAnyFlags(FieldGroup::SeasonMask))
1720 using Flag = TemporalFieldFlag;
1722 case QTimeZone::GenericTime:
1723 return flags.testFlag(Flag::GenericTime);
1724 case QTimeZone::StandardTime:
1725 return flags.testFlag(Flag::StandardTime);
1726 case QTimeZone::DaylightTime:
1727 return flags.testFlag(Flag::DaylightSavingTime);
1729 Q_UNREACHABLE_RETURN(
false);
1733
1734
1735
1736
1737
1738 QLocale digitLocale = locale;
1739 for (
int i = 0; i < 2; ++i) {
1744 const NumericPattern textPattern(text, digitLocale);
1745 Q_ASSERT(!textPattern.pattern.isEmpty());
1746 PatternAligner aligner(text, textPattern.pattern, flags);
1750 const auto consider = [&best, &aligner, txtSign = textPattern.sign, digitLocale]
1751 (QStringView candidate,
char sign, QTimeZone::TimeType season) {
1752 const auto idForOffset = [](
int offsetSeconds) ->
QByteArray {
1755 return QTimeZonePrivate::isoOffsetFormat(offsetSeconds,
1756 QTimeZone::OffsetName).toLatin1();
1758 const auto localeDataFor = [loc = digitLocale] (
const NumericPattern &pat) {
1759 if (pat.hasDigits) {
1760 if (pat.digitsAreLocale)
1761 return QLocalePrivate::get(loc)->m_data;
1762 return QLocaleData::c();
1764 return static_cast<
const QLocaleData *>(
nullptr);
1766 if (
const NumericPattern pat(candidate, digitLocale);
1768 (pat.sign & sign) != sign || (txtSign & sign) == sign) {
1769 const auto parsed = aligner.match(
1770 candidate, pat.pattern, localeDataFor(pat), pat.sign);
1771 if (parsed && parsed.length > best.nameLength) {
1774 const int offset = sign ==
'-' ? -parsed.offset : parsed.offset;
1775 best = { idForOffset(offset), parsed.length, season, offset };
1777 return pat.digitsAreLocale;
1782 bool nativeSeen =
false;
1783 for (
auto season : seasons) {
1784 if (!acceptSeason(season))
1786 for (
auto format : formats) {
1787 if (!acceptFormat(format))
1789 if (
const QString pos = positive.displayName(season, format, locale);
1790 pos.size() > best.nameLength) {
1791 if (consider(pos,
'+', season))
1794 if (
const QString neg = negative.displayName(season, format, locale);
1795 neg.size() > best.nameLength) {
1796 if (consider(neg,
'-', season))
1799 if (
const QString nul = greenwich.displayName(season, format, locale);
1800 nul.size() > best.nameLength) {
1801 if (aligner.compare(nul))
1802 best = {
"UTC"_ba, nul.size(), QTimeZone::GenericTime, 0 };
1804 if (best.nameLength == text.size())
1811 if (i == 0 && (digitLocale.zeroDigit() == u'0' || !nativeSeen))
1813 digitLocale = QLocale::c();
1820QTimeZonePrivate::NamePrefixMatch
1821QTimeZonePrivate::findOffsetPrefix(QStringView text,
const QLocale &locale,
1822 QtTemporalPattern::TemporalFieldFlags flags)
1824 NamePrefixMatch best;
1825 if (
auto match = findOffsetPrefixImpl(text, locale, flags))
1826 best = std::move(match);
1827 if (
auto match = findOffsetPrefixImpl(text, QLocale::c(), flags);
1828 match.nameLength > best.nameLength) {
1829 best = std::move(match);
1836QTimeZonePrivate::NamePrefixMatch
1837QTimeZonePrivate::findLongUtcPrefix(QStringView text)
1839 if (text.startsWith(u"UTC")) {
1840 if (text.size() > 4 && (text[3] == u'+' || text[3] == u'-')) {
1842 const auto digitAt = [text](qsizetype index) {
1843 using QtMiscUtils::isAsciiDigit;
1844 return index < text.size() && isAsciiDigit(text[index].unicode());
1846 qsizetype length = 3;
1847 constexpr int MaxGroups = 3;
1849 int fields[MaxGroups] = {0, 0, 0};
1852 Q_ASSERT(length < text.size());
1853 if (!digitAt(length + 1))
1855 QStringView tail = text.sliced(length + 1);
1856 qsizetype digits = digitAt(length + 2) ? 2 : 1;
1858 const qsizetype minDigits = groups ? 2 : 1;
1859 while (digits >= minDigits) {
1861 const unsigned short field = tail.first(digits).toUShort(&ok);
1862 if (ok && field < (groups ? 60 : 24)) {
1863 fields[groups] = field;
1869 if (digits < minDigits)
1872 length += 1 + digits;
1873 }
while (++groups < MaxGroups && length < text.size() && text[length] == u':');
1875 int offset = (fields[0] * 60 + fields[1]) * 60 + fields[2];
1876 if (text[3] == u'-')
1878 return { text.first(length).toLatin1(), length, QTimeZone::GenericTime, offset };
1881 return { utcQByteArray(), 3 };
1887QByteArrayView QTimeZonePrivate::aliasToIana(QByteArrayView alias)
1889 const auto data = std::lower_bound(std::begin(aliasMappingTable), std::end(aliasMappingTable),
1890 alias, earlierAliasId);
1891 if (data != std::end(aliasMappingTable) && data->aliasId() == alias)
1892 return data->ianaId();
1899QByteArrayView QTimeZonePrivate::ianaIdToWindowsId(QByteArrayView id)
1901 const auto idUtf8 = QUtf8StringView(id);
1903 for (
const ZoneData &data : zoneDataTable) {
1904 for (
auto l1 : data.ids()) {
1906 return toWindowsIdLiteral(data.windowsIdKey);
1914QByteArrayView QTimeZonePrivate::windowsIdToDefaultIanaId(QByteArrayView windowsId)
1916 const auto data = std::lower_bound(std::begin(windowsDataTable), std::end(windowsDataTable),
1917 windowsId, earlierWindowsId);
1918 if (data != std::end(windowsDataTable) && data->windowsId() == windowsId) {
1919 QByteArrayView id = data->ianaId();
1920 Q_ASSERT(id.indexOf(
' ') == -1);
1926QByteArrayView QTimeZonePrivate::windowsIdToDefaultIanaId(QByteArrayView windowsId,
1927 QLocale::Territory territory)
1930 if (territory == QLocale::World) {
1932 return windowsIdToDefaultIanaId(windowsId);
1935 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1936 const qint16 land =
static_cast<quint16>(territory);
1937 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1938 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1941 if (data->territory == land)
1942 return *data->ids().begin();
1948QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(QByteArrayView windowsId)
1950 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1951 QList<QByteArray> list;
1953 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1954 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1956 for (
auto l1 : data->ids())
1957 list << QByteArray(l1.data(), l1.size());
1964 std::sort(list.begin(), list.end());
1968QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(QByteArrayView windowsId,
1969 QLocale::Territory territory)
1972 QList<QByteArray> list;
1973 if (territory == QLocale::World) {
1975 list << windowsIdToDefaultIanaId(windowsId).toByteArray();
1977 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1978 const qint16 land =
static_cast<quint16>(territory);
1979 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1980 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1983 if (data->territory == land) {
1984 for (
auto l1 : data->ids())
1985 list << QByteArray(l1.data(), l1.size());
1997 while ((cut = ianaIds.indexOf(
' ')) >= 0) {
1998 if (id == ianaIds.first(cut))
2000 ianaIds = ianaIds.sliced(cut + 1);
2002 return id == ianaIds;
2006
2007
2008
2009
2010
2011
2014QUtcTimeZonePrivate::QUtcTimeZonePrivate()
2016 const QString name = utcQString();
2017 init(utcQByteArray(), 0, name, name, QLocale::AnyTerritory, name);
2021QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QByteArray &id)
2024 for (
const UtcData &data : utcDataTable) {
2025 if (isEntryInIanaList(id, data.id())) {
2026 QString name = QString::fromUtf8(id);
2027 init(id, data.offsetFromUtc, name, name, QLocale::AnyTerritory, name);
2035qint64 QUtcTimeZonePrivate::offsetFromUtcString(QByteArrayView id)
2040 if (!id.startsWith(
"UTC") || id.size() < 5)
2041 return invalidSeconds();
2042 const char signChar = id.at(3);
2043 if (signChar !=
'-' && signChar !=
'+')
2044 return invalidSeconds();
2045 const int sign = signChar ==
'-' ? -1 : 1;
2049 for (
auto offset : QLatin1StringView(id.mid(4)).tokenize(
':'_L1)) {
2050 if (offset.size() > 2 || (prior && offset.size() < 2))
2051 return invalidSeconds();
2053 unsigned short field = offset.toUShort(&ok);
2055 if (!ok || field >= (prior ? 60 : 24))
2056 return invalidSeconds();
2057 seconds = seconds * 60 + field;
2059 return invalidSeconds();
2063 return invalidSeconds();
2068 return seconds * sign;
2072QUtcTimeZonePrivate::QUtcTimeZonePrivate(qint32 offsetSeconds)
2077 const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
2078 offsetSeconds, atLowerUtcOffset);
2079 if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
2080 QByteArrayView ianaId = data->id();
2081 qsizetype cut = ianaId.indexOf(
' ');
2082 QByteArrayView cutId = (cut < 0 ? ianaId : ianaId.first(cut));
2083 if (cutId == utcQByteArray()) {
2085 id = utcQByteArray();
2086 name = utcQString();
2089 id = cutId.toByteArray();
2090 name = QString::fromUtf8(id);
2092 Q_ASSERT(!name.isEmpty());
2094 name = isoOffsetFormat(offsetSeconds, QTimeZone::OffsetName);
2097 init(id, offsetSeconds, name, name, QLocale::AnyTerritory, name);
2100QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QByteArray &zoneId,
int offsetSeconds,
2101 const QString &name,
const QString &abbreviation,
2102 QLocale::Territory territory,
const QString &comment)
2104 init(zoneId, offsetSeconds, name, abbreviation, territory, comment);
2107QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QUtcTimeZonePrivate &other)
2108 : QTimeZonePrivate(other), m_name(other.m_name),
2109 m_abbreviation(other.m_abbreviation),
2110 m_comment(other.m_comment),
2111 m_territory(other.m_territory),
2112 m_offsetFromUtc(other.m_offsetFromUtc)
2116QUtcTimeZonePrivate::~QUtcTimeZonePrivate()
2120QUtcTimeZonePrivate *QUtcTimeZonePrivate::clone()
const
2122 return new QUtcTimeZonePrivate(*
this);
2125QTimeZonePrivate::Data QUtcTimeZonePrivate::data(qint64 forMSecsSinceEpoch)
const
2128 d.abbreviation = m_abbreviation;
2129 d.atMSecsSinceEpoch = forMSecsSinceEpoch;
2130 d.standardTimeOffset = d.offsetFromUtc = m_offsetFromUtc;
2131 d.daylightTimeOffset = 0;
2136QTimeZonePrivate::Data QUtcTimeZonePrivate::data(QTimeZone::TimeType timeType)
const
2139 return data(QDateTime::currentMSecsSinceEpoch());
2142bool QUtcTimeZonePrivate::isDataLocale(
const QLocale &locale)
const
2145 return isAnglicLocale(locale);
2148void QUtcTimeZonePrivate::init(
const QByteArray &zoneId,
int offsetSeconds,
const QString &name,
2149 const QString &abbreviation, QLocale::Territory territory,
2150 const QString &comment)
2153 m_offsetFromUtc = offsetSeconds;
2155 m_abbreviation = abbreviation;
2156 m_territory = territory;
2157 m_comment = comment;
2160QLocale::Territory QUtcTimeZonePrivate::territory()
const
2165QString QUtcTimeZonePrivate::comment()
const
2171QString QUtcTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch,
2172 QTimeZone::NameType nameType,
2173 const QLocale &locale)
const
2175 Q_UNUSED(atMSecsSinceEpoch);
2176 return displayName(QTimeZone::StandardTime, nameType, locale);
2179QString QUtcTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
2180 QTimeZone::NameType nameType,
2181 const QLocale &locale)
const
2183#if QT_CONFIG(timezone_locale)
2190 m_offsetFromUtc != 0 ? QString() :
2192 QTimeZonePrivate::displayName(timeType, nameType, locale);
2199 const auto matchesFallback = [](
int offset, QStringView name) {
2201 int seconds = offset % 60;
2202 int rounded = offset
2203 + (seconds > 30 || (seconds == 30 && (offset / 60) % 2)
2205 : (seconds < -30 || (seconds == -30 && (offset / 60) % 2)
2208 const QString avoid = isoOffsetFormat(rounded);
2211 Q_ASSERT(avoid.startsWith(
"UTC"_L1));
2212 Q_ASSERT(avoid.size() == 9);
2215 if (!(name.startsWith(
"GMT"_L1) || name.startsWith(
"UTC"_L1)) || name.size() < 5)
2218 QStringView tail{avoid};
2219 tail = tail.sliced(3);
2220 if (name.sliced(3) == tail)
2222 while (tail.endsWith(
":00"_L1))
2223 tail = tail.chopped(3);
2224 while (name.endsWith(
":00"_L1))
2225 name = name.chopped(3);
2229 const QChar sign = name[3] == u'\u2212' ? u'-' : name[3];
2231 return sign == tail[0] && tail.sliced(tail[1] == u'0' ? 2 : 1) == name.sliced(4);
2233 if (!name.isEmpty() && (m_name.isEmpty() || !matchesFallback(m_offsetFromUtc, name)))
2239 if (nameType == QTimeZone::ShortName)
2240 return m_abbreviation;
2241 if (nameType == QTimeZone::OffsetName)
2242 return isoOffsetFormat(m_offsetFromUtc);
2246QString QUtcTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch)
const
2248 Q_UNUSED(atMSecsSinceEpoch);
2249 return m_abbreviation;
2252qint32 QUtcTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch)
const
2254 Q_UNUSED(atMSecsSinceEpoch);
2255 return m_offsetFromUtc;
2258qint32 QUtcTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch)
const
2260 Q_UNUSED(atMSecsSinceEpoch);
2264QByteArray QUtcTimeZonePrivate::systemTimeZoneId()
const
2267 const emscripten::val date = emscripten::val::global(
"Date").new_();
2268 if (date.isUndefined())
2269 return utcQByteArray();
2272 const int offsetSeconds = -date.call<
int>(
"getTimezoneOffset") * 60;
2273 if (offsetSeconds == 0)
2274 return utcQByteArray();
2275 return isoOffsetFormat(offsetSeconds).toUtf8();
2277 return utcQByteArray();
2281bool QUtcTimeZonePrivate::isTimeZoneIdAvailable(QByteArrayView ianaId)
const
2284 for (
const UtcData &data : utcDataTable) {
2285 if (isEntryInIanaList(ianaId, data.id()))
2294QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds()
const
2297 QList<QByteArray> result;
2298 result.reserve(std::size(utcDataTable));
2299 for (
const UtcData &data : utcDataTable) {
2300 QByteArrayView id = data.id();
2302 while ((cut = id.indexOf(
' ')) >= 0) {
2303 result << id.first(cut).toByteArray();
2304 id = id.sliced(cut + 1);
2306 result << id.toByteArray();
2309 std::sort(result.begin(), result.end());
2314QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(QLocale::Territory country)
const
2317 if (country == QLocale::AnyTerritory)
2318 return availableTimeZoneIds();
2319 return QList<QByteArray>();
2322QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(qint32 offsetSeconds)
const
2326 QList<QByteArray> result;
2327 const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
2328 offsetSeconds, atLowerUtcOffset);
2329 if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
2330 QByteArrayView id = data->id();
2332 while ((cut = id.indexOf(
' ')) >= 0) {
2333 result << id.first(cut).toByteArray();
2334 id = id.sliced(cut + 1);
2336 result << id.toByteArray();
2341 QByteArray isoName = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName).toUtf8();
2342 if (offsetFromUtcString(isoName) == qint64(offsetSeconds) && !result.contains(isoName))
2345 std::sort(result.begin(), result.end());
2350#ifndef QT_NO_DATASTREAM
2351void QUtcTimeZonePrivate::serialize(QDataStream &ds)
const
2353 ds <<
QStringLiteral(
"OffsetFromUtc") << QString::fromUtf8(m_id) << m_offsetFromUtc << m_name
2354 << 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