5#include <private/qtimezonelocale_p.h>
6#include <private/qtimezoneprivate_p.h>
9# include <QtCore/qspan.h>
10# include <private/qdatetime_p.h>
11# include <private/qtools_p.h>
13# include "qtimezonelocale_data_p.h"
14# include "qtimezoneprivate_data_p.h"
15# ifdef QT_CLDR_ZONE_DEBUG
16# include "../text/qlocale_data_p.h"
18static_assert(std::size(locale_data) == std::size(QtTimeZoneLocale::localeZoneData));
21 for (std::size_t i = 0; i < std::size(locale_data); ++i) {
22 const auto &loc = locale_data[i];
23 const auto &zone = QtTimeZoneLocale::localeZoneData[i];
24 if (loc.m_language_id != zone.m_language_id
25 || loc.m_script_id != zone.m_script_id
26 || loc.m_territory_id != zone.m_territory_id) {
38using namespace Qt::StringLiterals;
44UCalendarDisplayNameType ucalDisplayNameType(QTimeZone::TimeType timeType,
45 QTimeZone::NameType nameType)
50 case QTimeZone::ShortName:
51 return timeType == QTimeZone::DaylightTime ? UCAL_SHORT_DST : UCAL_SHORT_STANDARD;
52 case QTimeZone::DefaultName:
53 case QTimeZone::LongName:
54 return timeType == QTimeZone::DaylightTime ? UCAL_DST : UCAL_STANDARD;
55 case QTimeZone::OffsetName:
58 Q_UNREACHABLE_RETURN(UCAL_STANDARD);
63namespace QtTimeZoneLocale {
67QString ucalTimeZoneDisplayName(UCalendar *ucal,
68 QTimeZone::TimeType timeType,
69 QTimeZone::NameType nameType,
70 const QByteArray &localeCode)
72 constexpr int32_t BigNameLength = 50;
73 int32_t size = BigNameLength;
74 QString result(size, Qt::Uninitialized);
75 auto dst = [&result]() {
return reinterpret_cast<UChar *>(result.data()); };
76 UErrorCode status = U_ZERO_ERROR;
77 const UCalendarDisplayNameType utype = ucalDisplayNameType(timeType, nameType);
80 size = ucal_getTimeZoneDisplayName(ucal, utype, localeCode.constData(),
81 dst(), size, &status);
84 if (size > BigNameLength || status == U_BUFFER_OVERFLOW_ERROR) {
86 status = U_ZERO_ERROR;
87 size = ucal_getTimeZoneDisplayName(ucal, utype, localeCode.constData(),
88 dst(), size, &status);
91 if (!U_SUCCESS(status))
99bool ucalKnownTimeZoneId(
const QString &ianaStr)
101 const UChar *
const name =
reinterpret_cast<
const UChar *>(ianaStr.constData());
104 constexpr size_t size = 64;
109 UErrorCode status = U_ZERO_ERROR;
112 ucal_getCanonicalTimeZoneID(name, ianaStr.size(), buffer, size, &isSys, &status);
120QString QTimeZonePrivate::localeName(qint64 atMSecsSinceEpoch,
int offsetFromUtc,
121 QTimeZone::TimeType timeType,
122 QTimeZone::NameType nameType,
123 const QLocale &locale)
const
125 Q_UNUSED(atMSecsSinceEpoch);
129 if (nameType == QTimeZone::OffsetName)
130 return isoOffsetFormat(offsetFromUtc);
132 const QString id = QString::fromUtf8(m_id);
135 if (!QtTimeZoneLocale::ucalKnownTimeZoneId(id))
138 const QByteArray loc = locale.name().toUtf8();
139 UErrorCode status = U_ZERO_ERROR;
141 UCalendar *ucal = ucal_open(
reinterpret_cast<
const UChar *>(id.data()), id.size(),
142 loc.constData(), UCAL_DEFAULT, &status);
143 if (ucal && U_SUCCESS(status)) {
144 auto tidier = qScopeGuard([ucal]() { ucal_close(ucal); });
145 return QtTimeZoneLocale::ucalTimeZoneDisplayName(ucal, timeType, nameType, loc);
150namespace QtTimeZoneLocale {
152using namespace QtTimeZoneCldr;
153constexpr QByteArrayView LocaleZoneExemplar::ianaId()
const {
return ianaIdData + ianaIdIndex; }
154constexpr QByteArrayView LocaleZoneNames::ianaId()
const {
return ianaIdData + ianaIdIndex; }
158using namespace QtTimeZoneLocale;
159using namespace QtTimeZoneCldr;
162template <
typename Row,
typename Sought,
typename Condition>
163const Row *findTableEntryFor(
const QSpan<Row> data, Sought value, Condition test)
168 auto begin = data.begin(), end = data.end();
169 Q_ASSERT(begin == end || end->localeIndex > begin->localeIndex);
170 Q_ASSERT(begin == end || end[-1].localeIndex == begin->localeIndex);
171 auto row = std::lower_bound(begin, end, value, test);
172 return row == end ?
nullptr : row;
175QString exemplarCityFor(
const LocaleZoneData &locale,
const LocaleZoneData &next,
178 auto xct = findTableEntryFor(
179 QSpan(localeZoneExemplarTable).first(next.m_exemplarTableStart
180 ).sliced(locale.m_exemplarTableStart),
181 iana, [](
auto &row, QByteArrayView key) {
return row.ianaId() < key; });
182 if (xct && xct->ianaId() == iana)
183 return xct->exemplarCity().getData(exemplarCityTable);
188quint32 clipEpochMinute(qint64 epochMinute)
192 constexpr quint32 epoch = 0;
201 constexpr quint32 ragnarok = 1 ^ ~epoch;
202 return epochMinute + 1 >= ragnarok ? ragnarok : quint32(epochMinute);
205constexpr bool intervalEndsBefore(
const ZoneMetaHistory &record, quint32 dt)
noexcept
208 return record.end <= dt;
212
213
214
215quint16 metaZoneAt(QByteArrayView zoneId, qint64 atMSecsSinceEpoch)
217 using namespace QtPrivate::DateTimeConstants;
218 auto it = std::lower_bound(std::begin(zoneHistoryTable), std::end(zoneHistoryTable), zoneId,
219 [](
const ZoneMetaHistory &record, QByteArrayView id) {
220 return record.ianaId().compare(id, Qt::CaseInsensitive) < 0;
222 if (it == std::end(zoneHistoryTable) || it->ianaId().compare(zoneId, Qt::CaseInsensitive) > 0)
225 std::upper_bound(it, std::end(zoneHistoryTable), zoneId,
226 [](QByteArrayView id,
const ZoneMetaHistory &record) {
227 return id.compare(record.ianaId(), Qt::CaseInsensitive) < 0;
229 const quint32 dt = clipEpochMinute(atMSecsSinceEpoch / MSECS_PER_MIN);
230 it = std::lower_bound(it, stop, dt, intervalEndsBefore);
231 return it != stop && it->begin <= dt ? it->metaZoneKey : 0;
235bool zoneEverInMeta(QByteArrayView zoneId, quint16 metaKey)
237 for (
auto it = std::lower_bound(std::begin(zoneHistoryTable), std::end(zoneHistoryTable),
239 [](
const ZoneMetaHistory &record, QByteArrayView id) {
240 return record.ianaId().compare(id, Qt::CaseInsensitive) < 0;
242 it != std::end(zoneHistoryTable) && it->ianaId().compare(zoneId, Qt::CaseInsensitive) == 0;
244 if (it->metaZoneKey == metaKey)
250constexpr bool dataBeforeMeta(
const MetaZoneData &row, quint16 metaKey)
noexcept
252 return row.metaZoneKey < metaKey;
255constexpr bool metaDataBeforeTerritory(
const MetaZoneData &row, qint16 territory)
noexcept
257 return row.territory < territory;
260const MetaZoneData *metaZoneStart(quint16 metaKey)
262 const MetaZoneData *
const from =
263 std::lower_bound(std::begin(metaZoneTable), std::end(metaZoneTable),
264 metaKey, dataBeforeMeta);
265 if (from == std::end(metaZoneTable) || from->metaZoneKey != metaKey) {
266 qWarning(
"No metazone data found for metazone key %d", metaKey);
272const MetaZoneData *metaZoneDataFor(
const MetaZoneData *from, QLocale::Territory territory)
274 const quint16 metaKey = from->metaZoneKey;
275 const MetaZoneData *
const end =
276 std::lower_bound(from, std::end(metaZoneTable), metaKey + 1, dataBeforeMeta);
277 Q_ASSERT(end != from && end[-1].metaZoneKey == metaKey);
278 QLocale::Territory land = territory;
280 const MetaZoneData *row =
281 std::lower_bound(from, end, qint16(land), metaDataBeforeTerritory);
282 if (row != end && QLocale::Territory(row->territory) == land) {
283 Q_ASSERT(row->metaZoneKey == metaKey);
287 }
while (std::exchange(land, QLocale::World) != QLocale::World);
289 qWarning(
"Metazone %s lacks World data for %ls",
290 from->metaZoneId().constData(),
291 qUtf16Printable(QLocale::territoryToString(territory)));
295QString addPadded(qsizetype width,
const QString &zero,
const QString &number, QString &&onto)
298 width -= number.size() / zero.size();
303 return std::move(onto) + number;
306QString formatOffset(QStringView format,
int offsetMinutes,
const QLocale &locale,
307 QLocale::FormatType form)
309 Q_ASSERT(offsetMinutes >= 0);
310 const QString hour = locale.toString(offsetMinutes / 60);
311 const QString mins = locale.toString(offsetMinutes % 60);
314 const QString zero = locale.zeroDigit();
315 QStringView tail = format;
317 while (!tail.isEmpty()) {
318 if (tail.startsWith(u'\'')) {
319 qsizetype end = tail.indexOf(u'\'', 1);
321 qWarning(
"Unbalanced quote in offset format string: %s",
322 format.toUtf8().constData());
323 return result + tail;
324 }
else if (end == 1) {
327 tail = tail.sliced(2);
330 while (end + 1 < tail.size() && tail[end + 1] == u'\'') {
333 result += tail.sliced(1, end);
334 tail = tail.sliced(end + 1);
335 end = tail.indexOf(u'\'', 1);
337 qWarning(
"Unbalanced quoted quote in offset format string: %s",
338 format.toUtf8().constData());
339 return result + tail;
344 result += tail.sliced(1, end - 1);
345 tail = tail.sliced(end + 1);
347 }
else if (tail.startsWith(u'H')) {
349 while (width < tail.size() && tail[width] == u'H')
351 tail = tail.sliced(width);
352 if (form != QLocale::NarrowFormat)
353 result = addPadded(width, zero, hour, std::move(result));
356 }
else if (tail.startsWith(u'm')) {
358 while (width < tail.size() && tail[width] == u'm')
362 tail = tail.sliced(width);
363 if (form != QLocale::NarrowFormat)
364 result = addPadded(width, zero, mins, std::move(result));
365 else if (offsetMinutes % 60)
367 else if (result.endsWith(u':') || result.endsWith(u'.'))
370 }
else if (tail[0].isHighSurrogate() && tail.size() > 1
371 && tail[1].isLowSurrogate()) {
372 result += tail.first(2);
373 tail = tail.sliced(2);
375 result += tail.front();
376 tail = tail.sliced(1);
382struct OffsetFormatMatch
386 operator
bool() {
return size > 0; }
389OffsetFormatMatch matchOffsetText(QStringView text, QStringView format,
const QLocale &locale,
390 QLocale::FormatType scale)
395 OffsetFormatMatch res;
401 qsizetype cut = format.indexOf(u'H');
402 if (cut < 0 || !text.startsWith(format.first(cut)) || !format.endsWith(u"mm"))
404 text = text.sliced(cut);
405 QStringView sep = format.sliced(cut).chopped(2);
407 while (hlen < sep.size() && sep[hlen] == u'H')
409 sep = sep.sliced(hlen);
412 while (digits < text.size() && digits < 4 && text[digits].isDigit())
420 if (scale == QLocale::NarrowFormat || (hlen < 2 && text[0] != u'0'))
422 else if (digits < hlen + 2)
424 minStr = text.sliced(hlen).first(2);
425 }
else if (scale == QLocale::NarrowFormat) {
427 }
else if (hlen != digits) {
431 const qsizetype sepAt = text.indexOf(sep);
434 if (scale == QLocale::NarrowFormat || (hlen < 2 && text[0] != u'0'))
436 else if (digits != hlen)
438 if (sepAt >= 0 && text.size() >= sepAt + sep.size() + 2)
439 minStr = text.sliced(sepAt + sep.size()).first(2);
440 else if (scale != QLocale::NarrowFormat)
443 minStr = text.sliced(sepAt + sep.size());
449 uint minute = minStr.isEmpty() ? 0 : locale.toUInt(minStr, &ok);
450 if (!ok && scale == QLocale::NarrowFormat) {
455 if (ok && minute < 60) {
456 uint hour = locale.toUInt(text.first(hlen), &ok);
458 res.offset = (hour * 60 + minute) * 60;
459 res.size = cut + hlen;
460 if (!minStr.isEmpty())
461 res.size += sep.size() + minStr.size();
467OffsetFormatMatch matchOffsetFormat(QStringView text,
const QLocale &locale, qsizetype locInd,
468 QLocale::FormatType scale)
470 const LocaleZoneData &locData = localeZoneData[locInd];
471 const QStringView posHourForm = locData.posHourFormat().viewData(hourFormatTable);
472 const QStringView negHourForm = locData.negHourFormat().viewData(hourFormatTable);
474 const bool mapNeg = text.contains(u'-')
475 && (negHourForm.contains(u'\u2212') || negHourForm.contains(locale.negativeSign()));
477 if (scale == QLocale::ShortFormat) {
478 if (
auto match = matchOffsetText(text, posHourForm, locale, scale))
480 if (
auto match = matchOffsetText(text, negHourForm, locale, scale)) {
481 return { match.size, -match.offset };
483 const QString mapped = negHourForm.toString()
484 .replace(u'\u2212', u'-').replace(locale.negativeSign(),
"-"_L1);
485 if (
auto match = matchOffsetText(text, mapped, locale, scale))
486 return { match.size, -match.offset };
489 const QStringView offsetFormat = locData.offsetGmtFormat().viewData(gmtFormatTable);
490 qsizetype cut = offsetFormat.indexOf(u"%0");
492 const QStringView gmtPrefix = offsetFormat.first(cut);
493 const QStringView gmtSuffix = offsetFormat.sliced(cut + 2);
494 const qsizetype gmtSize = cut + gmtSuffix.size();
497 if ((gmtPrefix.isEmpty() || text.startsWith(gmtPrefix))
498 && (gmtSuffix.isEmpty() || text.sliced(cut).indexOf(gmtSuffix) >= 0)) {
499 if (
auto match = matchOffsetText(text.sliced(cut), posHourForm, locale, scale)) {
500 if (text.sliced(cut + match.size).startsWith(gmtSuffix))
501 return { gmtSize + match.size, match.offset };
503 if (
auto match = matchOffsetText(text.sliced(cut), negHourForm, locale, scale)) {
504 if (text.sliced(cut + match.size).startsWith(gmtSuffix))
505 return { gmtSize + match.size, -match.offset };
507 const QString mapped = negHourForm.toString()
508 .replace(u'\u2212', u'-').replace(locale.negativeSign(),
"-"_L1);
509 if (
auto match = matchOffsetText(text.sliced(cut), mapped, locale, scale)) {
510 if (text.sliced(cut + match.size).startsWith(gmtSuffix))
511 return { gmtSize + match.size, -match.offset };
515 if (gmtSize > 0 && text.sliced(cut).startsWith(gmtSuffix))
516 return { gmtSize, 0 };
525namespace QtTimeZoneLocale {
529 QList<QByteArrayView> result;
531 const TerritoryZone *row =
532 std::lower_bound(std::begin(territoryZoneMap), std::end(territoryZoneMap),
534 [](
const TerritoryZone &row, qint16 territory) {
535 return row.territory < territory;
537 if (row != std::end(territoryZoneMap) && QLocale::Territory(row->territory) == territory)
538 result << row->ianaId();
540 for (
const MetaZoneData &row : metaZoneTable) {
541 if (QLocale::Territory(row.territory) == territory)
542 result << row.ianaId();
550QString zoneOffsetFormat(
const QLocale &locale, qsizetype locInd, QLocale::FormatType width,
551 const QDateTime &,
int offsetSeconds)
557 const LocaleZoneData &locData = localeZoneData[locInd];
559 auto hourFormatR = offsetSeconds < 0 ? locData.negHourFormat() : locData.posHourFormat();
560 QStringView hourFormat = hourFormatR.viewData(hourFormatTable);
561 Q_ASSERT(!hourFormat.isEmpty());
563 offsetSeconds = qAbs(offsetSeconds);
566 const int offsetMinutes = (offsetSeconds + 29 + (1 & (offsetSeconds / 60))) / 60;
568 const QString hourOffset = formatOffset(hourFormat, offsetMinutes, locale, width);
569 if (width == QLocale::ShortFormat)
572 QStringView offsetFormat = locData.offsetGmtFormat().viewData(gmtFormatTable);
573 Q_ASSERT(!offsetFormat.isEmpty());
574 return offsetFormat.arg(hourOffset);
579QString QTimeZonePrivate::localeName(qint64 atMSecsSinceEpoch,
int offsetFromUtc,
580 QTimeZone::TimeType timeType,
581 QTimeZone::NameType nameType,
582 const QLocale &locale)
const
584 if (nameType == QTimeZone::OffsetName) {
586 return QtTimeZoneLocale::zoneOffsetFormat(locale, locale.d->m_index, QLocale::LongFormat,
587 QDateTime(), offsetFromUtc);
592 QByteArray ianaAbbrev, ianaTail;
593 const auto scanIana = [&](QByteArrayView iana) {
597 if (!ianaAbbrev.isEmpty() && !ianaTail.isEmpty())
599 qsizetype cut = iana.lastIndexOf(
'/');
600 QByteArrayView tail = cut < 0 ? iana : iana.sliced(cut + 1);
602 if (tail ==
"McMurdo") {
603 if (ianaTail.isEmpty())
604 ianaTail =
"McMurdo"_ba;
606 }
else if (tail ==
"DumontDUrville") {
607 if (ianaTail.isEmpty())
608 ianaTail =
"Dumont d'Urville"_ba;
610 }
else if (tail.isEmpty()) {
614 const auto isMixedCaseAbbrev = [tail](
char ch) {
618 switch (tail.size()) {
619 case 2:
return tail ==
"Gk";
620 case 3:
return tail ==
"CuT";
622 if (tail[0] ==
'C' && tail[1] == ch && tail[3] ==
'T') {
624 case 'h':
return tail[2] ==
'S';
625 case 'u':
return tail[2] ==
'S' || tail[2] ==
'D';
637 bool maybeAbbr = ianaAbbrev.isEmpty(), maybeCityName = ianaTail.isEmpty(), inword =
false;
639 for (
char ch : tail) {
640 if (ch ==
'+' || ch ==
'-') {
641 if (ch ==
'+' || !inword)
642 maybeCityName =
false;
650 }
else if (ch ==
'_') {
653 maybeCityName =
false;
655 }
else if (QChar::isLower(ch)) {
656 maybeAbbr = isMixedCaseAbbrev(ch);
659 }
else if (QChar::isUpper(ch)) {
663 maybeCityName =
false;
665 }
else if (QChar::isDigit(ch)) {
668 maybeCityName =
false;
672 if (!maybeAbbr && !maybeCityName)
675 if (maybeAbbr && maybeCityName)
679 if (tail.endsWith(
"-0") || tail.endsWith(
"+0"))
680 tail = tail.chopped(2);
681 ianaAbbrev = tail.toByteArray();
682 if (sign && iana.startsWith(
"Etc/")) {
684 ianaAbbrev = ianaAbbrev.replace(
'-',
'+');
685 else if (sign ==
'+')
686 ianaAbbrev = ianaAbbrev.replace(
'+',
'-');
690 ianaTail = tail.toByteArray().replace(
'_',
' ');
694 if (QByteArray iana = aliasToIana(m_id); !iana.isEmpty() && iana != m_id)
698#define tableLookup(table, member, sought, test)
699 findTableEntryFor(QSpan(table).first(nextData.member).sliced(locData.member), sought, test)
703 const QList<qsizetype> indices = fallbackLocalesFor(locale.d->m_index);
704 QString exemplarCity;
705 const auto metaIdBefore = [](
auto &row, quint16 key) {
return row.metaIdIndex < key; };
708 for (
const qsizetype locInd : indices) {
709 const LocaleZoneData &locData = localeZoneData[locInd];
711 Q_ASSERT(std::size_t(locInd) < std::size(localeZoneData) - 1);
712 const LocaleZoneData &nextData = localeZoneData[locInd + 1];
714 QByteArrayView iana{m_id};
715 if (quint16 metaKey = metaZoneAt(iana, atMSecsSinceEpoch)) {
716 if (
const MetaZoneData *metaFrom = metaZoneStart(metaKey)) {
717 quint16 metaIdIndex = metaFrom->metaIdIndex;
718 QLocaleData::DataRange range{0, 0};
719 const char16_t *strings =
nullptr;
720 if (nameType == QTimeZone::ShortName) {
721 auto row =
tableLookup(localeMetaZoneShortNameTable, m_metaShortTableStart,
722 metaIdIndex, metaIdBefore);
723 if (row && row->metaIdIndex == metaIdIndex) {
724 range = row->shortName(timeType);
725 strings = shortMetaZoneNameTable;
728 auto row =
tableLookup(localeMetaZoneLongNameTable, m_metaLongTableStart,
729 metaIdIndex, metaIdBefore);
730 if (row && row->metaIdIndex == metaIdIndex) {
731 range = row->longName(timeType);
732 strings = longMetaZoneNameTable;
735 Q_ASSERT(strings || !range.size);
738 return range.getData(strings);
740 if (
const auto *metaRow = metaZoneDataFor(metaFrom, locale.territory()))
741 iana = metaRow->ianaId();
746 if (exemplarCity.isEmpty()) {
747 exemplarCity = exemplarCityFor(locData, nextData, m_id);
748 if (exemplarCity.isEmpty())
749 exemplarCity = exemplarCityFor(locData, nextData, iana);
758 localeZoneNameTable, m_zoneTableStart,
759 iana, [](
auto &row, QByteArrayView key) {
return row.ianaId() < key; });
760 if (row && row->ianaId() == iana) {
761 QLocaleData::DataRange range = row->name(nameType, timeType);
763 auto table = nameType == QTimeZone::ShortName
766 return range.getData(table);
769 }
while (std::exchange(iana, QByteArrayView{m_id}) != m_id);
774 if (exemplarCity.isEmpty() && !ianaTail.isEmpty())
775 exemplarCity = QString::fromLatin1(ianaTail);
778 case QTimeZone::DefaultName:
779 case QTimeZone::LongName:
780 for (
const qsizetype locInd : indices) {
781 const LocaleZoneData &locData = localeZoneData[locInd];
782 QStringView regionFormat
783 = locData.regionFormatRange(timeType).viewData(regionFormatTable);
784 if (!regionFormat.isEmpty()) {
785 QString where = exemplarCity;
787 if (!where.isEmpty())
788 return regionFormat.arg(where);
792 for (
const qsizetype locInd : indices) {
793 const LocaleZoneData &locData = localeZoneData[locInd];
794 QStringView fallbackFormat = locData.fallbackFormat().viewData(fallbackFormatTable);
802 case QTimeZone::ShortName:
804 if (!ianaAbbrev.isEmpty())
805 return QString::fromLatin1(ianaAbbrev);
808 case QTimeZone::OffsetName:
809 Q_UNREACHABLE_RETURN(QString());
817 return QtTimeZoneLocale::zoneOffsetFormat(locale, locale.d->m_index, QLocale::NarrowFormat,
818 QDateTime(), offsetFromUtc);
823QTimeZonePrivate::NamePrefixMatch
824QTimeZonePrivate::findLongNamePrefix(QStringView text,
const QLocale &locale,
825 std::optional<qint64> atEpochMillis)
827 constexpr std::size_t invalidMetaId = std::size(metaIdData);
828 constexpr std::size_t invalidIanaId = std::size(ianaIdData);
829 constexpr QTimeZone::TimeType timeTypes[] = {
831 QTimeZone::GenericTime,
832 QTimeZone::StandardTime,
833 QTimeZone::DaylightTime,
836 qsizetype nameLength = 0;
837 QTimeZone::TimeType timeType = QTimeZone::GenericTime;
838 quint16 ianaIdIndex = invalidIanaId;
839 quint16 metaIdIndex = invalidMetaId;
840 QLocale::Territory where = QLocale::AnyTerritory;
842#define localeRows(table, member) QSpan(table).first(nextData.member).sliced(locData.member)
844 const QList<qsizetype> indices = fallbackLocalesFor(locale.d->m_index);
845 for (
const qsizetype locInd : indices) {
846 const LocaleZoneData &locData = localeZoneData[locInd];
848 Q_ASSERT(std::size_t(locInd) < std::size(localeZoneData) - 1);
849 const LocaleZoneData &nextData = localeZoneData[locInd + 1];
851 const auto metaRows =
localeRows(localeMetaZoneLongNameTable, m_metaLongTableStart);
852 for (
const LocaleMetaZoneLongNames &row : metaRows) {
853 for (
const QTimeZone::TimeType type : timeTypes) {
854 QLocaleData::DataRange range = row.longName(type);
855 if (range.size > best.nameLength) {
856 QStringView name = range.viewData(longMetaZoneNameTable);
857 if (text.startsWith(name)) {
858 best = {
static_cast<qsizetype>(range.size), type,
859 invalidIanaId, row.metaIdIndex };
860 if (best.nameLength >= text.size())
865 if (best.nameLength >= text.size())
869 const auto ianaRows =
localeRows(localeZoneNameTable, m_zoneTableStart);
870 for (
const LocaleZoneNames &row : ianaRows) {
871 for (
const QTimeZone::TimeType type : timeTypes) {
872 QLocaleData::DataRange range = row.longName(type);
873 if (range.size > best.nameLength) {
874 QStringView name = range.viewData(longZoneNameTable);
876 bool gotZone = row.ianaIdIndex == best.ianaIdIndex
877 || QTimeZone::isTimeZoneIdAvailable(row.ianaId().toByteArray());
878 if (text.startsWith(name) && gotZone)
879 best = {
static_cast<qsizetype>(range.size), type, row.ianaIdIndex };
885 if (best.metaIdIndex != invalidMetaId) {
886 const auto metaIdBefore = [](
auto &row, quint16 key) {
return row.metaIdIndex < key; };
889 const MetaZoneData *metaRow =
890 std::lower_bound(std::begin(metaZoneTable), std::end(metaZoneTable),
891 best.metaIdIndex, metaIdBefore);
893 for (; metaRow < std::end(metaZoneTable)
894 && metaRow->metaIdIndex == best.metaIdIndex; ++metaRow) {
895 auto metaLand = QLocale::Territory(metaRow->territory);
898 if ((best.where == QLocale::AnyTerritory || metaLand == QLocale::World)
900 ? metaRow->metaZoneKey == metaZoneAt(metaRow->ianaId(), *atEpochMillis)
901 : zoneEverInMeta(metaRow->ianaId(), metaRow->metaZoneKey))) {
902 if (metaRow->ianaIdIndex == best.ianaIdIndex
903 || QTimeZone::isTimeZoneIdAvailable(metaRow->ianaId().toByteArray())) {
904 best.ianaIdIndex = metaRow->ianaIdIndex;
905 best.where = metaLand;
906 if (best.where == QLocale::World)
912 if (best.ianaIdIndex != invalidIanaId)
913 return { QByteArray(ianaIdData + best.ianaIdIndex), best.nameLength, best.timeType };
919 NamePrefixMatch found;
920 for (
const qsizetype locInd : indices) {
921 const LocaleZoneData &locData = localeZoneData[locInd];
922 const LocaleZoneData &nextData = localeZoneData[locInd + 1];
923 for (
const QTimeZone::TimeType timeType : timeTypes) {
924 QStringView regionFormat
925 = locData.regionFormatRange(timeType).viewData(regionFormatTable);
927 const qsizetype cut = regionFormat.indexOf(u"%0");
931 QStringView prefix = regionFormat.first(cut);
933 if (cut > 0 && !text.startsWith(prefix))
935 QStringView suffix = regionFormat.sliced(cut + 2);
937 QStringView tail = text.sliced(cut);
940 if (suffix.size() && tail.indexOf(suffix) < 0)
945 const auto textMatches = [tail, suffix](QStringView where) {
946 return (where.isEmpty() || tail.startsWith(where))
947 && (suffix.isEmpty() || tail.sliced(where.size()).startsWith(suffix));
950 const auto cityRows =
localeRows(localeZoneExemplarTable, m_exemplarTableStart);
951 for (
const LocaleZoneExemplar &row : cityRows) {
952 QStringView city = row.exemplarCity().viewData(exemplarCityTable);
953 if (textMatches(city)) {
954 qsizetype length = cut + city.size() + suffix.size();
955 if (length > found.nameLength) {
956 bool gotZone = row.ianaId() == found.ianaId
957 || QTimeZone::isTimeZoneIdAvailable(row.ianaId().toByteArray());
959 found = { row.ianaId().toByteArray(), length, timeType };
964 const QList<QByteArray> allZones = QTimeZone::availableTimeZoneIds();
965 for (
const auto &iana : allZones) {
966 Q_ASSERT(!iana.isEmpty());
967 qsizetype slash = iana.lastIndexOf(
'/');
968 QByteArray local = slash > 0 ? iana.sliced(slash + 1) : iana;
969 QString city = QString::fromLatin1(local.replace(
'_',
' '));
970 if (textMatches(city)) {
971 qsizetype length = cut + city.size() + suffix.size();
972 if (length > found.nameLength)
973 found = { iana, length, timeType };
984QTimeZonePrivate::NamePrefixMatch
985QTimeZonePrivate::findNarrowOffsetPrefix(QStringView text,
const QLocale &locale)
988 if (
auto match = matchOffsetFormat(text, locale, locale.d->m_index, QLocale::NarrowFormat)) {
990 if (QTimeZone::MinUtcOffsetSecs <= match.offset
991 && match.offset <= QTimeZone::MaxUtcOffsetSecs) {
996 return { isoOffsetFormat(match.offset, QTimeZone::OffsetName).toLatin1(),
997 match.size, QTimeZone::GenericTime };
QList< QByteArrayView > ianaIdsForTerritory(QLocale::Territory territory)
#define tableLookup(table, member, sought, test)
#define localeRows(table, member)