8#if QT_CONFIG(timezone_locale)
9# include "qtimezonelocale_p.h"
13#include <QtCore/qbitarray.h>
14#include <qdatastream.h>
18#include <private/qcalendarmath_p.h>
19#include <private/qduplicatetracker_p.h>
20#include <private/qnumeric_p.h>
21#if QT_CONFIG(icu) || !QT_CONFIG(timezone_locale)
22# include <private/qstringiterator_p.h>
24#include <private/qtools_p.h>
29#include <emscripten/val.h>
34using namespace QtMiscUtils;
42 return less.windowsIdKey < more.windowsIdKey
43 || (less.windowsIdKey == more.windowsIdKey && less.territory < more.territory);
51 return less.windowsIdKey < more.windowsIdKey
52 || less.windowsId().compare(more.windowsId(), Qt::CaseInsensitive) < 0;
58 return entry.offsetFromUtc < offsetSeconds;
63 return entry.windowsIdKey < winIdKey;
68 return entry.aliasId().compare(aliasId, Qt::CaseInsensitive) < 0;
73 return entry.windowsId().compare(winId, Qt::CaseInsensitive) < 0;
78 return entry.windowsIdKey < winIdKey;
86 winId, earlierWindowsId);
88 return data->windowsIdKey;
99 if (Q_LIKELY(data.windowsIdKey == windowsIdKey))
100 return data.windowsId();
104 windowsIdKey, atLowerWindowsKey);
106 return data->windowsId();
116 windowsIdKey, zoneAtLowerWindowsKey);
120
121
123QTimeZonePrivate::QTimeZonePrivate()
127 Q_ASSERT(std::is_sorted(std::begin(zoneDataTable), std::end(zoneDataTable),
129 Q_ASSERT(std::is_sorted(std::begin(windowsDataTable), std::end(windowsDataTable),
133QTimeZonePrivate::~QTimeZonePrivate()
137bool QTimeZonePrivate::operator==(
const QTimeZonePrivate &other)
const
142 return (m_id == other.m_id);
145bool QTimeZonePrivate::operator!=(
const QTimeZonePrivate &other)
const
147 return !(*
this == other);
150bool QTimeZonePrivate::isValid()
const
152 return !m_id.isEmpty();
155QByteArray QTimeZonePrivate::id()
const
160QLocale::Territory QTimeZonePrivate::territory()
const
163 const QLatin1StringView sought(m_id.data(), m_id.size());
164 for (
const ZoneData &data : zoneDataTable) {
165 for (QLatin1StringView token : data.ids()) {
167 return QLocale::Territory(data.territory);
170 return QLocale::AnyTerritory;
173QString QTimeZonePrivate::comment()
const
178QString QTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch,
179 QTimeZone::NameType nameType,
180 const QLocale &locale)
const
182 const Data tran = data(atMSecsSinceEpoch);
183 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
184 if (nameType == QTimeZone::OffsetName && isAnglicLocale(locale))
185 return isoOffsetFormat(tran.offsetFromUtc);
186 if (nameType == QTimeZone::ShortName && isDataLocale(locale))
187 return tran.abbreviation;
189 QTimeZone::TimeType timeType
190 = tran.daylightTimeOffset != 0 ? QTimeZone::DaylightTime : QTimeZone::StandardTime;
191#if QT_CONFIG(timezone_locale)
192 return localeName(atMSecsSinceEpoch, tran.offsetFromUtc, timeType, nameType, locale);
194 return displayName(timeType, nameType, locale);
200QString QTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
201 QTimeZone::NameType nameType,
202 const QLocale &locale)
const
204 const Data tran = data(timeType);
205 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
206#if QT_CONFIG(timezone_locale)
207 return localeName(tran.atMSecsSinceEpoch, tran.offsetFromUtc, timeType, nameType, locale);
209 if (nameType == QTimeZone::OffsetName && isAnglicLocale(locale))
210 return isoOffsetFormat(tran.offsetFromUtc);
216QString QTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch)
const
218 if (QLocale() != QLocale::c()) {
219 const QString name = displayName(atMSecsSinceEpoch, QTimeZone::ShortName, QLocale());
223 return displayName(atMSecsSinceEpoch, QTimeZone::ShortName, QLocale::c());
226int QTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch)
const
228 const int std = standardTimeOffset(atMSecsSinceEpoch);
229 const int dst = daylightTimeOffset(atMSecsSinceEpoch);
230 const int bad = invalidSeconds();
231 return std == bad || dst == bad ? bad : std + dst;
234int QTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch)
const
236 Q_UNUSED(atMSecsSinceEpoch);
237 return invalidSeconds();
240int QTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch)
const
242 Q_UNUSED(atMSecsSinceEpoch);
243 return invalidSeconds();
246bool QTimeZonePrivate::hasDaylightTime()
const
251bool QTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch)
const
253 Q_UNUSED(atMSecsSinceEpoch);
257QTimeZonePrivate::Data QTimeZonePrivate::data(QTimeZone::TimeType timeType)
const
260 const auto validMatch = [timeType](
const Data &tran) {
261 return tran.atMSecsSinceEpoch != invalidMSecs()
262 && ((timeType == QTimeZone::DaylightTime) != (tran.daylightTimeOffset == 0));
266 const qint64 currentMSecs = QDateTime::currentMSecsSinceEpoch();
267 Data tran = data(currentMSecs);
268 if (validMatch(tran))
271 if (hasTransitions()) {
273 tran = nextTransition(currentMSecs);
274 if (validMatch(tran))
281 tran = previousTransition(currentMSecs + 1);
282 while (tran.atMSecsSinceEpoch != invalidMSecs()) {
283 tran = previousTransition(tran.atMSecsSinceEpoch);
284 if (validMatch(tran))
292
293
294
295
296
297
298
299
300
301bool QTimeZonePrivate::isDataLocale(
const QLocale &locale)
const
304 return locale == QLocale::system();
307QTimeZonePrivate::Data QTimeZonePrivate::data(qint64 forMSecsSinceEpoch)
const
309 Q_UNUSED(forMSecsSinceEpoch);
314QDateTimePrivate::ZoneState QTimeZonePrivate::stateAtZoneTime(
315 qint64 forLocalMSecs, QDateTimePrivate::TransitionOptions resolve)
const
317 auto dataToState = [](
const Data &d) {
318 return QDateTimePrivate::ZoneState(d.atMSecsSinceEpoch + d.offsetFromUtc * 1000,
320 d.daylightTimeOffset ? QDateTimePrivate::DaylightTime
321 : QDateTimePrivate::StandardTime);
325
326
327
328
329
330
331
332 std::integral_constant<qint64, 17 * 3600 * 1000> seventeenHoursInMSecs;
333 static_assert(-seventeenHoursInMSecs / 1000 < QTimeZone::MinUtcOffsetSecs
334 && seventeenHoursInMSecs / 1000 > QTimeZone::MaxUtcOffsetSecs);
337 const qint64 recent =
338 qSubOverflow(forLocalMSecs, seventeenHoursInMSecs, &millis) || millis < minMSecs()
339 ? minMSecs() : millis;
341 const qint64 imminent =
342 qAddOverflow(forLocalMSecs, seventeenHoursInMSecs, &millis)
343 ? maxMSecs() : millis;
345 Q_ASSERT(recent < imminent && seventeenHoursInMSecs - 1 <= imminent - recent);
348 const Data past = data(recent), future = data(imminent);
349 if (future.atMSecsSinceEpoch == invalidMSecs()
350 && past.atMSecsSinceEpoch == invalidMSecs()) {
353 return { forLocalMSecs };
356 if (Q_LIKELY(past.offsetFromUtc == future.offsetFromUtc
357 && past.standardTimeOffset == future.standardTimeOffset
359 && past.abbreviation == future.abbreviation)) {
361 data.atMSecsSinceEpoch = forLocalMSecs - future.offsetFromUtc * 1000;
362 return dataToState(data);
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391 if (hasTransitions()) {
393
394
395
396
397
398
399
400
401
402
407 Q_ASSERT(forLocalMSecs < 0 ||
408 forLocalMSecs - tran.offsetFromUtc * 1000 >= tran.atMSecsSinceEpoch);
410 Data nextTran = nextTransition(tran.atMSecsSinceEpoch);
412
413
414
415
416
417
418
419 while (nextTran.atMSecsSinceEpoch != invalidMSecs()
420 && forLocalMSecs > nextTran.atMSecsSinceEpoch + nextTran.offsetFromUtc * 1000) {
421 Data newTran = nextTransition(nextTran.atMSecsSinceEpoch);
422 if (newTran.atMSecsSinceEpoch == invalidMSecs()
423 || newTran.atMSecsSinceEpoch + newTran.offsetFromUtc * 1000 > imminent) {
430 const qint64 nextStart = nextTran.atMSecsSinceEpoch;
433 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
435 Q_ASSERT(forLocalMSecs < 0
436 || forLocalMSecs - tran.offsetFromUtc * 1000 > tran.atMSecsSinceEpoch);
438 tran.atMSecsSinceEpoch = forLocalMSecs - tran.offsetFromUtc * 1000;
446 if (nextStart == invalidMSecs() && tran.offsetFromUtc == future.offsetFromUtc)
447 return dataToState(tran);
450 if (tran.atMSecsSinceEpoch != invalidMSecs() && nextStart != invalidMSecs()) {
452
453
454
455
456
457
458
459
460
462 nextTran.atMSecsSinceEpoch = forLocalMSecs - nextTran.offsetFromUtc * 1000;
464 bool fallBack =
false;
465 if (nextStart > nextTran.atMSecsSinceEpoch) {
467 if (nextStart > tran.atMSecsSinceEpoch)
468 return dataToState(tran);
470 Q_ASSERT(tran.offsetFromUtc < nextTran.offsetFromUtc);
472 }
else if (nextStart <= tran.atMSecsSinceEpoch) {
474 return dataToState(nextTran);
476 Q_ASSERT(nextTran.offsetFromUtc < tran.offsetFromUtc);
484 = resolve.testFlag(QDateTimePrivate::FlipForReverseDst)
485 && (fallBack ? !tran.daylightTimeOffset && nextTran.daylightTimeOffset
486 : tran.daylightTimeOffset && !nextTran.daylightTimeOffset);
489 if (resolve.testFlag(flipped
490 ? QDateTimePrivate::FoldUseBefore
491 : QDateTimePrivate::FoldUseAfter)) {
492 return dataToState(nextTran);
494 if (resolve.testFlag(flipped
495 ? QDateTimePrivate::FoldUseAfter
496 : QDateTimePrivate::FoldUseBefore)) {
497 return dataToState(tran);
501
502
503
504
505
506 std::swap(tran.atMSecsSinceEpoch, nextTran.atMSecsSinceEpoch);
507 if (resolve.testFlag(flipped
508 ? QDateTimePrivate::GapUseBefore
509 : QDateTimePrivate::GapUseAfter))
510 return dataToState(nextTran);
511 if (resolve.testFlag(flipped
512 ? QDateTimePrivate::GapUseAfter
513 : QDateTimePrivate::GapUseBefore))
514 return dataToState(tran);
517 return {forLocalMSecs};
524 qint64 utcEpochMSecs;
527 int early = past.offsetFromUtc;
528 int late = future.offsetFromUtc;
529 if (early == late || late == invalidSeconds()) {
530 if (early == invalidSeconds()
531 || qSubOverflow(forLocalMSecs, early * qint64(1000), &utcEpochMSecs)) {
532 return {forLocalMSecs};
536 const qint64 forEarly = forLocalMSecs - early * 1000;
537 const qint64 forLate = forLocalMSecs - late * 1000;
540 const bool earlyOk = offsetFromUtc(forEarly) == early;
541 const bool lateOk = offsetFromUtc(forLate) == late;
545 Q_ASSERT(early > late);
547 if (resolve.testFlag(QDateTimePrivate::FoldUseBefore))
548 utcEpochMSecs = forEarly;
549 else if (resolve.testFlag(QDateTimePrivate::FoldUseAfter))
550 utcEpochMSecs = forLate;
552 return {forLocalMSecs};
555 utcEpochMSecs = forEarly;
559 utcEpochMSecs = forLate;
562 Q_ASSERT(late > early);
563 const int dstStep = (late - early) * 1000;
564 if (resolve.testFlag(QDateTimePrivate::GapUseBefore))
565 utcEpochMSecs = forEarly - dstStep;
566 else if (resolve.testFlag(QDateTimePrivate::GapUseAfter))
567 utcEpochMSecs = forLate + dstStep;
569 return {forLocalMSecs};
573 return dataToState(data(utcEpochMSecs));
576bool QTimeZonePrivate::hasTransitions()
const
581QTimeZonePrivate::Data QTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch)
const
583 Q_UNUSED(afterMSecsSinceEpoch);
587QTimeZonePrivate::Data QTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch)
const
589 Q_UNUSED(beforeMSecsSinceEpoch);
593QTimeZonePrivate::DataList QTimeZonePrivate::transitions(qint64 fromMSecsSinceEpoch,
594 qint64 toMSecsSinceEpoch)
const
597 if (toMSecsSinceEpoch >= fromMSecsSinceEpoch) {
599 Data next = nextTransition(fromMSecsSinceEpoch - 1);
600 while (next.atMSecsSinceEpoch != invalidMSecs()
601 && next.atMSecsSinceEpoch <= toMSecsSinceEpoch) {
603 next = nextTransition(next.atMSecsSinceEpoch);
609QByteArray QTimeZonePrivate::systemTimeZoneId()
const
614template <
typename Pred>
623 name, earlierAliasId);
625 name = data->ianaId();
633 for (
const auto &data : aliasMappingTable) {
634 QByteArrayView alias = data.aliasId();
635 if (data.ianaId() == name && test(alias))
641QByteArrayView QTimeZonePrivate::availableAlias(QByteArrayView ianaId)
const
643 return aliasMatching(ianaId, [
this](QByteArrayView id) {
return isTimeZoneIdAvailable(id); });
646bool QTimeZonePrivate::isTimeZoneIdAvailable(QByteArrayView ianaId)
const
650 const QList<QByteArray> tzIds = availableTimeZoneIds();
651 return std::binary_search(tzIds.begin(), tzIds.end(), ianaId);
657 std::sort(desired.begin(), desired.end());
658 const auto newEnd =
std::unique(desired.begin(), desired.end());
659 const auto newSize =
std::distance(desired.begin(), newEnd);
661 result.reserve(qMin(all.size(), newSize));
662 std::set_intersection(all.begin(), all.end(), desired.cbegin(),
663 std::next(desired.cbegin(), newSize),
std::back_inserter(result));
667QList<QByteArrayView> QTimeZonePrivate::matchingTimeZoneIds(QLocale::Territory territory)
const
670 QList<QByteArrayView> regions;
671#if QT_CONFIG(timezone_locale) && !QT_CONFIG(icu)
672 regions = QtTimeZoneLocale::ianaIdsForTerritory(territory);
675 if (territory == QLocale::World) {
678 for (
const WindowsData &data : windowsDataTable)
679 regions << data.ianaId();
681 for (
const ZoneData &data : zoneDataTable) {
682 if (data.territory == territory) {
683 for (
auto l1 : data.ids())
684 regions << QByteArrayView(l1.data(), l1.size());
691QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(QLocale::Territory territory)
const
693 return selectAvailable(matchingTimeZoneIds(territory), availableTimeZoneIds());
696QList<QByteArrayView> QTimeZonePrivate::matchingTimeZoneIds(
int offsetFromUtc)
const
699 QList<QByteArrayView> offsets;
701 for (
const WindowsData &winData : windowsDataTable) {
702 if (winData.offsetFromUtc == offsetFromUtc) {
703 for (
auto data = zoneStartForWindowsId(winData.windowsIdKey);
704 data != std::end(zoneDataTable) && data->windowsIdKey == winData.windowsIdKey;
706 for (
auto l1 : data->ids())
707 offsets << QByteArrayView(l1.data(), l1.size());
714QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(
int offsetFromUtc)
const
716 return selectAvailable(matchingTimeZoneIds(offsetFromUtc), availableTimeZoneIds());
719QList<QByteArray> QTimeZonePrivate::uniqueSortedAliasPadded(QList<QByteArray> &&zoneIds)
722 const QList<QByteArray> source = zoneIds;
724 for (
const auto &name : source) {
725 const auto zone = aliasToIana(name);
726 if (!zone.isEmpty()) {
727 zoneIds << zone.toByteArray();
728 Q_ASSERT(aliasToIana(zone).isEmpty());
731 std::sort(zoneIds.begin(), zoneIds.end());
732 zoneIds.erase(std::unique(zoneIds.begin(), zoneIds.end()), zoneIds.end());
736QList<QByteArray> QTimeZonePrivate::padSortedWithAliases(QList<QByteArray> &&zoneIds)
739 const QList<QByteArray> source = zoneIds;
740 for (
const auto &name : source) {
741 const auto zone = aliasToIana(name);
742 const auto pos = std::lower_bound(zoneIds.begin(), zoneIds.end(), zone);
743 if (pos != zoneIds.end() && *pos != zone)
744 zoneIds.insert(pos, zone.toByteArray());
749#ifndef QT_NO_DATASTREAM
750void QTimeZonePrivate::serialize(QDataStream &ds)
const
752 ds << QString::fromUtf8(m_id);
758QTimeZone::OffsetData QTimeZonePrivate::invalidOffsetData()
760 return { QString(), QDateTime(),
761 invalidSeconds(), invalidSeconds(), invalidSeconds() };
764QTimeZone::OffsetData QTimeZonePrivate::toOffsetData(
const QTimeZonePrivate::Data &data)
766 if (data.atMSecsSinceEpoch == invalidMSecs())
767 return invalidOffsetData();
771 QDateTime::fromMSecsSinceEpoch(data.atMSecsSinceEpoch, QTimeZone::UTC),
772 data.offsetFromUtc, data.standardTimeOffset, data.daylightTimeOffset };
776bool QTimeZonePrivate::isValidId(QByteArrayView ianaId)
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
818 const int MinSectionLength = 1;
819#if defined(Q_OS_ANDROID) || QT_CONFIG(icu)
822 const int MaxSectionLength = 17;
824 const int MaxSectionLength = 14;
826 int sectionLength = 0;
827 for (
const char *it = ianaId.begin(), *
const end = ianaId.end(); it != end; ++it, ++sectionLength) {
830 if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength)
833 }
else if (ch ==
'-') {
834 if (sectionLength == 0)
836 }
else if (!isAsciiLower(ch)
847 if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength)
852QString QTimeZonePrivate::isoOffsetFormat(
int offsetFromUtc, QTimeZone::NameType mode)
854 if (mode == QTimeZone::ShortName && !offsetFromUtc)
858 if (offsetFromUtc < 0) {
860 offsetFromUtc = -offsetFromUtc;
862 const int secs = offsetFromUtc % 60;
863 const int mins = (offsetFromUtc / 60) % 60;
864 const int hour = offsetFromUtc / 3600;
865 QString result = QString::asprintf(
"UTC%c%02d", sign, hour);
866 if (mode != QTimeZone::ShortName || secs || mins)
867 result += QString::asprintf(
":%02d", mins);
868 if (mode == QTimeZone::LongName || secs)
869 result += QString::asprintf(
":%02d", secs);
873#if QT_CONFIG(icu) || !QT_CONFIG(timezone_locale)
874static QTimeZonePrivate::NamePrefixMatch
875findUtcOffsetPrefix(QStringView text,
const QLocale &locale)
879 qsizetype signLen = 0;
881 auto signStart = [&signLen, &sign, locale](QStringView str) {
882 QString signStr = locale.negativeSign();
883 if (str.startsWith(signStr)) {
885 signLen = signStr.size();
889 if (str.startsWith(u'\u2212')) {
894 signStr = locale.positiveSign();
895 if (str.startsWith(signStr)) {
897 signLen = signStr.size();
903 if (!((text.startsWith(u"UTC") || text.startsWith(u"GMT")) && signStart(text.sliced(3))))
906 QStringView offset = text.sliced(3 + signLen);
907 QStringIterator iter(offset);
908 qsizetype hourEnd = 0, hmMid = 0, minEnd = 0;
911 while (digits < 4 && iter.hasNext()) {
913 if (!QChar::isDigit(ch))
919 hourEnd = std::exchange(hmMid, std::exchange(minEnd, iter.index()));
924 QStringView hourStr, minStr;
926 minStr = offset.first(minEnd).sliced(hourEnd);
927 }
else if (digits < 3 && iter.hasNext() && QChar::isPunct(ch)) {
929 hmMid = iter.index();
931 while (mindig < 2 && iter.hasNext() && QChar::isDigit(iter.next())) {
933 minEnd = iter.index();
936 minStr = offset.first(minEnd).sliced(hmMid);
942 hourStr = offset.first(hourEnd);
945 uint hour = 0, minute = 0;
946 if (!hourStr.isEmpty())
947 hour = locale.toUInt(hourStr, &ok);
948 if (ok && !minStr.isEmpty()) {
949 minute = locale.toUInt(minStr, &ok);
951 if ((!ok || minute >= 60) && minEnd > hourEnd + minStr.size()) {
960 constexpr int MaxOffsetSeconds
961 = qMax(QTimeZone::MaxUtcOffsetSecs, -QTimeZone::MinUtcOffsetSecs);
962 if (!ok || (hour * 60 + minute) * 60 > MaxOffsetSeconds)
972 std::snprintf(buffer,
sizeof(buffer),
"UTC%c%02u:%02u", sign, hour, minute);
974 std::snprintf(buffer,
sizeof(buffer),
"UTC%c%02u", sign, hour);
976 return { QByteArray(buffer, qstrnlen(buffer,
sizeof(buffer))),
977 3 + signLen + minEnd,
978 QTimeZone::GenericTime };
981QTimeZonePrivate::NamePrefixMatch
982QTimeZonePrivate::findLongNamePrefix(QStringView text,
const QLocale &locale,
983 std::optional<qint64> atEpochMillis)
987 QTimeZonePrivate::NamePrefixMatch best = findUtcOffsetPrefix(text, locale);
989 const auto matchLength = [text](QStringView name) -> qsizetype {
990 qsizetype length = 0;
991 if (name.size() > 0 && text.startsWith(name, Qt::CaseInsensitive)) {
992 length = name.size();
994 while (!text.first(length).startsWith(name, Qt::CaseInsensitive)) {
996 Q_ASSERT(length <= text.size());
999 if (length == name.size()) {
1000 while (length > 0 && text.first(length - 1).startsWith(name, Qt::CaseInsensitive))
1006 const auto when = atEpochMillis
1007 ? QDateTime::fromMSecsSinceEpoch(*atEpochMillis, QTimeZone::UTC)
1009 const auto typeFor = [when](QTimeZone zone) {
1010 if (when.isValid() && zone.isDaylightTime(when))
1011 return QTimeZone::DaylightTime;
1013 return QTimeZone::GenericTime;
1015 const auto tryZone = [&](
const QByteArray &iana) {
1016 bool matched =
false;
1017 constexpr QTimeZone::TimeType types[]
1018 = { QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime };
1019 QTimeZone zone(iana);
1020 if (!zone.isValid())
1022 if (when.isValid()) {
1023 const QString name = zone.displayName(when, QTimeZone::LongName, locale);
1024 if (qsizetype match = matchLength(name); match > best.nameLength) {
1025 best = { iana, match, typeFor(zone) };
1029 const bool neverDst = !zone.hasDaylightTime();
1030 for (
const QTimeZone::TimeType type : types) {
1031 if (neverDst && type == QTimeZone::DaylightTime)
1033 const QString name = zone.displayName(type, QTimeZone::LongName, locale);
1034 if (qsizetype match = matchLength(name); match > best.nameLength) {
1035 best = { iana, match, type };
1043 const QList<QByteArray> allZones = []() {
1044 QList<QByteArray> avail = QTimeZone::availableTimeZoneIds();
1045 const auto isCanonical = [](
const QByteArray &name) {
1047 return QTimeZonePrivate::aliasToIana(name).isEmpty();
1049 [[maybe_unused]]
const QList<QByteArray>::const_iterator
1050 firstAlias = std::partition(avail.begin(), avail.end(), isCanonical);
1053 Q_ASSERT(std::all_of(firstAlias, avail.constEnd(),
1054 [from = avail.constBegin(), to = firstAlias,
1055 avail](
const QByteArray &alias) {
1057 QByteArrayView iana = QTimeZonePrivate::aliasToIana(alias);
1058 return std::find_if(from, to, [iana](
const QByteArray &zone) {
1059 return zone == iana;
1060 }) != to || !avail.contains(iana);
1066 for (
const QByteArray &iana : allZones) {
1068 if (tryZone(iana) && best.nameLength >= text.size())
1083 QDuplicateTracker<QByteArray, std::size(aliasMappingTable)> triedAlready;
1084 for (
const QByteArray &iana : allZones)
1085 (
void) triedAlready.hasSeen(iana);
1086 for (
const auto &data : aliasMappingTable) {
1087 const QByteArray alias = data.aliasId().toByteArray();
1088 if (!triedAlready.hasSeen(alias) && tryZone(alias) && best.nameLength >= text.size())
1096QTimeZonePrivate::NamePrefixMatch
1097QTimeZonePrivate::findNarrowOffsetPrefix(QStringView,
const QLocale &)
1106#if QT_CONFIG(timezone_locale) && !QT_CONFIG(icu)
1109# define BACKEND_PROVIDES_OFFSET_PREFIX
1113#ifdef BACKEND_PROVIDES_OFFSET_PREFIX
1114# undef BACKEND_PROVIDES_OFFSET_PREFIX
1118struct NumericPattern
1120 NumericPattern(QStringView text,
const QLocale &locale);
1124 QList<qsizetype> pattern;
1126 bool digitsAreLocale;
1134 using Sign =
unsigned char;
1136 bool scanForToken(QStringView sought)
1140 if (sought.isEmpty())
1142 qsizetype tokensMatched = 0;
1143 const qsizetype n = sought.size();
1145 while ((idx = given.indexOf(sought, idx + n)) >= 0) {
1146 for (qsizetype i = 0; i < n; ++i)
1147 mask.setBit(idx + i);
1150 return tokensMatched > 0;
1153 Sign scanForSignsImpl(
const QLocale &locale, Sign signs)
1157 if (scanForToken(locale.positiveSign()))
1159 if (scanForToken(locale.negativeSign()))
1168 Scanner(QStringView text) : given(text), mask(text.size()) {}
1170 bool scanForDigits(
const QLocale &locale)
1174 bool matched =
false;
1175 for (
int i = 0; i < 10; ++i) {
1176 if (scanForToken(locale.toString(i)))
1182 Sign scanForSigns(
const QLocale &locale)
1186 Sign signs = scanForSignsImpl(locale,
'\0');
1187 signs = scanForSignsImpl(QLocale::c(), signs);
1188 if (scanForToken(u"\u2212"))
1193 QList<qsizetype> asPattern()
const
1197 QList<qsizetype> res;
1199 for (qsizetype i = 0, n = mask.size(); i < n; ++i) {
1200 if (mask.testBit(i)) {
1221NumericPattern::NumericPattern(QStringView text,
const QLocale &locale)
1226 Scanner scanner(text);
1227 digitsAreLocale = hasDigits = scanner.scanForDigits(locale);
1229 hasDigits = scanner.scanForDigits(QLocale::c());
1231 sign = scanner.scanForSigns(locale);
1233 pattern = scanner.asPattern();
1239 const QList<qsizetype> &txtPat;
1240 const QtTemporalPattern::TemporalFieldFlags options;
1241 qsizetype txtPos = 0, txtInd = 0;
1242 static constexpr uint Hour = 1, Minute = 2, Second = 4;
1243 uint seenFields = 0;
1244 using Digits = QLocaleData::DigitSequence;
1246 bool textMatch(QStringView str, qsizetype strPos, qsizetype slen, qsizetype tlen)
const
1250 if (txt.sliced(txtPos, tlen).compare(str.sliced(strPos, slen), Qt::CaseInsensitive) == 0)
1253 if (txtInd == 0 && slen == 3 && txt.first(3) == u"GMT" && str.first(3) == u"UTC") {
1254 Q_ASSERT(txtPos == 0);
1255 Q_ASSERT(strPos == 0);
1261 bool allowField(uint fieldBit)
const;
1262 bool allowSkipField(Digits &&fmt)
const;
1263 auto readField(QByteArrayView field, uint fieldBit,
int *value);
1264 bool scanExtraFields(QStringView sep,
const QLocaleData *locData,
1265 qsizetype &txtLen,
int &second);
1266 qsizetype scanMatchedFields(
const Digits &fmt,
const Digits &src,
bool allowExtraFields,
1267 int &hour,
int &minute,
int &second,
int &sign);
1276 PatternAligner(QStringView text,
const QList<qsizetype> &textPattern,
1277 QtTemporalPattern::TemporalFieldFlags flags)
1278 : txt(text), txtPat(textPattern), options(flags) {}
1283 static constexpr qint32 OffsetMagnitude = 38245;
1284 static constexpr QByteArrayView hourAscii{
"10"}, minuteAscii{
"37"}, secondAscii{
"25"};
1286 auto match(QStringView str,
const QList<qsizetype> &strPat,
1287 const QLocaleData *locData,
char signChar);
1290bool PatternAligner::allowField(uint fieldBit)
const
1292 if (!fieldBit || (seenFields & fieldBit))
1296 using namespace QtTemporalPattern::FieldGroup;
1297 if (!options.testAnyFlags(WidthMask))
1301 using namespace QtTemporalPattern;
1302 using F = TemporalFieldFlag;
1306 return matchesFlagsWithin(options, WidthMask & ~F::Narrow, WidthMask);
1308 return matchesFlagsWithin(options, F::Wide | F::Short, WidthMask);
1310 Q_UNREACHABLE_RETURN(
false);
1313bool PatternAligner::allowSkipField(Digits &&fmt)
const
1317 if (fmt.digits.startsWith(minuteAscii))
1319 else if (fmt.digits.startsWith(secondAscii))
1324 if (Q_UNLIKELY(seenFields & fieldBit))
1327 if (Q_UNLIKELY(seenFields & Second) && fieldBit == Minute)
1331 if (options.testAnyFlags(QtTemporalPattern::FieldGroup::WidthMask)) {
1332 using F = QtTemporalPattern::TemporalFieldFlag;
1337 return options.testAnyFlags(F::ZeroPad | F::Narrow);
1339 return options.testAnyFlags(F::ZeroPad | F::Narrow | F::Abbreviated);
1345auto PatternAligner::readField(QByteArrayView field, uint fieldBit,
int *value)
1348 constexpr int MaxHourOffset
1349 = qMax(QTimeZone::MaxUtcOffsetSecs, -QTimeZone::MinUtcOffsetSecs) / 3600;
1350 static_assert(MaxHourOffset > 9);
1352 QByteArrayView used;
1354 } res = { field,
false };
1356 if ((fieldBit != Hour && res.used.size() < 2) || !allowField(fieldBit) || !value)
1358 Q_ASSERT(*value == 0);
1359 seenFields |= fieldBit;
1360 if (res.used.size() > 2)
1361 res.used = res.used.first(2);
1362 *value = res.used.toInt(&res.ok);
1363 if (fieldBit == Hour && (!res.ok || *value > MaxHourOffset)) {
1365 *value = res.used.toInt(&res.ok);
1371bool PatternAligner::scanExtraFields(QStringView sep,
const QLocaleData *locData,
1372 qsizetype &txtLen,
int &second)
1376 while (txtInd + 1 < txtPat.size() && txt.sliced(txtPos, -txtLen) == sep) {
1378 txtLen = txtPat.at(++txtInd);
1380 Q_ASSERT((seenFields & Hour) && (seenFields & Minute));
1381 if (seenFields & Second)
1383 Q_ASSERT(second == 0);
1384 const Digits asciiParse
1385 = locData->digitSequence(txt.sliced(txtPos, txtLen));
1386 QByteArrayView found = asciiParse.digits;
1388 second = found.toInt(&ok);
1389 if (!ok || second >= 60)
1391 seenFields |= Second;
1393 txtLen = ++txtInd < txtPat.size() ? txtPat.at(txtInd) : 0;
1400qsizetype PatternAligner::scanMatchedFields(
const Digits &fmt,
const Digits &src,
1401 bool allowExtraFields,
1402 int &hour,
int &minute,
int &second,
int &sign)
1405 if (!fmt.digits.startsWith(hourAscii))
1407 if (sign || !src.sign)
1409 sign = fmt.sign == src.sign ? +1 : -1;
1410 }
else if (src.sign) {
1414 QByteArrayView chosen{fmt.digits}, found{src.digits};
1415 while (chosen.size() && found.size()) {
1416 const uint priorFields = seenFields;
1417 auto read = chosen.startsWith(hourAscii) ? readField(found, Hour, &hour)
1418 : chosen.startsWith(minuteAscii) ? readField(found, Minute, &minute)
1419 : chosen.startsWith(secondAscii) ? readField(found, Second, &second)
1420 : readField(found, 0u,
nullptr);
1424 if (read.used.size() < 2) {
1425 const uint newField = (seenFields ^ priorFields);
1428 if (newField == Hour) {
1435 found = found.sliced(read.used.size());
1436 allowExtraFields =
false;
1440 if (chosen.size() < fmt.digits.size() && (priorFields & Hour)) {
1443 seenFields = priorFields;
1444 Q_ASSERT(newField == Minute || newField == Second);
1445 if (newField == Minute)
1451 allowExtraFields =
false;
1457 Q_ASSERT(chosen.size() >= 2);
1458 chosen = chosen.sliced(2);
1459 found = found.sliced(read.used.size());
1461 if (chosen.size()) {
1462 Q_ASSERT(found.isEmpty());
1464 return (seenFields & Hour) ? 0 : -1;
1467 if (found.size() && (seenFields & Hour)) {
1470 if (allowExtraFields) {
1471 if (!Q_LIKELY(seenFields & Minute)) {
1472 const uint priorFields = seenFields;
1473 auto read = readField(found, Minute, &minute);
1474 if (!read.ok || read.used.size() < 2) {
1476 seenFields = priorFields;
1477 return found.size();
1479 found = found.sliced(read.used.size());
1481 if (!(seenFields & Second)) {
1482 const uint priorFields = seenFields;
1483 auto read = readField(found, Second, &second);
1484 if (!read.ok || read.used.size() < 2) {
1486 seenFields = priorFields;
1487 return found.size();
1489 found = found.sliced(read.used.size());
1493 return found.size();
1496 return found.size() ? -1 : 0;
1499auto PatternAligner::match(QStringView str,
const QList<qsizetype> &strPat,
1500 const QLocaleData *locData,
char signChar)
1502 Q_ASSERT(!str.isEmpty() && !strPat.isEmpty());
1507 constexpr auto AllowSign = Digits::Option::AllowSign;
1510 qsizetype length = 0;
1511 operator
bool()
const {
return length > 0; }
1513 if ((strPat.at(0) < 0) != (txtPat.at(0) < 0))
1517 int hour = 0, minute = 0, second = 0;
1518 int sign = !signChar;
1522 qsizetype skip = 0, strPos = 0, txtSkipped = 0;
1524 for (qsizetype len : strPat) {
1528 if (!allowSkipField(locData->digitSequence(str.sliced(strPos, len))))
1537 qsizetype txtLen = txtInd < txtPat.size() ? txtPat.at(txtInd) : 0;
1538 const bool maybeSep = txtInd > 0 && txtInd + 1 < strPat.size();
1541 if (!sep.isEmpty() && str.sliced(strPos, -len) != sep) {
1542 if (!scanExtraFields(sep, locData, txtLen, second))
1545 if (maybeSep && sep.isEmpty())
1546 sep = str.sliced(strPos, -len);
1548 if (!textMatch(str, strPos, -len, -txtLen)) {
1551 if (locData && !sep.isEmpty() && str.sliced(strPos, -len) == sep) {
1558 if (txtInd + 1 < strPat.size() - txtSkipped || len <= txtLen
1559 || !textMatch(str, strPos, -len, -len)) {
1573 if (txtPos >= txt.size()) {
1574 Q_ASSERT(txtInd >= txtPat.size());
1576 if (!sep.isEmpty() && txtPat.back() == sep.size() && txt.endsWith(sep)
1577 && strPat.back() == -sep.size() && str.endsWith(sep)) {
1582 txtInd = txtPat.size() - 1;
1583 txtPos -= sep.size();
1593 QStringView field = str.sliced(strPos, len);
1594 QStringView toParse = txt.sliced(txtPos, txtPat.at(txtInd));
1597 const Digits asciiField = locData->digitSequence(field, AllowSign);
1598 if (asciiField.endIndex() != field.size())
1600 const Digits asciiParse = locData->digitSequence(toParse, AllowSign);
1601 if (asciiParse.endIndex() != toParse.size())
1603 const uint priorFields = seenFields;
1605 const qsizetype spare
1606 = scanMatchedFields(asciiField, asciiParse,
1607 !txtSkipped && txtInd + 2 >= strPat.size() && sep.isEmpty(),
1608 hour, minute, second, sign);
1617 if (spare == 1 && (seenFields & Hour)) {
1619 }
else if (strPat.back() < 0) {
1620 if (sep.isEmpty() || !(priorFields & Hour)
1621 || strPat.back() != -sep.size() || !str.endsWith(sep)) {
1624 int offset = hour * 60;
1625 if (priorFields & Minute)
1628 if (priorFields & Second)
1630 return R{ sign * offset, txtPos };
1634 txtPos += asciiParse.digitStart
1635 + (asciiParse.digits.size() - spare) * asciiParse.digitWidth;
1639 strPos += asciiField.endIndex();
1640 txtPos += asciiParse.endIndex();
1645 return R{ sign * (second + 60 * (minute + 60 * hour)), txtPos };
1648QTimeZonePrivate::NamePrefixMatch
1649findOffsetPrefixImpl(QStringView text,
const QLocale &locale,
1650 QtTemporalPattern::TemporalFieldFlags flags)
1652 QTimeZonePrivate::NamePrefixMatch best;
1662 const QUtcTimeZonePrivate greenwich(0);
1664 const QUtcTimeZonePrivate positive(+PatternAligner::OffsetMagnitude);
1665 const QUtcTimeZonePrivate negative(-PatternAligner::OffsetMagnitude);
1667 constexpr QTimeZone::NameType formats[] = {
1668 QTimeZone::OffsetName, QTimeZone::LongName, QTimeZone::ShortName
1670 constexpr QTimeZone::TimeType seasons[] = {
1671 QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime
1674 const auto acceptFormat = [flags](QTimeZone::NameType format) {
1675 using namespace QtTemporalPattern;
1677 if (!flags.testAnyFlags(FieldGroup::WidthMask | FieldGroup::FormMask))
1679 using Flag = TemporalFieldFlag;
1680 constexpr TemporalFieldFlags Textual = Flag::Verbal | Flag::Standalone;
1681 constexpr TemporalFieldFlags Long = Flag::Abbreviated | Flag::Short | Flag::Wide;
1683 case QTimeZone::OffsetName:
1684 return matchesFlagWithin(flags, Flag::Numeric, FieldGroup::FormMask);
1685 case QTimeZone::DefaultName:
1686 case QTimeZone::LongName:
1687 return matchesFlagsWithin(flags, Textual, FieldGroup::FormMask)
1688 && matchesFlagsWithin(flags, Long, FieldGroup::WidthMask);
1689 case QTimeZone::ShortName:
1690 return matchesFlagsWithin(flags, Textual, FieldGroup::FormMask)
1691 && matchesFlagWithin(flags, Flag::Narrow, FieldGroup::WidthMask);
1693 Q_UNREACHABLE_RETURN(
false);
1695 const auto acceptSeason = [flags](QTimeZone::TimeType season) {
1696 using namespace QtTemporalPattern;
1698 if (!flags.testAnyFlags(FieldGroup::SeasonMask))
1700 using Flag = TemporalFieldFlag;
1702 case QTimeZone::GenericTime:
1703 return flags.testFlag(Flag::GenericTime);
1704 case QTimeZone::StandardTime:
1705 return flags.testFlag(Flag::StandardTime);
1706 case QTimeZone::DaylightTime:
1707 return flags.testFlag(Flag::DaylightSavingTime);
1709 Q_UNREACHABLE_RETURN(
false);
1713
1714
1715
1716
1717
1718 QLocale digitLocale = locale;
1719 for (
int i = 0; i < 2; ++i) {
1724 const NumericPattern textPattern(text, digitLocale);
1725 Q_ASSERT(!textPattern.pattern.isEmpty());
1726 PatternAligner aligner(text, textPattern.pattern, flags);
1730 const auto consider = [&best, &aligner, txtSign = textPattern.sign, digitLocale]
1731 (QStringView candidate,
char sign, QTimeZone::TimeType season) {
1732 const auto idForOffset = [sign](
int offsetSeconds) ->
QByteArray {
1736 offsetSeconds = -offsetSeconds;
1737 return QTimeZonePrivate::isoOffsetFormat(offsetSeconds,
1738 QTimeZone::OffsetName).toLatin1();
1740 const auto localeDataFor = [loc = digitLocale] (
const NumericPattern &pat) {
1741 if (pat.hasDigits) {
1742 if (pat.digitsAreLocale)
1743 return QLocalePrivate::get(loc)->m_data;
1744 return QLocaleData::c();
1746 return static_cast<
const QLocaleData *>(
nullptr);
1748 if (
const NumericPattern pat(candidate, digitLocale);
1750 (pat.sign & sign) != sign || (txtSign & sign) == sign) {
1751 const auto parsed = aligner.match(
1752 candidate, pat.pattern, localeDataFor(pat), pat.sign);
1753 if (parsed && parsed.length > best.nameLength)
1754 best = { idForOffset(parsed.offset), parsed.length, season };
1755 return pat.digitsAreLocale;
1760 bool nativeSeen =
false;
1761 for (
auto season : seasons) {
1762 if (!acceptSeason(season))
1764 for (
auto format : formats) {
1765 if (!acceptFormat(format))
1767 if (
const QString pos = positive.displayName(season, format, locale);
1768 pos.size() > best.nameLength) {
1769 if (consider(pos,
'+', season))
1772 if (
const QString neg = negative.displayName(season, format, locale);
1773 neg.size() > best.nameLength) {
1774 if (consider(neg,
'-', season))
1777 if (
const QString nul = greenwich.displayName(season, format, locale);
1778 nul.size() > best.nameLength) {
1779 if (text.startsWith(nul))
1780 best = {
"UTC"_ba, nul.size() };
1782 if (best.nameLength == text.size())
1789 if (i == 0 && (digitLocale.zeroDigit() == u'0' || !nativeSeen))
1791 digitLocale = QLocale::c();
1798QTimeZonePrivate::NamePrefixMatch
1799QTimeZonePrivate::findOffsetPrefix(QStringView text,
const QLocale &locale,
1800 QtTemporalPattern::TemporalFieldFlags flags)
1802 NamePrefixMatch best;
1803 if (
auto match = findOffsetPrefixImpl(text, locale, flags))
1804 best = std::move(match);
1805 if (
auto match = findOffsetPrefixImpl(text, QLocale::c(), flags);
1806 match.nameLength > best.nameLength) {
1807 best = std::move(match);
1814QTimeZonePrivate::NamePrefixMatch
1815QTimeZonePrivate::findLongUtcPrefix(QStringView text)
1817 if (text.startsWith(u"UTC")) {
1818 if (text.size() > 4 && (text[3] == u'+' || text[3] == u'-')) {
1820 const auto digitAt = [text](qsizetype index) {
1821 using QtMiscUtils::isAsciiDigit;
1822 return index < text.size() && isAsciiDigit(text[index].unicode());
1824 qsizetype length = 3;
1828 Q_ASSERT(length < text.size());
1829 if (!digitAt(length + 1) || (groups && !digitAt(length + 2)))
1831 length += digitAt(length + 2) ? 3 : 2;
1832 }
while (++groups < 3 && length < text.size() && text[length] == u':');
1834 return { text.first(length).toLatin1(), length, QTimeZone::GenericTime };
1836 return { utcQByteArray(), 3, QTimeZone::GenericTime };
1842QByteArrayView QTimeZonePrivate::aliasToIana(QByteArrayView alias)
1844 const auto data = std::lower_bound(std::begin(aliasMappingTable), std::end(aliasMappingTable),
1845 alias, earlierAliasId);
1846 if (data != std::end(aliasMappingTable) && data->aliasId() == alias)
1847 return data->ianaId();
1854QByteArrayView QTimeZonePrivate::ianaIdToWindowsId(QByteArrayView id)
1856 const auto idUtf8 = QUtf8StringView(id);
1858 for (
const ZoneData &data : zoneDataTable) {
1859 for (
auto l1 : data.ids()) {
1861 return toWindowsIdLiteral(data.windowsIdKey);
1869QByteArrayView QTimeZonePrivate::windowsIdToDefaultIanaId(QByteArrayView windowsId)
1871 const auto data = std::lower_bound(std::begin(windowsDataTable), std::end(windowsDataTable),
1872 windowsId, earlierWindowsId);
1873 if (data != std::end(windowsDataTable) && data->windowsId() == windowsId) {
1874 QByteArrayView id = data->ianaId();
1875 Q_ASSERT(id.indexOf(
' ') == -1);
1881QByteArrayView QTimeZonePrivate::windowsIdToDefaultIanaId(QByteArrayView windowsId,
1882 QLocale::Territory territory)
1885 if (territory == QLocale::World) {
1887 return windowsIdToDefaultIanaId(windowsId);
1890 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1891 const qint16 land =
static_cast<quint16>(territory);
1892 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1893 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1896 if (data->territory == land)
1897 return *data->ids().begin();
1903QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(QByteArrayView windowsId)
1905 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1906 QList<QByteArray> list;
1908 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1909 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1911 for (
auto l1 : data->ids())
1912 list << QByteArray(l1.data(), l1.size());
1919 std::sort(list.begin(), list.end());
1923QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(QByteArrayView windowsId,
1924 QLocale::Territory territory)
1927 QList<QByteArray> list;
1928 if (territory == QLocale::World) {
1930 list << windowsIdToDefaultIanaId(windowsId).toByteArray();
1932 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1933 const qint16 land =
static_cast<quint16>(territory);
1934 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1935 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1938 if (data->territory == land) {
1939 for (
auto l1 : data->ids())
1940 list << QByteArray(l1.data(), l1.size());
1952 while ((cut = ianaIds.indexOf(
' ')) >= 0) {
1953 if (id == ianaIds.first(cut))
1955 ianaIds = ianaIds.sliced(cut + 1);
1957 return id == ianaIds;
1961
1962
1963
1964
1965
1966
1969QUtcTimeZonePrivate::QUtcTimeZonePrivate()
1971 const QString name = utcQString();
1972 init(utcQByteArray(), 0, name, name, QLocale::AnyTerritory, name);
1976QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QByteArray &id)
1979 for (
const UtcData &data : utcDataTable) {
1980 if (isEntryInIanaList(id, data.id())) {
1981 QString name = QString::fromUtf8(id);
1982 init(id, data.offsetFromUtc, name, name, QLocale::AnyTerritory, name);
1990qint64 QUtcTimeZonePrivate::offsetFromUtcString(QByteArrayView id)
1995 if (!id.startsWith(
"UTC") || id.size() < 5)
1996 return invalidSeconds();
1997 const char signChar = id.at(3);
1998 if (signChar !=
'-' && signChar !=
'+')
1999 return invalidSeconds();
2000 const int sign = signChar ==
'-' ? -1 : 1;
2004 for (
auto offset : QLatin1StringView(id.mid(4)).tokenize(
':'_L1)) {
2005 if (offset.size() > 2 || (prior && offset.size() < 2))
2006 return invalidSeconds();
2008 unsigned short field = offset.toUShort(&ok);
2010 if (!ok || field >= (prior ? 60 : 24))
2011 return invalidSeconds();
2012 seconds = seconds * 60 + field;
2014 return invalidSeconds();
2018 return invalidSeconds();
2023 return seconds * sign;
2027QUtcTimeZonePrivate::QUtcTimeZonePrivate(qint32 offsetSeconds)
2032 const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
2033 offsetSeconds, atLowerUtcOffset);
2034 if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
2035 QByteArrayView ianaId = data->id();
2036 qsizetype cut = ianaId.indexOf(
' ');
2037 QByteArrayView cutId = (cut < 0 ? ianaId : ianaId.first(cut));
2038 if (cutId == utcQByteArray()) {
2040 id = utcQByteArray();
2041 name = utcQString();
2044 id = cutId.toByteArray();
2045 name = QString::fromUtf8(id);
2047 Q_ASSERT(!name.isEmpty());
2049 name = isoOffsetFormat(offsetSeconds, QTimeZone::OffsetName);
2052 init(id, offsetSeconds, name, name, QLocale::AnyTerritory, name);
2055QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QByteArray &zoneId,
int offsetSeconds,
2056 const QString &name,
const QString &abbreviation,
2057 QLocale::Territory territory,
const QString &comment)
2059 init(zoneId, offsetSeconds, name, abbreviation, territory, comment);
2062QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QUtcTimeZonePrivate &other)
2063 : QTimeZonePrivate(other), m_name(other.m_name),
2064 m_abbreviation(other.m_abbreviation),
2065 m_comment(other.m_comment),
2066 m_territory(other.m_territory),
2067 m_offsetFromUtc(other.m_offsetFromUtc)
2071QUtcTimeZonePrivate::~QUtcTimeZonePrivate()
2075QUtcTimeZonePrivate *QUtcTimeZonePrivate::clone()
const
2077 return new QUtcTimeZonePrivate(*
this);
2080QTimeZonePrivate::Data QUtcTimeZonePrivate::data(qint64 forMSecsSinceEpoch)
const
2083 d.abbreviation = m_abbreviation;
2084 d.atMSecsSinceEpoch = forMSecsSinceEpoch;
2085 d.standardTimeOffset = d.offsetFromUtc = m_offsetFromUtc;
2086 d.daylightTimeOffset = 0;
2091QTimeZonePrivate::Data QUtcTimeZonePrivate::data(QTimeZone::TimeType timeType)
const
2094 return data(QDateTime::currentMSecsSinceEpoch());
2097bool QUtcTimeZonePrivate::isDataLocale(
const QLocale &locale)
const
2100 return isAnglicLocale(locale);
2103void QUtcTimeZonePrivate::init(
const QByteArray &zoneId,
int offsetSeconds,
const QString &name,
2104 const QString &abbreviation, QLocale::Territory territory,
2105 const QString &comment)
2108 m_offsetFromUtc = offsetSeconds;
2110 m_abbreviation = abbreviation;
2111 m_territory = territory;
2112 m_comment = comment;
2115QLocale::Territory QUtcTimeZonePrivate::territory()
const
2120QString QUtcTimeZonePrivate::comment()
const
2126QString QUtcTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch,
2127 QTimeZone::NameType nameType,
2128 const QLocale &locale)
const
2130 Q_UNUSED(atMSecsSinceEpoch);
2131 return displayName(QTimeZone::StandardTime, nameType, locale);
2134QString QUtcTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
2135 QTimeZone::NameType nameType,
2136 const QLocale &locale)
const
2138#if QT_CONFIG(timezone_locale)
2145 m_offsetFromUtc != 0 ? QString() :
2147 QTimeZonePrivate::displayName(timeType, nameType, locale);
2154 const auto matchesFallback = [](
int offset, QStringView name) {
2156 int seconds = offset % 60;
2157 int rounded = offset
2158 + (seconds > 30 || (seconds == 30 && (offset / 60) % 2)
2160 : (seconds < -30 || (seconds == -30 && (offset / 60) % 2)
2163 const QString avoid = isoOffsetFormat(rounded);
2166 Q_ASSERT(avoid.startsWith(
"UTC"_L1));
2167 Q_ASSERT(avoid.size() == 9);
2170 if (!(name.startsWith(
"GMT"_L1) || name.startsWith(
"UTC"_L1)) || name.size() < 5)
2173 QStringView tail{avoid};
2174 tail = tail.sliced(3);
2175 if (name.sliced(3) == tail)
2177 while (tail.endsWith(
":00"_L1))
2178 tail = tail.chopped(3);
2179 while (name.endsWith(
":00"_L1))
2180 name = name.chopped(3);
2184 const QChar sign = name[3] == u'\u2212' ? u'-' : name[3];
2186 return sign == tail[0] && tail.sliced(tail[1] == u'0' ? 2 : 1) == name.sliced(4);
2188 if (!name.isEmpty() && (m_name.isEmpty() || !matchesFallback(m_offsetFromUtc, name)))
2194 if (nameType == QTimeZone::ShortName)
2195 return m_abbreviation;
2196 if (nameType == QTimeZone::OffsetName)
2197 return isoOffsetFormat(m_offsetFromUtc);
2201QString QUtcTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch)
const
2203 Q_UNUSED(atMSecsSinceEpoch);
2204 return m_abbreviation;
2207qint32 QUtcTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch)
const
2209 Q_UNUSED(atMSecsSinceEpoch);
2210 return m_offsetFromUtc;
2213qint32 QUtcTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch)
const
2215 Q_UNUSED(atMSecsSinceEpoch);
2219QByteArray QUtcTimeZonePrivate::systemTimeZoneId()
const
2222 const emscripten::val date = emscripten::val::global(
"Date").new_();
2223 if (date.isUndefined())
2224 return utcQByteArray();
2227 const int offsetSeconds = -date.call<
int>(
"getTimezoneOffset") * 60;
2228 if (offsetSeconds == 0)
2229 return utcQByteArray();
2230 return isoOffsetFormat(offsetSeconds).toUtf8();
2232 return utcQByteArray();
2236bool QUtcTimeZonePrivate::isTimeZoneIdAvailable(QByteArrayView ianaId)
const
2239 for (
const UtcData &data : utcDataTable) {
2240 if (isEntryInIanaList(ianaId, data.id()))
2249QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds()
const
2252 QList<QByteArray> result;
2253 result.reserve(std::size(utcDataTable));
2254 for (
const UtcData &data : utcDataTable) {
2255 QByteArrayView id = data.id();
2257 while ((cut = id.indexOf(
' ')) >= 0) {
2258 result << id.first(cut).toByteArray();
2259 id = id.sliced(cut + 1);
2261 result << id.toByteArray();
2264 std::sort(result.begin(), result.end());
2269QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(QLocale::Territory country)
const
2272 if (country == QLocale::AnyTerritory)
2273 return availableTimeZoneIds();
2274 return QList<QByteArray>();
2277QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(qint32 offsetSeconds)
const
2281 QList<QByteArray> result;
2282 const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
2283 offsetSeconds, atLowerUtcOffset);
2284 if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
2285 QByteArrayView id = data->id();
2287 while ((cut = id.indexOf(
' ')) >= 0) {
2288 result << id.first(cut).toByteArray();
2289 id = id.sliced(cut + 1);
2291 result << id.toByteArray();
2296 QByteArray isoName = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName).toUtf8();
2297 if (offsetFromUtcString(isoName) == qint64(offsetSeconds) && !result.contains(isoName))
2300 std::sort(result.begin(), result.end());
2305#ifndef QT_NO_DATASTREAM
2306void QUtcTimeZonePrivate::serialize(QDataStream &ds)
const
2308 ds <<
QStringLiteral(
"OffsetFromUtc") << QString::fromUtf8(m_id) << m_offsetFromUtc << m_name
2309 << 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