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
819 const int MinSectionLength = 1;
820#if defined(Q_OS_ANDROID) || QT_CONFIG(icu)
823 const int MaxSectionLength = 17;
825 const int MaxSectionLength = 14;
827 int sectionLength = 0;
828 for (
const char *it = ianaId.begin(), *
const end = ianaId.end(); it != end; ++it, ++sectionLength) {
831 if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength)
834 }
else if (ch ==
'-') {
835 if (sectionLength == 0)
837 }
else if (!isAsciiLower(ch)
848 if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength)
853QString QTimeZonePrivate::isoOffsetFormat(
int offsetFromUtc, QTimeZone::NameType mode)
855 if (mode == QTimeZone::ShortName && !offsetFromUtc)
859 if (offsetFromUtc < 0) {
861 offsetFromUtc = -offsetFromUtc;
863 const int secs = offsetFromUtc % 60;
864 const int mins = (offsetFromUtc / 60) % 60;
865 const int hour = offsetFromUtc / 3600;
866 QString result = QString::asprintf(
"UTC%c%02d", sign, hour);
867 if (mode != QTimeZone::ShortName || secs || mins)
868 result += QString::asprintf(
":%02d", mins);
869 if (mode == QTimeZone::LongName || secs)
870 result += QString::asprintf(
":%02d", secs);
874QList<QByteArray> QTimeZonePrivate::getCanonicalZonesThenAliases()
876 static const QList<QByteArray> canonicalZonesThenAliases = []() {
877 QList<QByteArray> avail = QTimeZone::availableTimeZoneIds();
878 const auto isCanonical = [](
const QByteArray &name) {
880 return QTimeZonePrivate::aliasToIana(name).isEmpty();
882 [[maybe_unused]]
const QList<QByteArray>::const_iterator
883 firstAlias = std::partition(avail.begin(), avail.end(), isCanonical);
886 Q_ASSERT(std::all_of(firstAlias, avail.constEnd(),
887 [from = avail.constBegin(), to = firstAlias,
888 avail](
const QByteArray &alias) {
890 QByteArrayView iana = QTimeZonePrivate::aliasToIana(alias);
891 return std::find_if(from, to, [iana](
const QByteArray &zone) {
893 }) != to || !avail.contains(iana);
898 return canonicalZonesThenAliases;
901#if QT_CONFIG(icu) || !QT_CONFIG(timezone_locale)
902static QTimeZonePrivate::NamePrefixMatch
903findUtcOffsetPrefix(QStringView text,
const QLocale &locale)
907 qsizetype signLen = 0;
909 auto signStart = [&signLen, &sign, locale](QStringView str) {
910 QString signStr = locale.negativeSign();
911 if (str.startsWith(signStr)) {
913 signLen = signStr.size();
917 if (str.startsWith(u'\u2212')) {
922 signStr = locale.positiveSign();
923 if (str.startsWith(signStr)) {
925 signLen = signStr.size();
931 if (!((text.startsWith(u"UTC") || text.startsWith(u"GMT")) && signStart(text.sliced(3))))
934 QStringView offset = text.sliced(3 + signLen);
935 QStringIterator iter(offset);
936 qsizetype hourEnd = 0, hmMid = 0, minEnd = 0;
939 while (digits < 4 && iter.hasNext()) {
941 if (!QChar::isDigit(ch))
947 hourEnd = std::exchange(hmMid, std::exchange(minEnd, iter.index()));
952 QStringView hourStr, minStr;
954 minStr = offset.first(minEnd).sliced(hourEnd);
955 }
else if (digits < 3 && iter.hasNext() && QChar::isPunct(ch)) {
957 hmMid = iter.index();
959 while (mindig < 2 && iter.hasNext() && QChar::isDigit(iter.next())) {
961 minEnd = iter.index();
964 minStr = offset.first(minEnd).sliced(hmMid);
970 hourStr = offset.first(hourEnd);
973 uint hour = 0, minute = 0;
974 if (!hourStr.isEmpty())
975 hour = locale.toUInt(hourStr, &ok);
976 if (ok && !minStr.isEmpty()) {
977 minute = locale.toUInt(minStr, &ok);
979 if ((!ok || minute >= 60) && minEnd > hourEnd + minStr.size()) {
988 constexpr int MaxOffsetSeconds
989 = qMax(QTimeZone::MaxUtcOffsetSecs, -QTimeZone::MinUtcOffsetSecs);
990 if (!ok || (hour * 60 + minute) * 60 > MaxOffsetSeconds)
1000 std::snprintf(buffer,
sizeof(buffer),
"UTC%c%02u:%02u", sign, hour, minute);
1002 std::snprintf(buffer,
sizeof(buffer),
"UTC%c%02u", sign, hour);
1004 return { QByteArray(buffer, qstrnlen(buffer,
sizeof(buffer))),
1005 3 + signLen + minEnd,
1006 QTimeZone::GenericTime };
1009QTimeZonePrivate::NamePrefixMatch
1010QTimeZonePrivate::findLongNamePrefix(QStringView text,
const QLocale &locale,
1011 std::optional<qint64> atEpochMillis)
1015 QTimeZonePrivate::NamePrefixMatch best = findUtcOffsetPrefix(text, locale);
1017 const auto matchLength = [text](QStringView name) -> qsizetype {
1018 qsizetype length = 0;
1019 if (name.size() > 0 && text.startsWith(name, Qt::CaseInsensitive)) {
1020 length = name.size();
1022 while (!text.first(length).startsWith(name, Qt::CaseInsensitive)) {
1024 Q_ASSERT(length <= text.size());
1027 if (length == name.size()) {
1028 while (length > 0 && text.first(length - 1).startsWith(name, Qt::CaseInsensitive))
1034 const auto when = atEpochMillis
1035 ? QDateTime::fromMSecsSinceEpoch(*atEpochMillis, QTimeZone::UTC)
1037 const auto typeFor = [when](QTimeZone zone) {
1038 if (when.isValid() && zone.isDaylightTime(when))
1039 return QTimeZone::DaylightTime;
1041 return QTimeZone::GenericTime;
1043 const auto tryZone = [&](
const QByteArray &iana) {
1044 bool matched =
false;
1045 constexpr QTimeZone::TimeType types[]
1046 = { QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime };
1047 QTimeZone zone(iana);
1048 if (!zone.isValid())
1050 if (when.isValid()) {
1051 const QString name = zone.displayName(when, QTimeZone::LongName, locale);
1052 if (qsizetype match = matchLength(name); match > best.nameLength) {
1053 best = { iana, match, typeFor(zone) };
1057 const bool neverDst = !zone.hasDaylightTime();
1058 for (
const QTimeZone::TimeType type : types) {
1059 if (neverDst && type == QTimeZone::DaylightTime)
1061 const QString name = zone.displayName(type, QTimeZone::LongName, locale);
1062 if (qsizetype match = matchLength(name); match > best.nameLength) {
1063 best = { iana, match, type };
1071 const QList<QByteArray> allZones = getCanonicalZonesThenAliases();
1072 for (
const QByteArray &iana : allZones) {
1074 if (tryZone(iana) && best.nameLength >= text.size())
1089 QDuplicateTracker<QByteArray, std::size(aliasMappingTable)> triedAlready;
1090 for (
const QByteArray &iana : allZones)
1091 (
void) triedAlready.hasSeen(iana);
1092 for (
const auto &data : aliasMappingTable) {
1093 const QByteArray alias = data.aliasId().toByteArray();
1094 if (!triedAlready.hasSeen(alias) && tryZone(alias) && best.nameLength >= text.size())
1102QTimeZonePrivate::NamePrefixMatch
1103QTimeZonePrivate::findNarrowOffsetPrefix(QStringView,
const QLocale &)
1112#if QT_CONFIG(timezone_locale) && !QT_CONFIG(icu)
1115# define BACKEND_PROVIDES_OFFSET_PREFIX
1119#ifdef BACKEND_PROVIDES_OFFSET_PREFIX
1120# undef BACKEND_PROVIDES_OFFSET_PREFIX
1124struct NumericPattern
1126 NumericPattern(QStringView text,
const QLocale &locale);
1130 QList<qsizetype> pattern;
1132 bool digitsAreLocale;
1140 using Sign =
unsigned char;
1142 bool scanForToken(QStringView sought)
1146 if (sought.isEmpty())
1148 qsizetype tokensMatched = 0;
1149 const qsizetype n = sought.size();
1151 while ((idx = given.indexOf(sought, idx + n)) >= 0) {
1152 for (qsizetype i = 0; i < n; ++i)
1153 mask.setBit(idx + i);
1156 return tokensMatched > 0;
1159 Sign scanForSignsImpl(
const QLocale &locale, Sign signs)
1163 if (scanForToken(locale.positiveSign()))
1165 if (scanForToken(locale.negativeSign()))
1174 Scanner(QStringView text) : given(text), mask(text.size()) {}
1176 bool scanForDigits(
const QLocale &locale)
1180 bool matched =
false;
1181 for (
int i = 0; i < 10; ++i) {
1182 if (scanForToken(locale.toString(i)))
1188 Sign scanForSigns(
const QLocale &locale)
1192 Sign signs = scanForSignsImpl(locale,
'\0');
1193 signs = scanForSignsImpl(QLocale::c(), signs);
1194 if (scanForToken(u"\u2212"))
1199 QList<qsizetype> asPattern()
const
1203 QList<qsizetype> res;
1205 for (qsizetype i = 0, n = mask.size(); i < n; ++i) {
1206 if (mask.testBit(i)) {
1227NumericPattern::NumericPattern(QStringView text,
const QLocale &locale)
1232 Scanner scanner(text);
1233 digitsAreLocale = hasDigits = scanner.scanForDigits(locale);
1235 hasDigits = scanner.scanForDigits(QLocale::c());
1237 sign = scanner.scanForSigns(locale);
1239 pattern = scanner.asPattern();
1245 const QList<qsizetype> &txtPat;
1246 const QtTemporalPattern::TemporalFieldFlags options;
1247 qsizetype txtPos = 0, txtInd = 0;
1248 static constexpr uint Hour = 1, Minute = 2, Second = 4;
1249 uint seenFields = 0;
1250 using Digits = QLocaleData::DigitSequence;
1252 bool textMatch(QStringView str, qsizetype strPos, qsizetype slen, qsizetype tlen)
const
1256 if (txt.sliced(txtPos, tlen).compare(str.sliced(strPos, slen), Qt::CaseInsensitive) == 0)
1259 if (txtInd == 0 && slen == 3 && txt.first(3) == u"GMT" && str.first(3) == u"UTC") {
1260 Q_ASSERT(txtPos == 0);
1261 Q_ASSERT(strPos == 0);
1267 bool allowField(uint fieldBit)
const;
1268 bool allowSkipField(Digits &&fmt)
const;
1269 auto readField(QByteArrayView field, uint fieldBit,
int *value);
1270 bool scanExtraFields(QStringView sep,
const QLocaleData *locData,
1271 qsizetype &txtLen,
int &second);
1272 qsizetype scanMatchedFields(
const Digits &fmt,
const Digits &src,
bool allowExtraFields,
1273 int &hour,
int &minute,
int &second,
int &sign);
1282 PatternAligner(QStringView text,
const QList<qsizetype> &textPattern,
1283 QtTemporalPattern::TemporalFieldFlags flags)
1284 : txt(text), txtPat(textPattern), options(flags) {}
1289 static constexpr qint32 OffsetMagnitude = 38245;
1290 static constexpr QByteArrayView hourAscii{
"10"}, minuteAscii{
"37"}, secondAscii{
"25"};
1292 auto match(QStringView str,
const QList<qsizetype> &strPat,
1293 const QLocaleData *locData,
char signChar);
1296bool PatternAligner::allowField(uint fieldBit)
const
1298 if (!fieldBit || (seenFields & fieldBit))
1302 using namespace QtTemporalPattern::FieldGroup;
1303 if (!options.testAnyFlags(WidthMask))
1307 using namespace QtTemporalPattern;
1308 using F = TemporalFieldFlag;
1312 return matchesFlagsWithin(options, WidthMask & ~F::Narrow, WidthMask);
1314 return matchesFlagsWithin(options, F::Wide | F::Short, WidthMask);
1316 Q_UNREACHABLE_RETURN(
false);
1319bool PatternAligner::allowSkipField(Digits &&fmt)
const
1323 if (fmt.digits.startsWith(minuteAscii))
1325 else if (fmt.digits.startsWith(secondAscii))
1330 if (Q_UNLIKELY(seenFields & fieldBit))
1333 if (Q_UNLIKELY(seenFields & Second) && fieldBit == Minute)
1337 if (options.testAnyFlags(QtTemporalPattern::FieldGroup::WidthMask)) {
1338 using F = QtTemporalPattern::TemporalFieldFlag;
1343 return options.testAnyFlags(F::ZeroPad | F::Narrow);
1345 return options.testAnyFlags(F::ZeroPad | F::Narrow | F::Abbreviated);
1351auto PatternAligner::readField(QByteArrayView field, uint fieldBit,
int *value)
1354 constexpr int MaxHourOffset
1355 = qMax(QTimeZone::MaxUtcOffsetSecs, -QTimeZone::MinUtcOffsetSecs) / 3600;
1356 static_assert(MaxHourOffset > 9);
1358 QByteArrayView used;
1360 } res = { field,
false };
1362 if ((fieldBit != Hour && res.used.size() < 2) || !allowField(fieldBit) || !value)
1364 Q_ASSERT(*value == 0);
1365 seenFields |= fieldBit;
1366 if (res.used.size() > 2)
1367 res.used = res.used.first(2);
1368 *value = res.used.toInt(&res.ok);
1369 if (fieldBit == Hour && (!res.ok || *value > MaxHourOffset)) {
1371 *value = res.used.toInt(&res.ok);
1377bool PatternAligner::scanExtraFields(QStringView sep,
const QLocaleData *locData,
1378 qsizetype &txtLen,
int &second)
1382 while (txtInd + 1 < txtPat.size() && txt.sliced(txtPos, -txtLen) == sep) {
1384 txtLen = txtPat.at(++txtInd);
1386 Q_ASSERT((seenFields & Hour) && (seenFields & Minute));
1387 if (seenFields & Second)
1389 Q_ASSERT(second == 0);
1390 const Digits asciiParse
1391 = locData->digitSequence(txt.sliced(txtPos, txtLen));
1392 QByteArrayView found = asciiParse.digits;
1394 second = found.toInt(&ok);
1395 if (!ok || second >= 60)
1397 seenFields |= Second;
1399 txtLen = ++txtInd < txtPat.size() ? txtPat.at(txtInd) : 0;
1406qsizetype PatternAligner::scanMatchedFields(
const Digits &fmt,
const Digits &src,
1407 bool allowExtraFields,
1408 int &hour,
int &minute,
int &second,
int &sign)
1411 if (!fmt.digits.startsWith(hourAscii))
1413 if (sign || !src.sign)
1415 sign = fmt.sign == src.sign ? +1 : -1;
1416 }
else if (src.sign) {
1420 QByteArrayView chosen{fmt.digits}, found{src.digits};
1421 while (chosen.size() && found.size()) {
1422 const uint priorFields = seenFields;
1423 auto read = chosen.startsWith(hourAscii) ? readField(found, Hour, &hour)
1424 : chosen.startsWith(minuteAscii) ? readField(found, Minute, &minute)
1425 : chosen.startsWith(secondAscii) ? readField(found, Second, &second)
1426 : readField(found, 0u,
nullptr);
1430 if (read.used.size() < 2) {
1431 const uint newField = (seenFields ^ priorFields);
1434 if (newField == Hour) {
1441 found = found.sliced(read.used.size());
1442 allowExtraFields =
false;
1446 if (chosen.size() < fmt.digits.size() && (priorFields & Hour)) {
1449 seenFields = priorFields;
1450 Q_ASSERT(newField == Minute || newField == Second);
1451 if (newField == Minute)
1457 allowExtraFields =
false;
1463 Q_ASSERT(chosen.size() >= 2);
1464 chosen = chosen.sliced(2);
1465 found = found.sliced(read.used.size());
1467 if (chosen.size()) {
1468 Q_ASSERT(found.isEmpty());
1470 return (seenFields & Hour) ? 0 : -1;
1473 if (found.size() && (seenFields & Hour)) {
1476 if (allowExtraFields) {
1477 if (!Q_LIKELY(seenFields & Minute)) {
1478 const uint priorFields = seenFields;
1479 auto read = readField(found, Minute, &minute);
1480 if (!read.ok || read.used.size() < 2) {
1482 seenFields = priorFields;
1483 return found.size();
1485 found = found.sliced(read.used.size());
1487 if (!(seenFields & Second)) {
1488 const uint priorFields = seenFields;
1489 auto read = readField(found, Second, &second);
1490 if (!read.ok || read.used.size() < 2) {
1492 seenFields = priorFields;
1493 return found.size();
1495 found = found.sliced(read.used.size());
1499 return found.size();
1502 return found.size() ? -1 : 0;
1505auto PatternAligner::match(QStringView str,
const QList<qsizetype> &strPat,
1506 const QLocaleData *locData,
char signChar)
1508 Q_ASSERT(!str.isEmpty() && !strPat.isEmpty());
1513 constexpr auto AllowSign = Digits::Option::AllowSign;
1516 qsizetype length = 0;
1517 operator
bool()
const {
return length > 0; }
1519 if ((strPat.at(0) < 0) != (txtPat.at(0) < 0))
1523 int hour = 0, minute = 0, second = 0;
1524 int sign = !signChar;
1528 qsizetype skip = 0, strPos = 0, txtSkipped = 0;
1530 for (qsizetype len : strPat) {
1534 if (!allowSkipField(locData->digitSequence(str.sliced(strPos, len))))
1543 qsizetype txtLen = txtInd < txtPat.size() ? txtPat.at(txtInd) : 0;
1544 const bool maybeSep = txtInd > 0 && txtInd + 1 < strPat.size();
1547 if (!sep.isEmpty() && str.sliced(strPos, -len) != sep) {
1548 if (!scanExtraFields(sep, locData, txtLen, second))
1551 if (maybeSep && sep.isEmpty())
1552 sep = str.sliced(strPos, -len);
1554 if (!textMatch(str, strPos, -len, -txtLen)) {
1557 if (locData && !sep.isEmpty() && str.sliced(strPos, -len) == sep) {
1564 if (txtInd + 1 < strPat.size() - txtSkipped || len <= txtLen
1565 || !textMatch(str, strPos, -len, -len)) {
1579 if (txtPos >= txt.size()) {
1580 Q_ASSERT(txtInd >= txtPat.size());
1582 if (!sep.isEmpty() && txtPat.back() == sep.size() && txt.endsWith(sep)
1583 && strPat.back() == -sep.size() && str.endsWith(sep)) {
1588 txtInd = txtPat.size() - 1;
1589 txtPos -= sep.size();
1599 QStringView field = str.sliced(strPos, len);
1600 QStringView toParse = txt.sliced(txtPos, txtPat.at(txtInd));
1603 const Digits asciiField = locData->digitSequence(field, AllowSign);
1604 if (asciiField.endIndex() != field.size())
1606 const Digits asciiParse = locData->digitSequence(toParse, AllowSign);
1607 if (asciiParse.endIndex() != toParse.size())
1609 const uint priorFields = seenFields;
1611 const qsizetype spare
1612 = scanMatchedFields(asciiField, asciiParse,
1613 !txtSkipped && txtInd + 2 >= strPat.size() && sep.isEmpty(),
1614 hour, minute, second, sign);
1623 if (spare == 1 && (seenFields & Hour)) {
1625 }
else if (strPat.back() < 0) {
1626 if (sep.isEmpty() || !(priorFields & Hour)
1627 || strPat.back() != -sep.size() || !str.endsWith(sep)) {
1630 int offset = hour * 60;
1631 if (priorFields & Minute)
1634 if (priorFields & Second)
1636 return R{ sign * offset, txtPos };
1640 txtPos += asciiParse.digitStart
1641 + (asciiParse.digits.size() - spare) * asciiParse.digitWidth;
1645 strPos += asciiField.endIndex();
1646 txtPos += asciiParse.endIndex();
1651 return R{ sign * (second + 60 * (minute + 60 * hour)), txtPos };
1654QTimeZonePrivate::NamePrefixMatch
1655findOffsetPrefixImpl(QStringView text,
const QLocale &locale,
1656 QtTemporalPattern::TemporalFieldFlags flags)
1658 QTimeZonePrivate::NamePrefixMatch best;
1668 const QUtcTimeZonePrivate greenwich(0);
1670 const QUtcTimeZonePrivate positive(+PatternAligner::OffsetMagnitude);
1671 const QUtcTimeZonePrivate negative(-PatternAligner::OffsetMagnitude);
1673 constexpr QTimeZone::NameType formats[] = {
1674 QTimeZone::OffsetName, QTimeZone::LongName, QTimeZone::ShortName
1676 constexpr QTimeZone::TimeType seasons[] = {
1677 QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime
1680 const auto acceptFormat = [flags](QTimeZone::NameType format) {
1681 using namespace QtTemporalPattern;
1683 if (!flags.testAnyFlags(FieldGroup::WidthMask | FieldGroup::FormMask))
1685 using Flag = TemporalFieldFlag;
1686 constexpr TemporalFieldFlags Textual = Flag::Verbal | Flag::Standalone;
1687 constexpr TemporalFieldFlags Long = Flag::Abbreviated | Flag::Short | Flag::Wide;
1689 case QTimeZone::OffsetName:
1690 return matchesFlagWithin(flags, Flag::Numeric, FieldGroup::FormMask);
1691 case QTimeZone::DefaultName:
1692 case QTimeZone::LongName:
1693 return matchesFlagsWithin(flags, Textual, FieldGroup::FormMask)
1694 && matchesFlagsWithin(flags, Long, FieldGroup::WidthMask);
1695 case QTimeZone::ShortName:
1696 return matchesFlagsWithin(flags, Textual, FieldGroup::FormMask)
1697 && matchesFlagWithin(flags, Flag::Narrow, FieldGroup::WidthMask);
1699 Q_UNREACHABLE_RETURN(
false);
1701 const auto acceptSeason = [flags](QTimeZone::TimeType season) {
1702 using namespace QtTemporalPattern;
1704 if (!flags.testAnyFlags(FieldGroup::SeasonMask))
1706 using Flag = TemporalFieldFlag;
1708 case QTimeZone::GenericTime:
1709 return flags.testFlag(Flag::GenericTime);
1710 case QTimeZone::StandardTime:
1711 return flags.testFlag(Flag::StandardTime);
1712 case QTimeZone::DaylightTime:
1713 return flags.testFlag(Flag::DaylightSavingTime);
1715 Q_UNREACHABLE_RETURN(
false);
1719
1720
1721
1722
1723
1724 QLocale digitLocale = locale;
1725 for (
int i = 0; i < 2; ++i) {
1730 const NumericPattern textPattern(text, digitLocale);
1731 Q_ASSERT(!textPattern.pattern.isEmpty());
1732 PatternAligner aligner(text, textPattern.pattern, flags);
1736 const auto consider = [&best, &aligner, txtSign = textPattern.sign, digitLocale]
1737 (QStringView candidate,
char sign, QTimeZone::TimeType season) {
1738 const auto idForOffset = [sign](
int offsetSeconds) ->
QByteArray {
1742 offsetSeconds = -offsetSeconds;
1743 return QTimeZonePrivate::isoOffsetFormat(offsetSeconds,
1744 QTimeZone::OffsetName).toLatin1();
1746 const auto localeDataFor = [loc = digitLocale] (
const NumericPattern &pat) {
1747 if (pat.hasDigits) {
1748 if (pat.digitsAreLocale)
1749 return QLocalePrivate::get(loc)->m_data;
1750 return QLocaleData::c();
1752 return static_cast<
const QLocaleData *>(
nullptr);
1754 if (
const NumericPattern pat(candidate, digitLocale);
1756 (pat.sign & sign) != sign || (txtSign & sign) == sign) {
1757 const auto parsed = aligner.match(
1758 candidate, pat.pattern, localeDataFor(pat), pat.sign);
1759 if (parsed && parsed.length > best.nameLength)
1760 best = { idForOffset(parsed.offset), parsed.length, season };
1761 return pat.digitsAreLocale;
1766 bool nativeSeen =
false;
1767 for (
auto season : seasons) {
1768 if (!acceptSeason(season))
1770 for (
auto format : formats) {
1771 if (!acceptFormat(format))
1773 if (
const QString pos = positive.displayName(season, format, locale);
1774 pos.size() > best.nameLength) {
1775 if (consider(pos,
'+', season))
1778 if (
const QString neg = negative.displayName(season, format, locale);
1779 neg.size() > best.nameLength) {
1780 if (consider(neg,
'-', season))
1783 if (
const QString nul = greenwich.displayName(season, format, locale);
1784 nul.size() > best.nameLength) {
1785 if (text.startsWith(nul))
1786 best = {
"UTC"_ba, nul.size() };
1788 if (best.nameLength == text.size())
1795 if (i == 0 && (digitLocale.zeroDigit() == u'0' || !nativeSeen))
1797 digitLocale = QLocale::c();
1804QTimeZonePrivate::NamePrefixMatch
1805QTimeZonePrivate::findOffsetPrefix(QStringView text,
const QLocale &locale,
1806 QtTemporalPattern::TemporalFieldFlags flags)
1808 NamePrefixMatch best;
1809 if (
auto match = findOffsetPrefixImpl(text, locale, flags))
1810 best = std::move(match);
1811 if (
auto match = findOffsetPrefixImpl(text, QLocale::c(), flags);
1812 match.nameLength > best.nameLength) {
1813 best = std::move(match);
1820QTimeZonePrivate::NamePrefixMatch
1821QTimeZonePrivate::findLongUtcPrefix(QStringView text)
1823 if (text.startsWith(u"UTC")) {
1824 if (text.size() > 4 && (text[3] == u'+' || text[3] == u'-')) {
1826 const auto digitAt = [text](qsizetype index) {
1827 using QtMiscUtils::isAsciiDigit;
1828 return index < text.size() && isAsciiDigit(text[index].unicode());
1830 qsizetype length = 3;
1834 Q_ASSERT(length < text.size());
1835 if (!digitAt(length + 1) || (groups && !digitAt(length + 2)))
1837 length += digitAt(length + 2) ? 3 : 2;
1838 }
while (++groups < 3 && length < text.size() && text[length] == u':');
1840 return { text.first(length).toLatin1(), length, QTimeZone::GenericTime };
1842 return { utcQByteArray(), 3, QTimeZone::GenericTime };
1848QByteArrayView QTimeZonePrivate::aliasToIana(QByteArrayView alias)
1850 const auto data = std::lower_bound(std::begin(aliasMappingTable), std::end(aliasMappingTable),
1851 alias, earlierAliasId);
1852 if (data != std::end(aliasMappingTable) && data->aliasId() == alias)
1853 return data->ianaId();
1860QByteArrayView QTimeZonePrivate::ianaIdToWindowsId(QByteArrayView id)
1862 const auto idUtf8 = QUtf8StringView(id);
1864 for (
const ZoneData &data : zoneDataTable) {
1865 for (
auto l1 : data.ids()) {
1867 return toWindowsIdLiteral(data.windowsIdKey);
1875QByteArrayView QTimeZonePrivate::windowsIdToDefaultIanaId(QByteArrayView windowsId)
1877 const auto data = std::lower_bound(std::begin(windowsDataTable), std::end(windowsDataTable),
1878 windowsId, earlierWindowsId);
1879 if (data != std::end(windowsDataTable) && data->windowsId() == windowsId) {
1880 QByteArrayView id = data->ianaId();
1881 Q_ASSERT(id.indexOf(
' ') == -1);
1887QByteArrayView QTimeZonePrivate::windowsIdToDefaultIanaId(QByteArrayView windowsId,
1888 QLocale::Territory territory)
1891 if (territory == QLocale::World) {
1893 return windowsIdToDefaultIanaId(windowsId);
1896 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1897 const qint16 land =
static_cast<quint16>(territory);
1898 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1899 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1902 if (data->territory == land)
1903 return *data->ids().begin();
1909QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(QByteArrayView windowsId)
1911 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1912 QList<QByteArray> list;
1914 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1915 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1917 for (
auto l1 : data->ids())
1918 list << QByteArray(l1.data(), l1.size());
1925 std::sort(list.begin(), list.end());
1929QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(QByteArrayView windowsId,
1930 QLocale::Territory territory)
1933 QList<QByteArray> list;
1934 if (territory == QLocale::World) {
1936 list << windowsIdToDefaultIanaId(windowsId).toByteArray();
1938 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1939 const qint16 land =
static_cast<quint16>(territory);
1940 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1941 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1944 if (data->territory == land) {
1945 for (
auto l1 : data->ids())
1946 list << QByteArray(l1.data(), l1.size());
1958 while ((cut = ianaIds.indexOf(
' ')) >= 0) {
1959 if (id == ianaIds.first(cut))
1961 ianaIds = ianaIds.sliced(cut + 1);
1963 return id == ianaIds;
1967
1968
1969
1970
1971
1972
1975QUtcTimeZonePrivate::QUtcTimeZonePrivate()
1977 const QString name = utcQString();
1978 init(utcQByteArray(), 0, name, name, QLocale::AnyTerritory, name);
1982QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QByteArray &id)
1985 for (
const UtcData &data : utcDataTable) {
1986 if (isEntryInIanaList(id, data.id())) {
1987 QString name = QString::fromUtf8(id);
1988 init(id, data.offsetFromUtc, name, name, QLocale::AnyTerritory, name);
1996qint64 QUtcTimeZonePrivate::offsetFromUtcString(QByteArrayView id)
2001 if (!id.startsWith(
"UTC") || id.size() < 5)
2002 return invalidSeconds();
2003 const char signChar = id.at(3);
2004 if (signChar !=
'-' && signChar !=
'+')
2005 return invalidSeconds();
2006 const int sign = signChar ==
'-' ? -1 : 1;
2010 for (
auto offset : QLatin1StringView(id.mid(4)).tokenize(
':'_L1)) {
2011 if (offset.size() > 2 || (prior && offset.size() < 2))
2012 return invalidSeconds();
2014 unsigned short field = offset.toUShort(&ok);
2016 if (!ok || field >= (prior ? 60 : 24))
2017 return invalidSeconds();
2018 seconds = seconds * 60 + field;
2020 return invalidSeconds();
2024 return invalidSeconds();
2029 return seconds * sign;
2033QUtcTimeZonePrivate::QUtcTimeZonePrivate(qint32 offsetSeconds)
2038 const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
2039 offsetSeconds, atLowerUtcOffset);
2040 if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
2041 QByteArrayView ianaId = data->id();
2042 qsizetype cut = ianaId.indexOf(
' ');
2043 QByteArrayView cutId = (cut < 0 ? ianaId : ianaId.first(cut));
2044 if (cutId == utcQByteArray()) {
2046 id = utcQByteArray();
2047 name = utcQString();
2050 id = cutId.toByteArray();
2051 name = QString::fromUtf8(id);
2053 Q_ASSERT(!name.isEmpty());
2055 name = isoOffsetFormat(offsetSeconds, QTimeZone::OffsetName);
2058 init(id, offsetSeconds, name, name, QLocale::AnyTerritory, name);
2061QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QByteArray &zoneId,
int offsetSeconds,
2062 const QString &name,
const QString &abbreviation,
2063 QLocale::Territory territory,
const QString &comment)
2065 init(zoneId, offsetSeconds, name, abbreviation, territory, comment);
2068QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QUtcTimeZonePrivate &other)
2069 : QTimeZonePrivate(other), m_name(other.m_name),
2070 m_abbreviation(other.m_abbreviation),
2071 m_comment(other.m_comment),
2072 m_territory(other.m_territory),
2073 m_offsetFromUtc(other.m_offsetFromUtc)
2077QUtcTimeZonePrivate::~QUtcTimeZonePrivate()
2081QUtcTimeZonePrivate *QUtcTimeZonePrivate::clone()
const
2083 return new QUtcTimeZonePrivate(*
this);
2086QTimeZonePrivate::Data QUtcTimeZonePrivate::data(qint64 forMSecsSinceEpoch)
const
2089 d.abbreviation = m_abbreviation;
2090 d.atMSecsSinceEpoch = forMSecsSinceEpoch;
2091 d.standardTimeOffset = d.offsetFromUtc = m_offsetFromUtc;
2092 d.daylightTimeOffset = 0;
2097QTimeZonePrivate::Data QUtcTimeZonePrivate::data(QTimeZone::TimeType timeType)
const
2100 return data(QDateTime::currentMSecsSinceEpoch());
2103bool QUtcTimeZonePrivate::isDataLocale(
const QLocale &locale)
const
2106 return isAnglicLocale(locale);
2109void QUtcTimeZonePrivate::init(
const QByteArray &zoneId,
int offsetSeconds,
const QString &name,
2110 const QString &abbreviation, QLocale::Territory territory,
2111 const QString &comment)
2114 m_offsetFromUtc = offsetSeconds;
2116 m_abbreviation = abbreviation;
2117 m_territory = territory;
2118 m_comment = comment;
2121QLocale::Territory QUtcTimeZonePrivate::territory()
const
2126QString QUtcTimeZonePrivate::comment()
const
2132QString QUtcTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch,
2133 QTimeZone::NameType nameType,
2134 const QLocale &locale)
const
2136 Q_UNUSED(atMSecsSinceEpoch);
2137 return displayName(QTimeZone::StandardTime, nameType, locale);
2140QString QUtcTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
2141 QTimeZone::NameType nameType,
2142 const QLocale &locale)
const
2144#if QT_CONFIG(timezone_locale)
2151 m_offsetFromUtc != 0 ? QString() :
2153 QTimeZonePrivate::displayName(timeType, nameType, locale);
2160 const auto matchesFallback = [](
int offset, QStringView name) {
2162 int seconds = offset % 60;
2163 int rounded = offset
2164 + (seconds > 30 || (seconds == 30 && (offset / 60) % 2)
2166 : (seconds < -30 || (seconds == -30 && (offset / 60) % 2)
2169 const QString avoid = isoOffsetFormat(rounded);
2172 Q_ASSERT(avoid.startsWith(
"UTC"_L1));
2173 Q_ASSERT(avoid.size() == 9);
2176 if (!(name.startsWith(
"GMT"_L1) || name.startsWith(
"UTC"_L1)) || name.size() < 5)
2179 QStringView tail{avoid};
2180 tail = tail.sliced(3);
2181 if (name.sliced(3) == tail)
2183 while (tail.endsWith(
":00"_L1))
2184 tail = tail.chopped(3);
2185 while (name.endsWith(
":00"_L1))
2186 name = name.chopped(3);
2190 const QChar sign = name[3] == u'\u2212' ? u'-' : name[3];
2192 return sign == tail[0] && tail.sliced(tail[1] == u'0' ? 2 : 1) == name.sliced(4);
2194 if (!name.isEmpty() && (m_name.isEmpty() || !matchesFallback(m_offsetFromUtc, name)))
2200 if (nameType == QTimeZone::ShortName)
2201 return m_abbreviation;
2202 if (nameType == QTimeZone::OffsetName)
2203 return isoOffsetFormat(m_offsetFromUtc);
2207QString QUtcTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch)
const
2209 Q_UNUSED(atMSecsSinceEpoch);
2210 return m_abbreviation;
2213qint32 QUtcTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch)
const
2215 Q_UNUSED(atMSecsSinceEpoch);
2216 return m_offsetFromUtc;
2219qint32 QUtcTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch)
const
2221 Q_UNUSED(atMSecsSinceEpoch);
2225QByteArray QUtcTimeZonePrivate::systemTimeZoneId()
const
2228 const emscripten::val date = emscripten::val::global(
"Date").new_();
2229 if (date.isUndefined())
2230 return utcQByteArray();
2233 const int offsetSeconds = -date.call<
int>(
"getTimezoneOffset") * 60;
2234 if (offsetSeconds == 0)
2235 return utcQByteArray();
2236 return isoOffsetFormat(offsetSeconds).toUtf8();
2238 return utcQByteArray();
2242bool QUtcTimeZonePrivate::isTimeZoneIdAvailable(QByteArrayView ianaId)
const
2245 for (
const UtcData &data : utcDataTable) {
2246 if (isEntryInIanaList(ianaId, data.id()))
2255QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds()
const
2258 QList<QByteArray> result;
2259 result.reserve(std::size(utcDataTable));
2260 for (
const UtcData &data : utcDataTable) {
2261 QByteArrayView id = data.id();
2263 while ((cut = id.indexOf(
' ')) >= 0) {
2264 result << id.first(cut).toByteArray();
2265 id = id.sliced(cut + 1);
2267 result << id.toByteArray();
2270 std::sort(result.begin(), result.end());
2275QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(QLocale::Territory country)
const
2278 if (country == QLocale::AnyTerritory)
2279 return availableTimeZoneIds();
2280 return QList<QByteArray>();
2283QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(qint32 offsetSeconds)
const
2287 QList<QByteArray> result;
2288 const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
2289 offsetSeconds, atLowerUtcOffset);
2290 if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
2291 QByteArrayView id = data->id();
2293 while ((cut = id.indexOf(
' ')) >= 0) {
2294 result << id.first(cut).toByteArray();
2295 id = id.sliced(cut + 1);
2297 result << id.toByteArray();
2302 QByteArray isoName = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName).toUtf8();
2303 if (offsetFromUtcString(isoName) == qint64(offsetSeconds) && !result.contains(isoName))
2306 std::sort(result.begin(), result.end());
2311#ifndef QT_NO_DATASTREAM
2312void QUtcTimeZonePrivate::serialize(QDataStream &ds)
const
2314 ds <<
QStringLiteral(
"OffsetFromUtc") << QString::fromUtf8(m_id) << m_offsetFromUtc << m_name
2315 << 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