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.digitBounds[asciiParse.digits.size() - spare];
1644 strPos += asciiField.endIndex();
1645 txtPos += asciiParse.endIndex();
1650 return R{ sign * (second + 60 * (minute + 60 * hour)), txtPos };
1653QTimeZonePrivate::NamePrefixMatch
1654findOffsetPrefixImpl(QStringView text,
const QLocale &locale,
1655 QtTemporalPattern::TemporalFieldFlags flags)
1657 QTimeZonePrivate::NamePrefixMatch best;
1667 const QUtcTimeZonePrivate greenwich(0);
1669 const QUtcTimeZonePrivate positive(+PatternAligner::OffsetMagnitude);
1670 const QUtcTimeZonePrivate negative(-PatternAligner::OffsetMagnitude);
1672 constexpr QTimeZone::NameType formats[] = {
1673 QTimeZone::OffsetName, QTimeZone::LongName, QTimeZone::ShortName
1675 constexpr QTimeZone::TimeType seasons[] = {
1676 QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime
1679 const auto acceptFormat = [flags](QTimeZone::NameType format) {
1680 using namespace QtTemporalPattern;
1682 if (!flags.testAnyFlags(FieldGroup::WidthMask | FieldGroup::FormMask))
1684 using Flag = TemporalFieldFlag;
1685 constexpr TemporalFieldFlags Textual = Flag::Verbal | Flag::Standalone;
1686 constexpr TemporalFieldFlags Long = Flag::Abbreviated | Flag::Short | Flag::Wide;
1688 case QTimeZone::OffsetName:
1689 return matchesFlagWithin(flags, Flag::Numeric, FieldGroup::FormMask);
1690 case QTimeZone::DefaultName:
1691 case QTimeZone::LongName:
1692 return matchesFlagsWithin(flags, Textual, FieldGroup::FormMask)
1693 && matchesFlagsWithin(flags, Long, FieldGroup::WidthMask);
1694 case QTimeZone::ShortName:
1695 return matchesFlagsWithin(flags, Textual, FieldGroup::FormMask)
1696 && matchesFlagWithin(flags, Flag::Narrow, FieldGroup::WidthMask);
1698 Q_UNREACHABLE_RETURN(
false);
1700 const auto acceptSeason = [flags](QTimeZone::TimeType season) {
1701 using namespace QtTemporalPattern;
1703 if (!flags.testAnyFlags(FieldGroup::SeasonMask))
1705 using Flag = TemporalFieldFlag;
1707 case QTimeZone::GenericTime:
1708 return flags.testFlag(Flag::GenericTime);
1709 case QTimeZone::StandardTime:
1710 return flags.testFlag(Flag::StandardTime);
1711 case QTimeZone::DaylightTime:
1712 return flags.testFlag(Flag::DaylightSavingTime);
1714 Q_UNREACHABLE_RETURN(
false);
1718
1719
1720
1721
1722
1723 QLocale digitLocale = locale;
1724 for (
int i = 0; i < 2; ++i) {
1729 const NumericPattern textPattern(text, digitLocale);
1730 Q_ASSERT(!textPattern.pattern.isEmpty());
1731 PatternAligner aligner(text, textPattern.pattern, flags);
1735 const auto consider = [&best, &aligner, txtSign = textPattern.sign, digitLocale]
1736 (QStringView candidate,
char sign, QTimeZone::TimeType season) {
1737 const auto idForOffset = [sign](
int offsetSeconds) ->
QByteArray {
1741 offsetSeconds = -offsetSeconds;
1742 return QTimeZonePrivate::isoOffsetFormat(offsetSeconds,
1743 QTimeZone::OffsetName).toLatin1();
1745 const auto localeDataFor = [loc = digitLocale] (
const NumericPattern &pat) {
1746 if (pat.hasDigits) {
1747 if (pat.digitsAreLocale)
1748 return QLocalePrivate::get(loc)->m_data;
1749 return QLocaleData::c();
1751 return static_cast<
const QLocaleData *>(
nullptr);
1753 if (
const NumericPattern pat(candidate, digitLocale);
1755 (pat.sign & sign) != sign || (txtSign & sign) == sign) {
1756 const auto parsed = aligner.match(
1757 candidate, pat.pattern, localeDataFor(pat), pat.sign);
1758 if (parsed && parsed.length > best.nameLength)
1759 best = { idForOffset(parsed.offset), parsed.length, season };
1760 return pat.digitsAreLocale;
1765 bool nativeSeen =
false;
1766 for (
auto season : seasons) {
1767 if (!acceptSeason(season))
1769 for (
auto format : formats) {
1770 if (!acceptFormat(format))
1772 if (
const QString pos = positive.displayName(season, format, locale);
1773 pos.size() > best.nameLength) {
1774 if (consider(pos,
'+', season))
1777 if (
const QString neg = negative.displayName(season, format, locale);
1778 neg.size() > best.nameLength) {
1779 if (consider(neg,
'-', season))
1782 if (
const QString nul = greenwich.displayName(season, format, locale);
1783 nul.size() > best.nameLength) {
1784 if (text.startsWith(nul))
1785 best = {
"UTC"_ba, nul.size() };
1787 if (best.nameLength == text.size())
1794 if (i == 0 && (digitLocale.zeroDigit() == u'0' || !nativeSeen))
1796 digitLocale = QLocale::c();
1803QTimeZonePrivate::NamePrefixMatch
1804QTimeZonePrivate::findOffsetPrefix(QStringView text,
const QLocale &locale,
1805 QtTemporalPattern::TemporalFieldFlags flags)
1807 NamePrefixMatch best;
1808 if (
auto match = findOffsetPrefixImpl(text, locale, flags))
1809 best = std::move(match);
1810 if (
auto match = findOffsetPrefixImpl(text, QLocale::c(), flags);
1811 match.nameLength > best.nameLength) {
1812 best = std::move(match);
1819QTimeZonePrivate::NamePrefixMatch
1820QTimeZonePrivate::findLongUtcPrefix(QStringView text)
1822 if (text.startsWith(u"UTC")) {
1823 if (text.size() > 4 && (text[3] == u'+' || text[3] == u'-')) {
1825 const auto digitAt = [text](qsizetype index) {
1826 using QtMiscUtils::isAsciiDigit;
1827 return index < text.size() && isAsciiDigit(text[index].unicode());
1829 qsizetype length = 3;
1833 Q_ASSERT(length < text.size());
1834 if (!digitAt(length + 1) || (groups && !digitAt(length + 2)))
1836 length += digitAt(length + 2) ? 3 : 2;
1837 }
while (++groups < 3 && length < text.size() && text[length] == u':');
1839 return { text.first(length).toLatin1(), length, QTimeZone::GenericTime };
1841 return { utcQByteArray(), 3, QTimeZone::GenericTime };
1847QByteArrayView QTimeZonePrivate::aliasToIana(QByteArrayView alias)
1849 const auto data = std::lower_bound(std::begin(aliasMappingTable), std::end(aliasMappingTable),
1850 alias, earlierAliasId);
1851 if (data != std::end(aliasMappingTable) && data->aliasId() == alias)
1852 return data->ianaId();
1859QByteArrayView QTimeZonePrivate::ianaIdToWindowsId(QByteArrayView id)
1861 const auto idUtf8 = QUtf8StringView(id);
1863 for (
const ZoneData &data : zoneDataTable) {
1864 for (
auto l1 : data.ids()) {
1866 return toWindowsIdLiteral(data.windowsIdKey);
1874QByteArrayView QTimeZonePrivate::windowsIdToDefaultIanaId(QByteArrayView windowsId)
1876 const auto data = std::lower_bound(std::begin(windowsDataTable), std::end(windowsDataTable),
1877 windowsId, earlierWindowsId);
1878 if (data != std::end(windowsDataTable) && data->windowsId() == windowsId) {
1879 QByteArrayView id = data->ianaId();
1880 Q_ASSERT(id.indexOf(
' ') == -1);
1886QByteArrayView QTimeZonePrivate::windowsIdToDefaultIanaId(QByteArrayView windowsId,
1887 QLocale::Territory territory)
1890 if (territory == QLocale::World) {
1892 return windowsIdToDefaultIanaId(windowsId);
1895 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1896 const qint16 land =
static_cast<quint16>(territory);
1897 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1898 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1901 if (data->territory == land)
1902 return *data->ids().begin();
1908QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(QByteArrayView windowsId)
1910 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1911 QList<QByteArray> list;
1913 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1914 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1916 for (
auto l1 : data->ids())
1917 list << QByteArray(l1.data(), l1.size());
1924 std::sort(list.begin(), list.end());
1928QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(QByteArrayView windowsId,
1929 QLocale::Territory territory)
1932 QList<QByteArray> list;
1933 if (territory == QLocale::World) {
1935 list << windowsIdToDefaultIanaId(windowsId).toByteArray();
1937 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1938 const qint16 land =
static_cast<quint16>(territory);
1939 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1940 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1943 if (data->territory == land) {
1944 for (
auto l1 : data->ids())
1945 list << QByteArray(l1.data(), l1.size());
1957 while ((cut = ianaIds.indexOf(
' ')) >= 0) {
1958 if (id == ianaIds.first(cut))
1960 ianaIds = ianaIds.sliced(cut + 1);
1962 return id == ianaIds;
1966
1967
1968
1969
1970
1971
1974QUtcTimeZonePrivate::QUtcTimeZonePrivate()
1976 const QString name = utcQString();
1977 init(utcQByteArray(), 0, name, name, QLocale::AnyTerritory, name);
1981QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QByteArray &id)
1984 for (
const UtcData &data : utcDataTable) {
1985 if (isEntryInIanaList(id, data.id())) {
1986 QString name = QString::fromUtf8(id);
1987 init(id, data.offsetFromUtc, name, name, QLocale::AnyTerritory, name);
1995qint64 QUtcTimeZonePrivate::offsetFromUtcString(QByteArrayView id)
2000 if (!id.startsWith(
"UTC") || id.size() < 5)
2001 return invalidSeconds();
2002 const char signChar = id.at(3);
2003 if (signChar !=
'-' && signChar !=
'+')
2004 return invalidSeconds();
2005 const int sign = signChar ==
'-' ? -1 : 1;
2009 for (
auto offset : QLatin1StringView(id.mid(4)).tokenize(
':'_L1)) {
2010 if (offset.size() > 2 || (prior && offset.size() < 2))
2011 return invalidSeconds();
2013 unsigned short field = offset.toUShort(&ok);
2015 if (!ok || field >= (prior ? 60 : 24))
2016 return invalidSeconds();
2017 seconds = seconds * 60 + field;
2019 return invalidSeconds();
2023 return invalidSeconds();
2028 return seconds * sign;
2032QUtcTimeZonePrivate::QUtcTimeZonePrivate(qint32 offsetSeconds)
2037 const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
2038 offsetSeconds, atLowerUtcOffset);
2039 if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
2040 QByteArrayView ianaId = data->id();
2041 qsizetype cut = ianaId.indexOf(
' ');
2042 QByteArrayView cutId = (cut < 0 ? ianaId : ianaId.first(cut));
2043 if (cutId == utcQByteArray()) {
2045 id = utcQByteArray();
2046 name = utcQString();
2049 id = cutId.toByteArray();
2050 name = QString::fromUtf8(id);
2052 Q_ASSERT(!name.isEmpty());
2054 name = isoOffsetFormat(offsetSeconds, QTimeZone::OffsetName);
2057 init(id, offsetSeconds, name, name, QLocale::AnyTerritory, name);
2060QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QByteArray &zoneId,
int offsetSeconds,
2061 const QString &name,
const QString &abbreviation,
2062 QLocale::Territory territory,
const QString &comment)
2064 init(zoneId, offsetSeconds, name, abbreviation, territory, comment);
2067QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QUtcTimeZonePrivate &other)
2068 : QTimeZonePrivate(other), m_name(other.m_name),
2069 m_abbreviation(other.m_abbreviation),
2070 m_comment(other.m_comment),
2071 m_territory(other.m_territory),
2072 m_offsetFromUtc(other.m_offsetFromUtc)
2076QUtcTimeZonePrivate::~QUtcTimeZonePrivate()
2080QUtcTimeZonePrivate *QUtcTimeZonePrivate::clone()
const
2082 return new QUtcTimeZonePrivate(*
this);
2085QTimeZonePrivate::Data QUtcTimeZonePrivate::data(qint64 forMSecsSinceEpoch)
const
2088 d.abbreviation = m_abbreviation;
2089 d.atMSecsSinceEpoch = forMSecsSinceEpoch;
2090 d.standardTimeOffset = d.offsetFromUtc = m_offsetFromUtc;
2091 d.daylightTimeOffset = 0;
2096QTimeZonePrivate::Data QUtcTimeZonePrivate::data(QTimeZone::TimeType timeType)
const
2099 return data(QDateTime::currentMSecsSinceEpoch());
2102bool QUtcTimeZonePrivate::isDataLocale(
const QLocale &locale)
const
2105 return isAnglicLocale(locale);
2108void QUtcTimeZonePrivate::init(
const QByteArray &zoneId,
int offsetSeconds,
const QString &name,
2109 const QString &abbreviation, QLocale::Territory territory,
2110 const QString &comment)
2113 m_offsetFromUtc = offsetSeconds;
2115 m_abbreviation = abbreviation;
2116 m_territory = territory;
2117 m_comment = comment;
2120QLocale::Territory QUtcTimeZonePrivate::territory()
const
2125QString QUtcTimeZonePrivate::comment()
const
2131QString QUtcTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch,
2132 QTimeZone::NameType nameType,
2133 const QLocale &locale)
const
2135 Q_UNUSED(atMSecsSinceEpoch);
2136 return displayName(QTimeZone::StandardTime, nameType, locale);
2139QString QUtcTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
2140 QTimeZone::NameType nameType,
2141 const QLocale &locale)
const
2143#if QT_CONFIG(timezone_locale)
2150 m_offsetFromUtc != 0 ? QString() :
2152 QTimeZonePrivate::displayName(timeType, nameType, locale);
2159 const auto matchesFallback = [](
int offset, QStringView name) {
2161 int seconds = offset % 60;
2162 int rounded = offset
2163 + (seconds > 30 || (seconds == 30 && (offset / 60) % 2)
2165 : (seconds < -30 || (seconds == -30 && (offset / 60) % 2)
2168 const QString avoid = isoOffsetFormat(rounded);
2171 Q_ASSERT(avoid.startsWith(
"UTC"_L1));
2172 Q_ASSERT(avoid.size() == 9);
2175 if (!(name.startsWith(
"GMT"_L1) || name.startsWith(
"UTC"_L1)) || name.size() < 5)
2178 QStringView tail{avoid};
2179 tail = tail.sliced(3);
2180 if (name.sliced(3) == tail)
2182 while (tail.endsWith(
":00"_L1))
2183 tail = tail.chopped(3);
2184 while (name.endsWith(
":00"_L1))
2185 name = name.chopped(3);
2189 const QChar sign = name[3] == u'\u2212' ? u'-' : name[3];
2191 return sign == tail[0] && tail.sliced(tail[1] == u'0' ? 2 : 1) == name.sliced(4);
2193 if (!name.isEmpty() && (m_name.isEmpty() || !matchesFallback(m_offsetFromUtc, name)))
2199 if (nameType == QTimeZone::ShortName)
2200 return m_abbreviation;
2201 if (nameType == QTimeZone::OffsetName)
2202 return isoOffsetFormat(m_offsetFromUtc);
2206QString QUtcTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch)
const
2208 Q_UNUSED(atMSecsSinceEpoch);
2209 return m_abbreviation;
2212qint32 QUtcTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch)
const
2214 Q_UNUSED(atMSecsSinceEpoch);
2215 return m_offsetFromUtc;
2218qint32 QUtcTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch)
const
2220 Q_UNUSED(atMSecsSinceEpoch);
2224QByteArray QUtcTimeZonePrivate::systemTimeZoneId()
const
2227 const emscripten::val date = emscripten::val::global(
"Date").new_();
2228 if (date.isUndefined())
2229 return utcQByteArray();
2232 const int offsetSeconds = -date.call<
int>(
"getTimezoneOffset") * 60;
2233 if (offsetSeconds == 0)
2234 return utcQByteArray();
2235 return isoOffsetFormat(offsetSeconds).toUtf8();
2237 return utcQByteArray();
2241bool QUtcTimeZonePrivate::isTimeZoneIdAvailable(QByteArrayView ianaId)
const
2244 for (
const UtcData &data : utcDataTable) {
2245 if (isEntryInIanaList(ianaId, data.id()))
2254QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds()
const
2257 QList<QByteArray> result;
2258 result.reserve(std::size(utcDataTable));
2259 for (
const UtcData &data : utcDataTable) {
2260 QByteArrayView id = data.id();
2262 while ((cut = id.indexOf(
' ')) >= 0) {
2263 result << id.first(cut).toByteArray();
2264 id = id.sliced(cut + 1);
2266 result << id.toByteArray();
2269 std::sort(result.begin(), result.end());
2274QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(QLocale::Territory country)
const
2277 if (country == QLocale::AnyTerritory)
2278 return availableTimeZoneIds();
2279 return QList<QByteArray>();
2282QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(qint32 offsetSeconds)
const
2286 QList<QByteArray> result;
2287 const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
2288 offsetSeconds, atLowerUtcOffset);
2289 if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
2290 QByteArrayView id = data->id();
2292 while ((cut = id.indexOf(
' ')) >= 0) {
2293 result << id.first(cut).toByteArray();
2294 id = id.sliced(cut + 1);
2296 result << id.toByteArray();
2301 QByteArray isoName = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName).toUtf8();
2302 if (offsetFromUtcString(isoName) == qint64(offsetSeconds) && !result.contains(isoName))
2305 std::sort(result.begin(), result.end());
2310#ifndef QT_NO_DATASTREAM
2311void QUtcTimeZonePrivate::serialize(QDataStream &ds)
const
2313 ds <<
QStringLiteral(
"OffsetFromUtc") << QString::fromUtf8(m_id) << m_offsetFromUtc << m_name
2314 << 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