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/qnumeric_p.h>
20#if QT_CONFIG(icu) || !QT_CONFIG(timezone_locale)
21# include <private/qstringiterator_p.h>
23#include <private/qtools_p.h>
28#include <emscripten/val.h>
33using namespace QtMiscUtils;
41 return less.windowsIdKey < more.windowsIdKey
42 || (less.windowsIdKey == more.windowsIdKey && less.territory < more.territory);
50 return less.windowsIdKey < more.windowsIdKey
51 || less.windowsId().compare(more.windowsId(), Qt::CaseInsensitive) < 0;
57 return entry.offsetFromUtc < offsetSeconds;
62 return entry.windowsIdKey < winIdKey;
67 return entry.aliasId().compare(aliasId, Qt::CaseInsensitive) < 0;
72 return entry.windowsId().compare(winId, Qt::CaseInsensitive) < 0;
77 return entry.windowsIdKey < winIdKey;
85 winId, earlierWindowsId);
87 return data->windowsIdKey;
98 if (Q_LIKELY(data.windowsIdKey == windowsIdKey))
99 return data.windowsId();
103 windowsIdKey, atLowerWindowsKey);
105 return data->windowsId();
115 windowsIdKey, zoneAtLowerWindowsKey);
119
120
122QTimeZonePrivate::QTimeZonePrivate()
126 Q_ASSERT(std::is_sorted(std::begin(zoneDataTable), std::end(zoneDataTable),
128 Q_ASSERT(std::is_sorted(std::begin(windowsDataTable), std::end(windowsDataTable),
132QTimeZonePrivate::~QTimeZonePrivate()
136bool QTimeZonePrivate::operator==(
const QTimeZonePrivate &other)
const
141 return (m_id == other.m_id);
144bool QTimeZonePrivate::operator!=(
const QTimeZonePrivate &other)
const
146 return !(*
this == other);
149bool QTimeZonePrivate::isValid()
const
151 return !m_id.isEmpty();
154QByteArray QTimeZonePrivate::id()
const
159QLocale::Territory QTimeZonePrivate::territory()
const
162 const QLatin1StringView sought(m_id.data(), m_id.size());
163 for (
const ZoneData &data : zoneDataTable) {
164 for (QLatin1StringView token : data.ids()) {
166 return QLocale::Territory(data.territory);
169 return QLocale::AnyTerritory;
172QString QTimeZonePrivate::comment()
const
177QString QTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch,
178 QTimeZone::NameType nameType,
179 const QLocale &locale)
const
181 const Data tran = data(atMSecsSinceEpoch);
182 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
183 if (nameType == QTimeZone::OffsetName && isAnglicLocale(locale))
184 return isoOffsetFormat(tran.offsetFromUtc);
185 if (nameType == QTimeZone::ShortName && isDataLocale(locale))
186 return tran.abbreviation;
188 QTimeZone::TimeType timeType
189 = tran.daylightTimeOffset != 0 ? QTimeZone::DaylightTime : QTimeZone::StandardTime;
190#if QT_CONFIG(timezone_locale)
191 return localeName(atMSecsSinceEpoch, tran.offsetFromUtc, timeType, nameType, locale);
193 return displayName(timeType, nameType, locale);
199QString QTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
200 QTimeZone::NameType nameType,
201 const QLocale &locale)
const
203 const Data tran = data(timeType);
204 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
205#if QT_CONFIG(timezone_locale)
206 return localeName(tran.atMSecsSinceEpoch, tran.offsetFromUtc, timeType, nameType, locale);
208 if (nameType == QTimeZone::OffsetName && isAnglicLocale(locale))
209 return isoOffsetFormat(tran.offsetFromUtc);
215QString QTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch)
const
217 if (QLocale() != QLocale::c()) {
218 const QString name = displayName(atMSecsSinceEpoch, QTimeZone::ShortName, QLocale());
222 return displayName(atMSecsSinceEpoch, QTimeZone::ShortName, QLocale::c());
225int QTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch)
const
227 const int std = standardTimeOffset(atMSecsSinceEpoch);
228 const int dst = daylightTimeOffset(atMSecsSinceEpoch);
229 const int bad = invalidSeconds();
230 return std == bad || dst == bad ? bad : std + dst;
233int QTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch)
const
235 Q_UNUSED(atMSecsSinceEpoch);
236 return invalidSeconds();
239int QTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch)
const
241 Q_UNUSED(atMSecsSinceEpoch);
242 return invalidSeconds();
245bool QTimeZonePrivate::hasDaylightTime()
const
250bool QTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch)
const
252 Q_UNUSED(atMSecsSinceEpoch);
256QTimeZonePrivate::Data QTimeZonePrivate::data(QTimeZone::TimeType timeType)
const
259 const auto validMatch = [timeType](
const Data &tran) {
260 return tran.atMSecsSinceEpoch != invalidMSecs()
261 && ((timeType == QTimeZone::DaylightTime) != (tran.daylightTimeOffset == 0));
265 const qint64 currentMSecs = QDateTime::currentMSecsSinceEpoch();
266 Data tran = data(currentMSecs);
267 if (validMatch(tran))
270 if (hasTransitions()) {
272 tran = nextTransition(currentMSecs);
273 if (validMatch(tran))
280 tran = previousTransition(currentMSecs + 1);
281 while (tran.atMSecsSinceEpoch != invalidMSecs()) {
282 tran = previousTransition(tran.atMSecsSinceEpoch);
283 if (validMatch(tran))
291
292
293
294
295
296
297
298
299
300bool QTimeZonePrivate::isDataLocale(
const QLocale &locale)
const
303 return locale == QLocale::system();
306QTimeZonePrivate::Data QTimeZonePrivate::data(qint64 forMSecsSinceEpoch)
const
308 Q_UNUSED(forMSecsSinceEpoch);
313QDateTimePrivate::ZoneState QTimeZonePrivate::stateAtZoneTime(
314 qint64 forLocalMSecs, QDateTimePrivate::TransitionOptions resolve)
const
316 auto dataToState = [](
const Data &d) {
317 return QDateTimePrivate::ZoneState(d.atMSecsSinceEpoch + d.offsetFromUtc * 1000,
319 d.daylightTimeOffset ? QDateTimePrivate::DaylightTime
320 : QDateTimePrivate::StandardTime);
324
325
326
327
328
329
330
331 std::integral_constant<qint64, 17 * 3600 * 1000> seventeenHoursInMSecs;
332 static_assert(-seventeenHoursInMSecs / 1000 < QTimeZone::MinUtcOffsetSecs
333 && seventeenHoursInMSecs / 1000 > QTimeZone::MaxUtcOffsetSecs);
336 const qint64 recent =
337 qSubOverflow(forLocalMSecs, seventeenHoursInMSecs, &millis) || millis < minMSecs()
338 ? minMSecs() : millis;
340 const qint64 imminent =
341 qAddOverflow(forLocalMSecs, seventeenHoursInMSecs, &millis)
342 ? maxMSecs() : millis;
344 Q_ASSERT(recent < imminent && seventeenHoursInMSecs < imminent - recent + 1);
346 const Data past = data(recent), future = data(imminent);
347 if (future.atMSecsSinceEpoch == invalidMSecs()
348 && past.atMSecsSinceEpoch == invalidMSecs()) {
351 return { forLocalMSecs };
354 if (Q_LIKELY(past.offsetFromUtc == future.offsetFromUtc
355 && past.standardTimeOffset == future.standardTimeOffset
357 && past.abbreviation == future.abbreviation)) {
359 data.atMSecsSinceEpoch = forLocalMSecs - future.offsetFromUtc * 1000;
360 return dataToState(data);
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389 if (hasTransitions()) {
391
392
393
394
395
396
397
398
399
400
405 Q_ASSERT(forLocalMSecs < 0 ||
406 forLocalMSecs - tran.offsetFromUtc * 1000 >= tran.atMSecsSinceEpoch);
408 Data nextTran = nextTransition(tran.atMSecsSinceEpoch);
410
411
412
413
414
415
416
417 while (nextTran.atMSecsSinceEpoch != invalidMSecs()
418 && forLocalMSecs > nextTran.atMSecsSinceEpoch + nextTran.offsetFromUtc * 1000) {
419 Data newTran = nextTransition(nextTran.atMSecsSinceEpoch);
420 if (newTran.atMSecsSinceEpoch == invalidMSecs()
421 || newTran.atMSecsSinceEpoch + newTran.offsetFromUtc * 1000 > imminent) {
428 const qint64 nextStart = nextTran.atMSecsSinceEpoch;
431 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
433 Q_ASSERT(forLocalMSecs < 0
434 || forLocalMSecs - tran.offsetFromUtc * 1000 > tran.atMSecsSinceEpoch);
436 tran.atMSecsSinceEpoch = forLocalMSecs - tran.offsetFromUtc * 1000;
444 if (nextStart == invalidMSecs() && tran.offsetFromUtc == future.offsetFromUtc)
445 return dataToState(tran);
448 if (tran.atMSecsSinceEpoch != invalidMSecs() && nextStart != invalidMSecs()) {
450
451
452
453
454
455
456
457
458
460 nextTran.atMSecsSinceEpoch = forLocalMSecs - nextTran.offsetFromUtc * 1000;
462 bool fallBack =
false;
463 if (nextStart > nextTran.atMSecsSinceEpoch) {
465 if (nextStart > tran.atMSecsSinceEpoch)
466 return dataToState(tran);
468 Q_ASSERT(tran.offsetFromUtc < nextTran.offsetFromUtc);
470 }
else if (nextStart <= tran.atMSecsSinceEpoch) {
472 return dataToState(nextTran);
474 Q_ASSERT(nextTran.offsetFromUtc < tran.offsetFromUtc);
482 = resolve.testFlag(QDateTimePrivate::FlipForReverseDst)
483 && (fallBack ? !tran.daylightTimeOffset && nextTran.daylightTimeOffset
484 : tran.daylightTimeOffset && !nextTran.daylightTimeOffset);
487 if (resolve.testFlag(flipped
488 ? QDateTimePrivate::FoldUseBefore
489 : QDateTimePrivate::FoldUseAfter)) {
490 return dataToState(nextTran);
492 if (resolve.testFlag(flipped
493 ? QDateTimePrivate::FoldUseAfter
494 : QDateTimePrivate::FoldUseBefore)) {
495 return dataToState(tran);
499
500
501
502
503
504 std::swap(tran.atMSecsSinceEpoch, nextTran.atMSecsSinceEpoch);
505 if (resolve.testFlag(flipped
506 ? QDateTimePrivate::GapUseBefore
507 : QDateTimePrivate::GapUseAfter))
508 return dataToState(nextTran);
509 if (resolve.testFlag(flipped
510 ? QDateTimePrivate::GapUseAfter
511 : QDateTimePrivate::GapUseBefore))
512 return dataToState(tran);
515 return {forLocalMSecs};
522 qint64 utcEpochMSecs;
525 int early = past.offsetFromUtc;
526 int late = future.offsetFromUtc;
527 if (early == late || late == invalidSeconds()) {
528 if (early == invalidSeconds()
529 || qSubOverflow(forLocalMSecs, early * qint64(1000), &utcEpochMSecs)) {
530 return {forLocalMSecs};
534 const qint64 forEarly = forLocalMSecs - early * 1000;
535 const qint64 forLate = forLocalMSecs - late * 1000;
538 const bool earlyOk = offsetFromUtc(forEarly) == early;
539 const bool lateOk = offsetFromUtc(forLate) == late;
543 Q_ASSERT(early > late);
545 if (resolve.testFlag(QDateTimePrivate::FoldUseBefore))
546 utcEpochMSecs = forEarly;
547 else if (resolve.testFlag(QDateTimePrivate::FoldUseAfter))
548 utcEpochMSecs = forLate;
550 return {forLocalMSecs};
553 utcEpochMSecs = forEarly;
557 utcEpochMSecs = forLate;
560 Q_ASSERT(late > early);
561 const int dstStep = (late - early) * 1000;
562 if (resolve.testFlag(QDateTimePrivate::GapUseBefore))
563 utcEpochMSecs = forEarly - dstStep;
564 else if (resolve.testFlag(QDateTimePrivate::GapUseAfter))
565 utcEpochMSecs = forLate + dstStep;
567 return {forLocalMSecs};
571 return dataToState(data(utcEpochMSecs));
574bool QTimeZonePrivate::hasTransitions()
const
579QTimeZonePrivate::Data QTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch)
const
581 Q_UNUSED(afterMSecsSinceEpoch);
585QTimeZonePrivate::Data QTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch)
const
587 Q_UNUSED(beforeMSecsSinceEpoch);
591QTimeZonePrivate::DataList QTimeZonePrivate::transitions(qint64 fromMSecsSinceEpoch,
592 qint64 toMSecsSinceEpoch)
const
595 if (toMSecsSinceEpoch >= fromMSecsSinceEpoch) {
597 Data next = nextTransition(fromMSecsSinceEpoch - 1);
598 while (next.atMSecsSinceEpoch != invalidMSecs()
599 && next.atMSecsSinceEpoch <= toMSecsSinceEpoch) {
601 next = nextTransition(next.atMSecsSinceEpoch);
607QByteArray QTimeZonePrivate::systemTimeZoneId()
const
612template <
typename Pred>
621 name, earlierAliasId);
623 name = data->ianaId();
631 for (
const auto &data : aliasMappingTable) {
632 QByteArrayView alias = data.aliasId();
633 if (data.ianaId() == name && test(alias))
639QByteArrayView QTimeZonePrivate::availableAlias(QByteArrayView ianaId)
const
641 return aliasMatching(ianaId, [
this](QByteArrayView id) {
return isTimeZoneIdAvailable(id); });
644bool QTimeZonePrivate::isTimeZoneIdAvailable(QByteArrayView ianaId)
const
648 const QList<QByteArray> tzIds = availableTimeZoneIds();
649 return std::binary_search(tzIds.begin(), tzIds.end(), ianaId);
655 std::sort(desired.begin(), desired.end());
656 const auto newEnd =
std::unique(desired.begin(), desired.end());
657 const auto newSize =
std::distance(desired.begin(), newEnd);
659 result.reserve(qMin(all.size(), newSize));
660 std::set_intersection(all.begin(), all.end(), desired.cbegin(),
661 std::next(desired.cbegin(), newSize),
std::back_inserter(result));
665QList<QByteArrayView> QTimeZonePrivate::matchingTimeZoneIds(QLocale::Territory territory)
const
668 QList<QByteArrayView> regions;
669#if QT_CONFIG(timezone_locale) && !QT_CONFIG(icu)
670 regions = QtTimeZoneLocale::ianaIdsForTerritory(territory);
673 if (territory == QLocale::World) {
676 for (
const WindowsData &data : windowsDataTable)
677 regions << data.ianaId();
679 for (
const ZoneData &data : zoneDataTable) {
680 if (data.territory == territory) {
681 for (
auto l1 : data.ids())
682 regions << QByteArrayView(l1.data(), l1.size());
689QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(QLocale::Territory territory)
const
691 return selectAvailable(matchingTimeZoneIds(territory), availableTimeZoneIds());
694QList<QByteArrayView> QTimeZonePrivate::matchingTimeZoneIds(
int offsetFromUtc)
const
697 QList<QByteArrayView> offsets;
699 for (
const WindowsData &winData : windowsDataTable) {
700 if (winData.offsetFromUtc == offsetFromUtc) {
701 for (
auto data = zoneStartForWindowsId(winData.windowsIdKey);
702 data != std::end(zoneDataTable) && data->windowsIdKey == winData.windowsIdKey;
704 for (
auto l1 : data->ids())
705 offsets << QByteArrayView(l1.data(), l1.size());
712QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(
int offsetFromUtc)
const
714 return selectAvailable(matchingTimeZoneIds(offsetFromUtc), availableTimeZoneIds());
717QList<QByteArray> QTimeZonePrivate::uniqueSortedAliasPadded(QList<QByteArray> &&zoneIds)
720 const QList<QByteArray> source = zoneIds;
722 for (
const auto &name : source) {
723 const auto zone = aliasToIana(name);
724 if (!zone.isEmpty()) {
725 zoneIds << zone.toByteArray();
726 Q_ASSERT(aliasToIana(zone).isEmpty());
729 std::sort(zoneIds.begin(), zoneIds.end());
730 zoneIds.erase(std::unique(zoneIds.begin(), zoneIds.end()), zoneIds.end());
734QList<QByteArray> QTimeZonePrivate::padSortedWithAliases(QList<QByteArray> &&zoneIds)
737 const QList<QByteArray> source = zoneIds;
738 for (
const auto &name : source) {
739 const auto zone = aliasToIana(name);
740 const auto pos = std::lower_bound(zoneIds.begin(), zoneIds.end(), zone);
741 if (pos != zoneIds.end() && *pos != zone)
742 zoneIds.insert(pos, zone.toByteArray());
747#ifndef QT_NO_DATASTREAM
748void QTimeZonePrivate::serialize(QDataStream &ds)
const
750 ds << QString::fromUtf8(m_id);
756QTimeZone::OffsetData QTimeZonePrivate::invalidOffsetData()
758 return { QString(), QDateTime(),
759 invalidSeconds(), invalidSeconds(), invalidSeconds() };
762QTimeZone::OffsetData QTimeZonePrivate::toOffsetData(
const QTimeZonePrivate::Data &data)
764 if (data.atMSecsSinceEpoch == invalidMSecs())
765 return invalidOffsetData();
769 QDateTime::fromMSecsSinceEpoch(data.atMSecsSinceEpoch, QTimeZone::UTC),
770 data.offsetFromUtc, data.standardTimeOffset, data.daylightTimeOffset };
774bool QTimeZonePrivate::isValidId(QByteArrayView ianaId)
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
816 const int MinSectionLength = 1;
817#if defined(Q_OS_ANDROID) || QT_CONFIG(icu)
820 const int MaxSectionLength = 17;
822 const int MaxSectionLength = 14;
824 int sectionLength = 0;
825 for (
const char *it = ianaId.begin(), *
const end = ianaId.end(); it != end; ++it, ++sectionLength) {
828 if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength)
831 }
else if (ch ==
'-') {
832 if (sectionLength == 0)
834 }
else if (!isAsciiLower(ch)
845 if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength)
850QString QTimeZonePrivate::isoOffsetFormat(
int offsetFromUtc, QTimeZone::NameType mode)
852 if (mode == QTimeZone::ShortName && !offsetFromUtc)
856 if (offsetFromUtc < 0) {
858 offsetFromUtc = -offsetFromUtc;
860 const int secs = offsetFromUtc % 60;
861 const int mins = (offsetFromUtc / 60) % 60;
862 const int hour = offsetFromUtc / 3600;
863 QString result = QString::asprintf(
"UTC%c%02d", sign, hour);
864 if (mode != QTimeZone::ShortName || secs || mins)
865 result += QString::asprintf(
":%02d", mins);
866 if (mode == QTimeZone::LongName || secs)
867 result += QString::asprintf(
":%02d", secs);
871#if QT_CONFIG(icu) || !QT_CONFIG(timezone_locale)
872static QTimeZonePrivate::NamePrefixMatch
873findUtcOffsetPrefix(QStringView text,
const QLocale &locale)
877 qsizetype signLen = 0;
879 auto signStart = [&signLen, &sign, locale](QStringView str) {
880 QString signStr = locale.negativeSign();
881 if (str.startsWith(signStr)) {
883 signLen = signStr.size();
887 if (str.startsWith(u'\u2212')) {
892 signStr = locale.positiveSign();
893 if (str.startsWith(signStr)) {
895 signLen = signStr.size();
901 if (!((text.startsWith(u"UTC") || text.startsWith(u"GMT")) && signStart(text.sliced(3))))
904 QStringView offset = text.sliced(3 + signLen);
905 QStringIterator iter(offset);
906 qsizetype hourEnd = 0, hmMid = 0, minEnd = 0;
909 while (digits < 4 && iter.hasNext()) {
911 if (!QChar::isDigit(ch))
917 hourEnd = std::exchange(hmMid, std::exchange(minEnd, iter.index()));
922 QStringView hourStr, minStr;
924 minStr = offset.first(minEnd).sliced(hourEnd);
925 }
else if (digits < 3 && iter.hasNext() && QChar::isPunct(ch)) {
927 hmMid = iter.index();
929 while (mindig < 2 && iter.hasNext() && QChar::isDigit(iter.next())) {
931 minEnd = iter.index();
934 minStr = offset.first(minEnd).sliced(hmMid);
940 hourStr = offset.first(hourEnd);
943 uint hour = 0, minute = 0;
944 if (!hourStr.isEmpty())
945 hour = locale.toUInt(hourStr, &ok);
946 if (ok && !minStr.isEmpty()) {
947 minute = locale.toUInt(minStr, &ok);
949 if ((!ok || minute >= 60) && minEnd > hourEnd + minStr.size()) {
958 constexpr int MaxOffsetSeconds
959 = qMax(QTimeZone::MaxUtcOffsetSecs, -QTimeZone::MinUtcOffsetSecs);
960 if (!ok || (hour * 60 + minute) * 60 > MaxOffsetSeconds)
970 std::snprintf(buffer,
sizeof(buffer),
"UTC%c%02u:%02u", sign, hour, minute);
972 std::snprintf(buffer,
sizeof(buffer),
"UTC%c%02u", sign, hour);
974 return { QByteArray(buffer, qstrnlen(buffer,
sizeof(buffer))),
975 3 + signLen + minEnd,
976 QTimeZone::GenericTime };
979QTimeZonePrivate::NamePrefixMatch
980QTimeZonePrivate::findLongNamePrefix(QStringView text,
const QLocale &locale,
981 std::optional<qint64> atEpochMillis)
984 const auto matchLength = [text](QStringView name) -> qsizetype {
985 qsizetype length = 0;
986 if (name.size() > 0 && text.startsWith(name, Qt::CaseInsensitive)) {
987 length = name.size();
989 while (!text.first(length).startsWith(name, Qt::CaseInsensitive)) {
991 Q_ASSERT(length <= text.size());
994 if (length == name.size()) {
995 while (length > 0 && text.first(length - 1).startsWith(name, Qt::CaseInsensitive))
1001 const auto when = atEpochMillis
1002 ? QDateTime::fromMSecsSinceEpoch(*atEpochMillis, QTimeZone::UTC)
1004 const auto typeFor = [when](QTimeZone zone) {
1005 if (when.isValid() && zone.isDaylightTime(when))
1006 return QTimeZone::DaylightTime;
1008 return QTimeZone::GenericTime;
1010 QTimeZonePrivate::NamePrefixMatch best = findUtcOffsetPrefix(text, locale);
1011 constexpr QTimeZone::TimeType types[]
1012 = { QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime };
1013 const QList<QByteArray> allZones = []() {
1014 QList<QByteArray> avail = QTimeZone::availableTimeZoneIds();
1015 const auto isCanonical = [](
const QByteArray &name) {
1017 return QTimeZonePrivate::aliasToIana(name).isEmpty();
1020 const auto firstAlias = std::partition(avail.begin(), avail.end(), isCanonical);
1023 Q_ASSERT(std::all_of(
static_cast<
typeof(avail.constBegin())>(firstAlias),
1025 [from = avail.constBegin(),
1026 to =
static_cast<
typeof(avail.constBegin())>(firstAlias),
1027 avail](
const QByteArray &alias) {
1029 QByteArrayView iana = QTimeZonePrivate::aliasToIana(alias);
1030 return std::find_if(from, to, [iana](
const QByteArray &zone) {
1031 return zone == iana;
1032 }) != to || !avail.contains(iana);
1038 for (
const QByteArray &iana : allZones) {
1039 QTimeZone zone(iana);
1040 if (!zone.isValid())
1042 if (when.isValid()) {
1043 const QString name = zone.displayName(when, QTimeZone::LongName, locale);
1044 if (qsizetype match = matchLength(name); match > best.nameLength)
1045 best = { iana, match, typeFor(zone) };
1047 const bool neverDst = !zone.hasDaylightTime();
1048 for (
const QTimeZone::TimeType type : types) {
1049 if (neverDst && type == QTimeZone::DaylightTime)
1051 const QString name = zone.displayName(type, QTimeZone::LongName, locale);
1052 if (qsizetype match = matchLength(name); match > best.nameLength)
1053 best = { iana, match, type };
1057 if (best.nameLength >= text.size())
1067QTimeZonePrivate::NamePrefixMatch
1068QTimeZonePrivate::findNarrowOffsetPrefix(QStringView,
const QLocale &)
1077#if QT_CONFIG(timezone_locale) && !QT_CONFIG(icu)
1080# define BACKEND_PROVIDES_OFFSET_PREFIX
1084#ifdef BACKEND_PROVIDES_OFFSET_PREFIX
1085# undef BACKEND_PROVIDES_OFFSET_PREFIX
1089struct NumericPattern
1091 NumericPattern(QStringView text,
const QLocale &locale);
1095 QList<qsizetype> pattern;
1097 bool digitsAreLocale;
1105 using Sign =
unsigned char;
1107 bool scanForToken(QStringView sought)
1111 if (sought.isEmpty())
1113 qsizetype tokensMatched = 0;
1114 const qsizetype n = sought.size();
1116 while ((idx = given.indexOf(sought, idx + n)) >= 0) {
1117 for (qsizetype i = 0; i < n; ++i)
1118 mask.setBit(idx + i);
1121 return tokensMatched > 0;
1124 Sign scanForSignsImpl(
const QLocale &locale, Sign signs)
1128 if (scanForToken(locale.positiveSign()))
1130 if (scanForToken(locale.negativeSign()))
1139 Scanner(QStringView text) : given(text), mask(text.size()) {}
1141 bool scanForDigits(
const QLocale &locale)
1145 bool matched =
false;
1146 for (
int i = 0; i < 10; ++i) {
1147 if (scanForToken(locale.toString(i)))
1153 Sign scanForSigns(
const QLocale &locale)
1157 Sign signs = scanForSignsImpl(locale,
'\0');
1158 signs = scanForSignsImpl(QLocale::c(), signs);
1159 if (scanForToken(u"\u2212"))
1164 QList<qsizetype> asPattern()
const
1168 QList<qsizetype> res;
1170 for (qsizetype i = 0, n = mask.size(); i < n; ++i) {
1171 if (mask.testBit(i)) {
1192NumericPattern::NumericPattern(QStringView text,
const QLocale &locale)
1197 Scanner scanner(text);
1198 digitsAreLocale = hasDigits = scanner.scanForDigits(locale);
1200 hasDigits = scanner.scanForDigits(QLocale::c());
1202 sign = scanner.scanForSigns(locale);
1204 pattern = scanner.asPattern();
1210 const QList<qsizetype> &txtPat;
1211 const QtTemporalPattern::TemporalFieldFlags options;
1212 qsizetype txtPos = 0, txtInd = 0;
1213 static constexpr uint Hour = 1, Minute = 2, Second = 4;
1214 uint seenFields = 0;
1215 using Digits = QLocaleData::DigitSequence;
1217 bool textMatch(QStringView str, qsizetype strPos, qsizetype slen, qsizetype tlen)
const
1221 if (txt.sliced(txtPos, tlen).compare(str.sliced(strPos, slen), Qt::CaseInsensitive) == 0)
1224 if (txtInd == 0 && slen == 3 && txt.first(3) == u"GMT" && str.first(3) == u"UTC") {
1225 Q_ASSERT(txtPos == 0);
1226 Q_ASSERT(strPos == 0);
1232 bool allowField(uint fieldBit)
const;
1233 bool allowSkipField(Digits &&fmt)
const;
1234 auto readField(QByteArrayView field, uint fieldBit,
int *value);
1235 bool scanExtraFields(QStringView sep,
const QLocaleData *locData,
1236 qsizetype &txtLen,
int &second);
1237 qsizetype scanMatchedFields(
const Digits &fmt,
const Digits &src,
bool allowExtraFields,
1238 int &hour,
int &minute,
int &second,
int &sign);
1247 PatternAligner(QStringView text,
const QList<qsizetype> &textPattern,
1248 QtTemporalPattern::TemporalFieldFlags flags)
1249 : txt(text), txtPat(textPattern), options(flags) {}
1254 static constexpr qint32 OffsetMagnitude = 38245;
1255 static constexpr QByteArrayView hourAscii{
"10"}, minuteAscii{
"37"}, secondAscii{
"25"};
1257 auto match(QStringView str,
const QList<qsizetype> &strPat,
1258 const QLocaleData *locData,
char signChar);
1261bool PatternAligner::allowField(uint fieldBit)
const
1263 if (!fieldBit || (seenFields & fieldBit))
1267 using namespace QtTemporalPattern::FieldGroup;
1268 if (!options.testAnyFlags(WidthMask))
1272 using namespace QtTemporalPattern;
1273 using F = TemporalFieldFlag;
1277 return matchesFlagsWithin(options, WidthMask & ~F::Narrow, WidthMask);
1279 return matchesFlagsWithin(options, F::Wide | F::Short, WidthMask);
1281 Q_UNREACHABLE_RETURN(
false);
1284bool PatternAligner::allowSkipField(Digits &&fmt)
const
1288 if (fmt.digits.startsWith(minuteAscii))
1290 else if (fmt.digits.startsWith(secondAscii))
1295 if (Q_UNLIKELY(seenFields & fieldBit))
1298 if (Q_UNLIKELY(seenFields & Second) && fieldBit == Minute)
1302 if (options.testAnyFlags(QtTemporalPattern::FieldGroup::WidthMask)) {
1303 using F = QtTemporalPattern::TemporalFieldFlag;
1308 return options.testAnyFlags(F::ZeroPad | F::Narrow);
1310 return options.testAnyFlags(F::ZeroPad | F::Narrow | F::Abbreviated);
1316auto PatternAligner::readField(QByteArrayView field, uint fieldBit,
int *value)
1319 constexpr int MaxHourOffset
1320 = qMax(QTimeZone::MaxUtcOffsetSecs, -QTimeZone::MinUtcOffsetSecs) / 3600;
1321 static_assert(MaxHourOffset > 9);
1323 QByteArrayView used;
1325 } res = { field,
false };
1327 if ((fieldBit != Hour && res.used.size() < 2) || !allowField(fieldBit) || !value)
1329 Q_ASSERT(*value == 0);
1330 seenFields |= fieldBit;
1331 if (res.used.size() > 2)
1332 res.used = res.used.first(2);
1333 *value = res.used.toInt(&res.ok);
1334 if (fieldBit == Hour && (!res.ok || *value > MaxHourOffset)) {
1336 *value = res.used.toInt(&res.ok);
1342bool PatternAligner::scanExtraFields(QStringView sep,
const QLocaleData *locData,
1343 qsizetype &txtLen,
int &second)
1347 while (txtInd + 1 < txtPat.size() && txt.sliced(txtPos, -txtLen) == sep) {
1349 txtLen = txtPat.at(++txtInd);
1351 Q_ASSERT((seenFields & Hour) && (seenFields & Minute));
1352 if (seenFields & Second)
1354 Q_ASSERT(second == 0);
1355 const Digits asciiParse
1356 = locData->digitSequence(txt.sliced(txtPos, txtLen));
1357 QByteArrayView found = asciiParse.digits;
1359 second = found.toInt(&ok);
1360 if (!ok || second >= 60)
1362 seenFields |= Second;
1364 txtLen = ++txtInd < txtPat.size() ? txtPat.at(txtInd) : 0;
1371qsizetype PatternAligner::scanMatchedFields(
const Digits &fmt,
const Digits &src,
1372 bool allowExtraFields,
1373 int &hour,
int &minute,
int &second,
int &sign)
1376 if (!fmt.digits.startsWith(hourAscii))
1378 if (sign || !src.sign)
1380 sign = fmt.sign == src.sign ? +1 : -1;
1381 }
else if (src.sign) {
1385 QByteArrayView chosen{fmt.digits}, found{src.digits};
1386 while (chosen.size() && found.size()) {
1387 const uint priorFields = seenFields;
1388 auto read = chosen.startsWith(hourAscii) ? readField(found, Hour, &hour)
1389 : chosen.startsWith(minuteAscii) ? readField(found, Minute, &minute)
1390 : chosen.startsWith(secondAscii) ? readField(found, Second, &second)
1391 : readField(found, 0u,
nullptr);
1395 if (read.used.size() < 2) {
1396 const uint newField = (seenFields ^ priorFields);
1399 if (newField == Hour) {
1406 found = found.sliced(read.used.size());
1407 allowExtraFields =
false;
1411 if (chosen.size() < fmt.digits.size() && (priorFields & Hour)) {
1414 seenFields = priorFields;
1415 Q_ASSERT(newField == Minute || newField == Second);
1416 if (newField == Minute)
1422 allowExtraFields =
false;
1428 Q_ASSERT(chosen.size() >= 2);
1429 chosen = chosen.sliced(2);
1430 found = found.sliced(read.used.size());
1432 if (chosen.size()) {
1433 Q_ASSERT(found.isEmpty());
1435 return (seenFields & Hour) ? 0 : -1;
1438 if (found.size() && (seenFields & Hour)) {
1441 if (allowExtraFields) {
1442 if (!Q_LIKELY(seenFields & Minute)) {
1443 const uint priorFields = seenFields;
1444 auto read = readField(found, Minute, &minute);
1445 if (!read.ok || read.used.size() < 2) {
1447 seenFields = priorFields;
1448 return found.size();
1450 found = found.sliced(read.used.size());
1452 if (!(seenFields & Second)) {
1453 const uint priorFields = seenFields;
1454 auto read = readField(found, Second, &second);
1455 if (!read.ok || read.used.size() < 2) {
1457 seenFields = priorFields;
1458 return found.size();
1460 found = found.sliced(read.used.size());
1464 return found.size();
1467 return found.size() ? -1 : 0;
1470auto PatternAligner::match(QStringView str,
const QList<qsizetype> &strPat,
1471 const QLocaleData *locData,
char signChar)
1473 Q_ASSERT(!str.isEmpty() && !strPat.isEmpty());
1478 constexpr auto AllowSign = Digits::Option::AllowSign;
1481 qsizetype length = 0;
1482 operator
bool()
const {
return length > 0; }
1484 if ((strPat.at(0) < 0) != (txtPat.at(0) < 0))
1488 int hour = 0, minute = 0, second = 0;
1489 int sign = !signChar;
1493 qsizetype skip = 0, strPos = 0, txtSkipped = 0;
1495 for (qsizetype len : strPat) {
1499 if (!allowSkipField(locData->digitSequence(str.sliced(strPos, len))))
1508 qsizetype txtLen = txtInd < txtPat.size() ? txtPat.at(txtInd) : 0;
1509 const bool maybeSep = txtInd > 0 && txtInd + 1 < strPat.size();
1512 if (!sep.isEmpty() && str.sliced(strPos, -len) != sep) {
1513 if (!scanExtraFields(sep, locData, txtLen, second))
1516 if (maybeSep && sep.isEmpty())
1517 sep = str.sliced(strPos, -len);
1519 if (!textMatch(str, strPos, -len, -txtLen)) {
1522 if (locData && !sep.isEmpty() && str.sliced(strPos, -len) == sep) {
1529 if (txtInd + 1 < strPat.size() - txtSkipped || len <= txtLen
1530 || !textMatch(str, strPos, -len, -len)) {
1544 if (txtPos >= txt.size()) {
1545 Q_ASSERT(txtInd >= txtPat.size());
1547 if (!sep.isEmpty() && txtPat.back() == sep.size() && txt.endsWith(sep)
1548 && strPat.back() == -sep.size() && str.endsWith(sep)) {
1553 txtInd = txtPat.size() - 1;
1554 txtPos -= sep.size();
1564 QStringView field = str.sliced(strPos, len);
1565 QStringView toParse = txt.sliced(txtPos, txtPat.at(txtInd));
1568 const Digits asciiField = locData->digitSequence(field, AllowSign);
1569 if (asciiField.endIndex() != field.size())
1571 const Digits asciiParse = locData->digitSequence(toParse, AllowSign);
1572 if (asciiParse.endIndex() != toParse.size())
1574 const uint priorFields = seenFields;
1576 const qsizetype spare
1577 = scanMatchedFields(asciiField, asciiParse,
1578 !txtSkipped && txtInd + 2 >= strPat.size() && sep.isEmpty(),
1579 hour, minute, second, sign);
1588 if (spare == 1 && (seenFields & Hour)) {
1590 }
else if (strPat.back() < 0) {
1591 if (sep.isEmpty() || !(priorFields & Hour)
1592 || strPat.back() != -sep.size() || !str.endsWith(sep)) {
1595 int offset = hour * 60;
1596 if (priorFields & Minute)
1599 if (priorFields & Second)
1601 return R{ sign * offset, txtPos };
1605 txtPos += asciiParse.digitStart
1606 + (asciiParse.digits.size() - spare) * asciiParse.digitWidth;
1610 strPos += asciiField.endIndex();
1611 txtPos += asciiParse.endIndex();
1616 return R{ sign * (second + 60 * (minute + 60 * hour)), txtPos };
1619QTimeZonePrivate::NamePrefixMatch
1620findOffsetPrefixImpl(QStringView text,
const QLocale &locale,
1621 QtTemporalPattern::TemporalFieldFlags flags)
1623 QTimeZonePrivate::NamePrefixMatch best;
1633 const QUtcTimeZonePrivate greenwich(0);
1635 const QUtcTimeZonePrivate positive(+PatternAligner::OffsetMagnitude);
1636 const QUtcTimeZonePrivate negative(-PatternAligner::OffsetMagnitude);
1638 constexpr QTimeZone::NameType formats[] = {
1639 QTimeZone::OffsetName, QTimeZone::LongName, QTimeZone::ShortName
1641 constexpr QTimeZone::TimeType seasons[] = {
1642 QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime
1645 const auto acceptFormat = [flags](QTimeZone::NameType format) {
1646 using namespace QtTemporalPattern;
1648 if (!flags.testAnyFlags(FieldGroup::WidthMask | FieldGroup::FormMask))
1650 using Flag = TemporalFieldFlag;
1651 constexpr TemporalFieldFlags Textual = Flag::Verbal | Flag::Standalone;
1652 constexpr TemporalFieldFlags Long = Flag::Abbreviated | Flag::Short | Flag::Wide;
1654 case QTimeZone::OffsetName:
1655 return matchesFlagWithin(flags, Flag::Numeric, FieldGroup::FormMask);
1656 case QTimeZone::DefaultName:
1657 case QTimeZone::LongName:
1658 return matchesFlagsWithin(flags, Textual, FieldGroup::FormMask)
1659 && matchesFlagsWithin(flags, Long, FieldGroup::WidthMask);
1660 case QTimeZone::ShortName:
1661 return matchesFlagsWithin(flags, Textual, FieldGroup::FormMask)
1662 && matchesFlagWithin(flags, Flag::Narrow, FieldGroup::WidthMask);
1664 Q_UNREACHABLE_RETURN(
false);
1666 const auto acceptSeason = [flags](QTimeZone::TimeType season) {
1667 using namespace QtTemporalPattern;
1669 if (!flags.testAnyFlags(FieldGroup::SeasonMask))
1671 using Flag = TemporalFieldFlag;
1673 case QTimeZone::GenericTime:
1674 return flags.testFlag(Flag::GenericTime);
1675 case QTimeZone::StandardTime:
1676 return flags.testFlag(Flag::StandardTime);
1677 case QTimeZone::DaylightTime:
1678 return flags.testFlag(Flag::DaylightSavingTime);
1680 Q_UNREACHABLE_RETURN(
false);
1684
1685
1686
1687
1688
1689 QLocale digitLocale = locale;
1690 for (
int i = 0; i < 2; ++i) {
1695 const NumericPattern textPattern(text, digitLocale);
1696 Q_ASSERT(!textPattern.pattern.isEmpty());
1697 PatternAligner aligner(text, textPattern.pattern, flags);
1701 const auto consider = [&best, &aligner, txtSign = textPattern.sign, digitLocale]
1702 (QStringView candidate,
char sign, QTimeZone::TimeType season) {
1703 const auto idForOffset = [sign](
int offsetSeconds) ->
QByteArray {
1707 offsetSeconds = -offsetSeconds;
1708 return QTimeZonePrivate::isoOffsetFormat(offsetSeconds,
1709 QTimeZone::OffsetName).toLatin1();
1711 const auto localeDataFor = [loc = digitLocale] (
const NumericPattern &pat) {
1712 if (pat.hasDigits) {
1713 if (pat.digitsAreLocale)
1714 return QLocalePrivate::get(loc)->m_data;
1715 return QLocaleData::c();
1717 return static_cast<
const QLocaleData *>(
nullptr);
1719 if (
const NumericPattern pat(candidate, digitLocale);
1721 (pat.sign & sign) != sign || (txtSign & sign) == sign) {
1722 const auto parsed = aligner.match(
1723 candidate, pat.pattern, localeDataFor(pat), pat.sign);
1724 if (parsed && parsed.length > best.nameLength)
1725 best = { idForOffset(parsed.offset), parsed.length, season };
1726 return pat.digitsAreLocale;
1731 bool nativeSeen =
false;
1732 for (
auto season : seasons) {
1733 if (!acceptSeason(season))
1735 for (
auto format : formats) {
1736 if (!acceptFormat(format))
1738 if (
const QString pos = positive.displayName(season, format, locale);
1739 pos.size() > best.nameLength) {
1740 if (consider(pos,
'+', season))
1743 if (
const QString neg = negative.displayName(season, format, locale);
1744 neg.size() > best.nameLength) {
1745 if (consider(neg,
'-', season))
1748 if (
const QString nul = greenwich.displayName(season, format, locale);
1749 nul.size() > best.nameLength) {
1750 if (text.startsWith(nul))
1751 best = {
"UTC"_ba, nul.size() };
1753 if (best.nameLength == text.size())
1760 if (i == 0 && (digitLocale.zeroDigit() == u'0' || !nativeSeen))
1762 digitLocale = QLocale::c();
1769QTimeZonePrivate::NamePrefixMatch
1770QTimeZonePrivate::findOffsetPrefix(QStringView text,
const QLocale &locale,
1771 QtTemporalPattern::TemporalFieldFlags flags)
1773 NamePrefixMatch best;
1774 if (
auto match = findOffsetPrefixImpl(text, locale, flags))
1775 best = std::move(match);
1776 if (
auto match = findOffsetPrefixImpl(text, QLocale::c(), flags);
1777 match.nameLength > best.nameLength) {
1778 best = std::move(match);
1785QTimeZonePrivate::NamePrefixMatch
1786QTimeZonePrivate::findLongUtcPrefix(QStringView text)
1788 if (text.startsWith(u"UTC")) {
1789 if (text.size() > 4 && (text[3] == u'+' || text[3] == u'-')) {
1791 const auto digitAt = [text](qsizetype index) {
1792 using QtMiscUtils::isAsciiDigit;
1793 return index < text.size() && isAsciiDigit(text[index].unicode());
1795 qsizetype length = 3;
1799 Q_ASSERT(length < text.size());
1800 if (!digitAt(length + 1) || (groups && !digitAt(length + 2)))
1802 length += digitAt(length + 2) ? 3 : 2;
1803 }
while (++groups < 3 && length < text.size() && text[length] == u':');
1805 return { text.first(length).toLatin1(), length, QTimeZone::GenericTime };
1807 return { utcQByteArray(), 3, QTimeZone::GenericTime };
1813QByteArrayView QTimeZonePrivate::aliasToIana(QByteArrayView alias)
1815 const auto data = std::lower_bound(std::begin(aliasMappingTable), std::end(aliasMappingTable),
1816 alias, earlierAliasId);
1817 if (data != std::end(aliasMappingTable) && data->aliasId() == alias)
1818 return data->ianaId();
1825QByteArrayView QTimeZonePrivate::ianaIdToWindowsId(QByteArrayView id)
1827 const auto idUtf8 = QUtf8StringView(id);
1829 for (
const ZoneData &data : zoneDataTable) {
1830 for (
auto l1 : data.ids()) {
1832 return toWindowsIdLiteral(data.windowsIdKey);
1840QByteArrayView QTimeZonePrivate::windowsIdToDefaultIanaId(QByteArrayView windowsId)
1842 const auto data = std::lower_bound(std::begin(windowsDataTable), std::end(windowsDataTable),
1843 windowsId, earlierWindowsId);
1844 if (data != std::end(windowsDataTable) && data->windowsId() == windowsId) {
1845 QByteArrayView id = data->ianaId();
1846 Q_ASSERT(id.indexOf(
' ') == -1);
1852QByteArrayView QTimeZonePrivate::windowsIdToDefaultIanaId(QByteArrayView windowsId,
1853 QLocale::Territory territory)
1856 if (territory == QLocale::World) {
1858 return windowsIdToDefaultIanaId(windowsId);
1861 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1862 const qint16 land =
static_cast<quint16>(territory);
1863 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1864 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1867 if (data->territory == land)
1868 return *data->ids().begin();
1874QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(QByteArrayView windowsId)
1876 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1877 QList<QByteArray> list;
1879 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1880 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1882 for (
auto l1 : data->ids())
1883 list << QByteArray(l1.data(), l1.size());
1890 std::sort(list.begin(), list.end());
1894QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(QByteArrayView windowsId,
1895 QLocale::Territory territory)
1898 QList<QByteArray> list;
1899 if (territory == QLocale::World) {
1901 list << windowsIdToDefaultIanaId(windowsId).toByteArray();
1903 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
1904 const qint16 land =
static_cast<quint16>(territory);
1905 for (
auto data = zoneStartForWindowsId(windowsIdKey);
1906 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
1909 if (data->territory == land) {
1910 for (
auto l1 : data->ids())
1911 list << QByteArray(l1.data(), l1.size());
1923 while ((cut = ianaIds.indexOf(
' ')) >= 0) {
1924 if (id == ianaIds.first(cut))
1926 ianaIds = ianaIds.sliced(cut + 1);
1928 return id == ianaIds;
1932
1933
1934
1935
1936
1937
1940QUtcTimeZonePrivate::QUtcTimeZonePrivate()
1942 const QString name = utcQString();
1943 init(utcQByteArray(), 0, name, name, QLocale::AnyTerritory, name);
1947QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QByteArray &id)
1950 for (
const UtcData &data : utcDataTable) {
1951 if (isEntryInIanaList(id, data.id())) {
1952 QString name = QString::fromUtf8(id);
1953 init(id, data.offsetFromUtc, name, name, QLocale::AnyTerritory, name);
1961qint64 QUtcTimeZonePrivate::offsetFromUtcString(QByteArrayView id)
1966 if (!id.startsWith(
"UTC") || id.size() < 5)
1967 return invalidSeconds();
1968 const char signChar = id.at(3);
1969 if (signChar !=
'-' && signChar !=
'+')
1970 return invalidSeconds();
1971 const int sign = signChar ==
'-' ? -1 : 1;
1975 for (
auto offset : QLatin1StringView(id.mid(4)).tokenize(
':'_L1)) {
1976 if (offset.size() > 2 || (prior && offset.size() < 2))
1977 return invalidSeconds();
1979 unsigned short field = offset.toUShort(&ok);
1981 if (!ok || field >= (prior ? 60 : 24))
1982 return invalidSeconds();
1983 seconds = seconds * 60 + field;
1985 return invalidSeconds();
1989 return invalidSeconds();
1994 return seconds * sign;
1998QUtcTimeZonePrivate::QUtcTimeZonePrivate(qint32 offsetSeconds)
2003 const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
2004 offsetSeconds, atLowerUtcOffset);
2005 if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
2006 QByteArrayView ianaId = data->id();
2007 qsizetype cut = ianaId.indexOf(
' ');
2008 QByteArrayView cutId = (cut < 0 ? ianaId : ianaId.first(cut));
2009 if (cutId == utcQByteArray()) {
2011 id = utcQByteArray();
2012 name = utcQString();
2015 id = cutId.toByteArray();
2016 name = QString::fromUtf8(id);
2018 Q_ASSERT(!name.isEmpty());
2020 name = isoOffsetFormat(offsetSeconds, QTimeZone::OffsetName);
2023 init(id, offsetSeconds, name, name, QLocale::AnyTerritory, name);
2026QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QByteArray &zoneId,
int offsetSeconds,
2027 const QString &name,
const QString &abbreviation,
2028 QLocale::Territory territory,
const QString &comment)
2030 init(zoneId, offsetSeconds, name, abbreviation, territory, comment);
2033QUtcTimeZonePrivate::QUtcTimeZonePrivate(
const QUtcTimeZonePrivate &other)
2034 : QTimeZonePrivate(other), m_name(other.m_name),
2035 m_abbreviation(other.m_abbreviation),
2036 m_comment(other.m_comment),
2037 m_territory(other.m_territory),
2038 m_offsetFromUtc(other.m_offsetFromUtc)
2042QUtcTimeZonePrivate::~QUtcTimeZonePrivate()
2046QUtcTimeZonePrivate *QUtcTimeZonePrivate::clone()
const
2048 return new QUtcTimeZonePrivate(*
this);
2051QTimeZonePrivate::Data QUtcTimeZonePrivate::data(qint64 forMSecsSinceEpoch)
const
2054 d.abbreviation = m_abbreviation;
2055 d.atMSecsSinceEpoch = forMSecsSinceEpoch;
2056 d.standardTimeOffset = d.offsetFromUtc = m_offsetFromUtc;
2057 d.daylightTimeOffset = 0;
2062QTimeZonePrivate::Data QUtcTimeZonePrivate::data(QTimeZone::TimeType timeType)
const
2065 return data(QDateTime::currentMSecsSinceEpoch());
2068bool QUtcTimeZonePrivate::isDataLocale(
const QLocale &locale)
const
2071 return isAnglicLocale(locale);
2074void QUtcTimeZonePrivate::init(
const QByteArray &zoneId,
int offsetSeconds,
const QString &name,
2075 const QString &abbreviation, QLocale::Territory territory,
2076 const QString &comment)
2079 m_offsetFromUtc = offsetSeconds;
2081 m_abbreviation = abbreviation;
2082 m_territory = territory;
2083 m_comment = comment;
2086QLocale::Territory QUtcTimeZonePrivate::territory()
const
2091QString QUtcTimeZonePrivate::comment()
const
2097QString QUtcTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch,
2098 QTimeZone::NameType nameType,
2099 const QLocale &locale)
const
2101 Q_UNUSED(atMSecsSinceEpoch);
2102 return displayName(QTimeZone::StandardTime, nameType, locale);
2105QString QUtcTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
2106 QTimeZone::NameType nameType,
2107 const QLocale &locale)
const
2109#if QT_CONFIG(timezone_locale)
2116 m_offsetFromUtc != 0 ? QString() :
2118 QTimeZonePrivate::displayName(timeType, nameType, locale);
2125 const auto matchesFallback = [](
int offset, QStringView name) {
2127 int seconds = offset % 60;
2128 int rounded = offset
2129 + (seconds > 30 || (seconds == 30 && (offset / 60) % 2)
2131 : (seconds < -30 || (seconds == -30 && (offset / 60) % 2)
2134 const QString avoid = isoOffsetFormat(rounded);
2137 Q_ASSERT(avoid.startsWith(
"UTC"_L1));
2138 Q_ASSERT(avoid.size() == 9);
2141 if (!(name.startsWith(
"GMT"_L1) || name.startsWith(
"UTC"_L1)) || name.size() < 5)
2144 QStringView tail{avoid};
2145 tail = tail.sliced(3);
2146 if (name.sliced(3) == tail)
2148 while (tail.endsWith(
":00"_L1))
2149 tail = tail.chopped(3);
2150 while (name.endsWith(
":00"_L1))
2151 name = name.chopped(3);
2155 const QChar sign = name[3] == u'\u2212' ? u'-' : name[3];
2157 return sign == tail[0] && tail.sliced(tail[1] == u'0' ? 2 : 1) == name.sliced(4);
2159 if (!name.isEmpty() && (m_name.isEmpty() || !matchesFallback(m_offsetFromUtc, name)))
2165 if (nameType == QTimeZone::ShortName)
2166 return m_abbreviation;
2167 if (nameType == QTimeZone::OffsetName)
2168 return isoOffsetFormat(m_offsetFromUtc);
2172QString QUtcTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch)
const
2174 Q_UNUSED(atMSecsSinceEpoch);
2175 return m_abbreviation;
2178qint32 QUtcTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch)
const
2180 Q_UNUSED(atMSecsSinceEpoch);
2181 return m_offsetFromUtc;
2184qint32 QUtcTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch)
const
2186 Q_UNUSED(atMSecsSinceEpoch);
2190QByteArray QUtcTimeZonePrivate::systemTimeZoneId()
const
2193 const emscripten::val date = emscripten::val::global(
"Date").new_();
2194 if (date.isUndefined())
2195 return utcQByteArray();
2198 const int offsetSeconds = -date.call<
int>(
"getTimezoneOffset") * 60;
2199 if (offsetSeconds == 0)
2200 return utcQByteArray();
2201 return isoOffsetFormat(offsetSeconds).toUtf8();
2203 return utcQByteArray();
2207bool QUtcTimeZonePrivate::isTimeZoneIdAvailable(QByteArrayView ianaId)
const
2210 for (
const UtcData &data : utcDataTable) {
2211 if (isEntryInIanaList(ianaId, data.id()))
2220QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds()
const
2223 QList<QByteArray> result;
2224 result.reserve(std::size(utcDataTable));
2225 for (
const UtcData &data : utcDataTable) {
2226 QByteArrayView id = data.id();
2228 while ((cut = id.indexOf(
' ')) >= 0) {
2229 result << id.first(cut).toByteArray();
2230 id = id.sliced(cut + 1);
2232 result << id.toByteArray();
2235 std::sort(result.begin(), result.end());
2240QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(QLocale::Territory country)
const
2243 if (country == QLocale::AnyTerritory)
2244 return availableTimeZoneIds();
2245 return QList<QByteArray>();
2248QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(qint32 offsetSeconds)
const
2252 QList<QByteArray> result;
2253 const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
2254 offsetSeconds, atLowerUtcOffset);
2255 if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
2256 QByteArrayView id = data->id();
2258 while ((cut = id.indexOf(
' ')) >= 0) {
2259 result << id.first(cut).toByteArray();
2260 id = id.sliced(cut + 1);
2262 result << id.toByteArray();
2267 QByteArray isoName = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName).toUtf8();
2268 if (offsetFromUtcString(isoName) == qint64(offsetSeconds) && !result.contains(isoName))
2271 std::sort(result.begin(), result.end());
2276#ifndef QT_NO_DATASTREAM
2277void QUtcTimeZonePrivate::serialize(QDataStream &ds)
const
2279 ds <<
QStringLiteral(
"OffsetFromUtc") << QString::fromUtf8(m_id) << m_offsetFromUtc << m_name
2280 << 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