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) {
40using namespace Qt::StringLiterals;
46UCalendarDisplayNameType ucalDisplayNameType(QTimeZone::TimeType timeType,
47 QTimeZone::NameType nameType)
52 case QTimeZone::ShortName:
53 return timeType == QTimeZone::DaylightTime ? UCAL_SHORT_DST : UCAL_SHORT_STANDARD;
54 case QTimeZone::DefaultName:
55 case QTimeZone::LongName:
56 return timeType == QTimeZone::DaylightTime ? UCAL_DST : UCAL_STANDARD;
57 case QTimeZone::OffsetName:
60 Q_UNREACHABLE_RETURN(UCAL_STANDARD);
65namespace QtTimeZoneLocale {
69QString ucalTimeZoneDisplayName(UCalendar *ucal,
70 QTimeZone::TimeType timeType,
71 QTimeZone::NameType nameType,
72 const QByteArray &localeCode)
74 constexpr int32_t BigNameLength = 50;
75 int32_t size = BigNameLength;
76 QString result(size, Qt::Uninitialized);
77 auto dst = [&result]() {
return reinterpret_cast<UChar *>(result.data()); };
78 UErrorCode status = U_ZERO_ERROR;
79 const UCalendarDisplayNameType utype = ucalDisplayNameType(timeType, nameType);
82 size = ucal_getTimeZoneDisplayName(ucal, utype, localeCode.constData(),
83 dst(), size, &status);
86 if (size > BigNameLength || status == U_BUFFER_OVERFLOW_ERROR) {
88 status = U_ZERO_ERROR;
89 size = ucal_getTimeZoneDisplayName(ucal, utype, localeCode.constData(),
90 dst(), size, &status);
93 if (!U_SUCCESS(status))
101bool ucalKnownTimeZoneId(
const QString &ianaStr)
103 const UChar *
const name =
reinterpret_cast<
const UChar *>(ianaStr.constData());
106 constexpr size_t size = 64;
111 UErrorCode status = U_ZERO_ERROR;
114 ucal_getCanonicalTimeZoneID(name, ianaStr.size(), buffer, size, &isSys, &status);
122QString QTimeZonePrivate::localeName(qint64 atMSecsSinceEpoch,
int offsetFromUtc,
123 QTimeZone::TimeType timeType,
124 QTimeZone::NameType nameType,
125 const QLocale &locale)
const
127 Q_UNUSED(atMSecsSinceEpoch);
131 if (nameType == QTimeZone::OffsetName)
132 return isoOffsetFormat(offsetFromUtc);
134 const QString id = QString::fromUtf8(m_id);
137 if (!QtTimeZoneLocale::ucalKnownTimeZoneId(id))
140 const QByteArray loc = locale.name().toUtf8();
141 UErrorCode status = U_ZERO_ERROR;
143 UCalendar *ucal = ucal_open(
reinterpret_cast<
const UChar *>(id.data()), id.size(),
144 loc.constData(), UCAL_DEFAULT, &status);
145 if (ucal && U_SUCCESS(status)) {
146 auto tidier = qScopeGuard([ucal]() { ucal_close(ucal); });
147 return QtTimeZoneLocale::ucalTimeZoneDisplayName(ucal, timeType, nameType, loc);
161using namespace QtTimeZoneCldr;
164template <
typename Row,
typename Sought,
typename Condition>
165const Row *findTableEntryFor(
const QSpan<Row> data, Sought value, Condition test)
170 auto begin = data.begin(), end = data.end();
171 Q_ASSERT(begin == end || end->localeIndex > begin->localeIndex);
172 Q_ASSERT(begin == end || end[-1].localeIndex == begin->localeIndex);
173 auto row =
std::lower_bound(begin, end, value, test);
174 return row == end ?
nullptr : row;
177QString exemplarCityFor(
const LocaleZoneData &locale,
const LocaleZoneData &next,
180 auto xct = findTableEntryFor(
181 QSpan(localeZoneExemplarTable).first(next.m_exemplarTableStart
182 ).sliced(locale.m_exemplarTableStart),
183 iana, [](
auto &row, QByteArrayView key) {
return row.ianaId() < key; });
184 if (xct && xct->ianaId() == iana)
185 return xct->exemplarCity().getData(exemplarCityTable);
190quint32 clipEpochMinute(qint64 epochMinute)
194 constexpr quint32 epoch = 0;
203 constexpr quint32 ragnarok = 1 ^ ~epoch;
204 return epochMinute + 1 >= ragnarok ? ragnarok : quint32(epochMinute);
207constexpr bool intervalEndsBefore(
const ZoneMetaHistory &record, quint32 dt)
noexcept
210 return record.end <= dt;
214
215
216
217quint16 metaZoneAt(QByteArrayView zoneId, qint64 atMSecsSinceEpoch)
219 using namespace QtPrivate::DateTimeConstants;
220 auto it =
std::lower_bound(
std::begin(zoneHistoryTable),
std::end(zoneHistoryTable), zoneId,
221 [](
const ZoneMetaHistory &record, QByteArrayView id) {
222 return record.ianaId().compare(id, Qt::CaseInsensitive) < 0;
224 if (it ==
std::end(zoneHistoryTable) || it->ianaId().compare(zoneId, Qt::CaseInsensitive) > 0)
227 std::upper_bound(it,
std::end(zoneHistoryTable), zoneId,
228 [](QByteArrayView id,
const ZoneMetaHistory &record) {
229 return id.compare(record.ianaId(), Qt::CaseInsensitive) < 0;
231 const quint32 dt = clipEpochMinute(atMSecsSinceEpoch / MSECS_PER_MIN);
232 it =
std::lower_bound(it, stop, dt, intervalEndsBefore);
233 return it != stop && it->begin <= dt ? it->metaZoneKey : 0;
237bool zoneEverInMeta(QByteArrayView zoneId, quint16 metaKey)
239 for (
auto it =
std::lower_bound(
std::begin(zoneHistoryTable),
std::end(zoneHistoryTable),
241 [](
const ZoneMetaHistory &record, QByteArrayView id) {
242 return record.ianaId().compare(id, Qt::CaseInsensitive) < 0;
244 it !=
std::end(zoneHistoryTable) && it->ianaId().compare(zoneId, Qt::CaseInsensitive) == 0;
246 if (it->metaZoneKey == metaKey)
252constexpr bool dataBeforeMeta(
const MetaZoneData &row, quint16 metaKey)
noexcept
254 return row.metaZoneKey < metaKey;
257constexpr bool metaDataBeforeTerritory(
const MetaZoneData &row, qint16 territory)
noexcept
259 return row.territory < territory;
262const MetaZoneData *metaZoneStart(quint16 metaKey)
264 const MetaZoneData *
const from =
265 std::lower_bound(
std::begin(metaZoneTable),
std::end(metaZoneTable),
266 metaKey, dataBeforeMeta);
267 if (from ==
std::end(metaZoneTable) || from->metaZoneKey != metaKey) {
268 qWarning(
"No metazone data found for metazone key %d", metaKey);
274const MetaZoneData *metaZoneDataFor(
const MetaZoneData *from, QLocale::Territory territory)
276 const quint16 metaKey = from->metaZoneKey;
277 const MetaZoneData *
const end =
278 std::lower_bound(from,
std::end(metaZoneTable), metaKey + 1, dataBeforeMeta);
279 Q_ASSERT(end != from && end[-1].metaZoneKey == metaKey);
280 QLocale::Territory land = territory;
282 const MetaZoneData *row =
283 std::lower_bound(from, end, qint16(land), metaDataBeforeTerritory);
284 if (row != end && QLocale::Territory(row->territory) == land) {
285 Q_ASSERT(row->metaZoneKey == metaKey);
289 }
while (std::exchange(land, QLocale::World) != QLocale::World);
291 qWarning(
"Metazone %s lacks World data for %ls",
292 from->metaZoneId().constData(),
293 qUtf16Printable(QLocale::territoryToString(territory)));
297QString addPadded(qsizetype width,
const QString &zero,
const QString &number, QString &&onto)
300 width -= number.size() / zero.size();
305 return std::move(onto) + number;
308QString formatOffset(QStringView format,
int offsetMinutes,
const QLocale &locale,
309 QtTemporalPattern::TemporalFieldFlags flags)
311 using Flag = QtTemporalPattern::TemporalFieldFlag;
312 Q_ASSERT(offsetMinutes >= 0);
313 const QString hour = locale.toString(offsetMinutes / 60);
314 const QString mins = locale.toString(offsetMinutes % 60);
317 const QString zero = locale.zeroDigit();
318 QStringView tail = format;
320 while (!tail.isEmpty()) {
321 if (tail.startsWith(u'\'')) {
322 qsizetype end = tail.indexOf(u'\'', 1);
324 qWarning(
"Unbalanced quote in offset format string: %s",
325 format.toUtf8().constData());
326 return result + tail;
327 }
else if (end == 1) {
330 tail = tail.sliced(2);
333 while (end + 1 < tail.size() && tail[end + 1] == u'\'') {
336 result += tail.sliced(1, end);
337 tail = tail.sliced(end + 1);
338 end = tail.indexOf(u'\'', 1);
340 qWarning(
"Unbalanced quoted quote in offset format string: %s",
341 format.toUtf8().constData());
342 return result + tail;
347 result += tail.sliced(1, end - 1);
348 tail = tail.sliced(end + 1);
350 }
else if (tail.startsWith(u'H')) {
352 while (width < tail.size() && tail[width] == u'H')
354 tail = tail.sliced(width);
355 if (flags.testFlag(Flag::ZeroPad))
356 result = addPadded(width, zero, hour, std::move(result));
359 }
else if (tail.startsWith(u'm')) {
361 while (width < tail.size() && tail[width] == u'm')
365 tail = tail.sliced(width);
366 if (flags.testFlag(Flag::ZeroPad))
367 result = addPadded(width, zero, mins, std::move(result));
368 else if (offsetMinutes % 60)
370 else if (result.endsWith(u':') || result.endsWith(u'.'))
373 }
else if (tail[0].isHighSurrogate() && tail.size() > 1
374 && tail[1].isLowSurrogate()) {
375 result += tail.first(2);
376 tail = tail.sliced(2);
378 result += tail.front();
379 tail = tail.sliced(1);
385struct OffsetFormatMatch
389 operator
bool()
const {
return size > 0; }
392OffsetFormatMatch matchOffsetText(QStringView text, QStringView format,
const QLocale &locale,
393 QtTemporalPattern::TemporalFieldFlags flags)
395 using namespace QtTemporalPattern;
396 using namespace FieldGroup;
397 using Flag = TemporalFieldFlag;
398 const bool zeroPad = flags.testFlag(Flag::ZeroPad);
399 OffsetFormatMatch res;
412 qsizetype cut = format.indexOf(u'H');
413 if (cut < 0 || !text.startsWith(format.first(cut)) || !format.endsWith(u"mm"))
415 QStringView sep = format.sliced(cut).chopped(2);
417 while (hlen < sep.size() && sep[hlen] == u'H')
420 sep = sep.sliced(hlen);
422 const QLocaleData *
const locDat = QLocalePrivate::get(locale)->m_data;
423 using Digits = QLocaleData::DigitSequence;
424 const Digits early = locDat->digitSequence(text, {}, cut);
425 Q_ASSERT(!early.sign);
428 if (qsizetype maxLen =
std::max(2, hlen); maxLen < hrs.digits.size())
429 hrs = hrs.first(maxLen);
430 if (hrs.digits.size() < 1)
432 if (zeroPad && hrs.digits.size() < hlen)
435 Digits mins = early.sliced(hrs.digits.size());
436 if (!sep.isEmpty()) {
437 const qsizetype sepAt = text.indexOf(sep, cut);
438 if (sepAt == hrs.endIndex())
439 mins = locDat->digitSequence(text, {}, sepAt + sep.size());
441 mins = mins.first(0);
443 Q_ASSERT(!mins.sign);
444 if (mins.digits.size() > 2)
445 mins = mins.first(2);
446 else if (!mins.isEmpty() && mins.digits.size() < 2)
447 mins = mins.first(0);
449 constexpr int MaxOffsetHours
450 = (
std::max)(-QTimeZone::MinUtcOffsetSecs, QTimeZone::MaxUtcOffsetSecs) / 3600;
453 uint hour = hrs.digits.toUInt(&ok);
454 if (!ok || hour > MaxOffsetHours || (zeroPad && mins.isEmpty())) {
460 mins = mins.first(0);
461 hour = hrs.digits.toUInt(&ok);
465 && (!zeroPad || matchesFlagWithin(flags, Flag::Abbreviated, WidthMask))) {
466 uint minute = mins.digits.toUInt(&ok);
467 if (ok && minute < 60) {
468 res.offset = (hour * 60 + minute) * 60;
469 res.size = mins.endIndex();
473 if (!zeroPad || matchesFlagWithin(flags, Flag::Narrow, WidthMask)) {
474 res.offset = hour * 60 * 60;
475 res.size = hrs.endIndex();
481OffsetFormatMatch matchOffsetFormat(QStringView text,
const QLocale &locale, qsizetype locInd,
482 QtTemporalPattern::TemporalFieldFlags flags)
484 OffsetFormatMatch best;
485 using Flag = QtTemporalPattern::TemporalFieldFlag;
486 using namespace QtTemporalPattern::FieldGroup;
487 const LocaleZoneData &locData = localeZoneData[locInd];
488 const QStringView posHourForm = locData.posHourFormat().viewData(hourFormatTable);
489 const QStringView negHourForm = locData.negHourFormat().viewData(hourFormatTable);
491 const bool mapNeg = text.contains(u'-')
492 && (negHourForm.contains(u'\u2212') || negHourForm.contains(locale.negativeSign()));
493 if (QtTemporalPattern::matchesFlagWithin(flags, Flag::NeedNoUtcPrefix, UtcPrefixMask)) {
494 if (
auto match = matchOffsetText(text, posHourForm, locale, flags))
496 if (
auto match = matchOffsetText(text, negHourForm, locale, flags); match.size > best.size)
497 best = { match.size, -match.offset };
499 const QString mapped = negHourForm.toString()
500 .replace(u'\u2212', u'-').replace(locale.negativeSign(),
"-"_L1);
501 if (
auto match = matchOffsetText(text, mapped, locale, flags); match.size > best.size)
502 best = { match.size, -match.offset };
505 if (QtTemporalPattern::matchesFlagWithin(flags, Flag::AcceptUtcPrefix, UtcPrefixMask)) {
506 const QStringView offsetFormat = locData.offsetGmtFormat().viewData(gmtFormatTable);
507 if (
const qsizetype cut = offsetFormat.indexOf(u"%0"); cut >= 0) {
508 const QStringView gmtPrefix = offsetFormat.first(cut);
509 const QStringView gmtSuffix = offsetFormat.sliced(cut + 2);
510 const qsizetype gmtSize = cut + gmtSuffix.size();
511 const auto crossMatch = [gmtPrefix, text]
512 (QLatin1StringView lhs, QLatin1StringView rhs) {
513 const qsizetype len = lhs.size();
514 Q_ASSERT(len == rhs.size());
515 if (!gmtPrefix.startsWith(lhs) || !text.startsWith(rhs))
517 if (gmtPrefix.size() == len)
519 return text.sliced(len).startsWith(gmtPrefix.sliced(len));
523 if ((gmtPrefix.isEmpty() || text.startsWith(gmtPrefix)
526 || crossMatch(
"GMT"_L1,
"UTC"_L1) || crossMatch(
"UTC"_L1,
"GMT"_L1))
527 && (gmtSuffix.isEmpty() || text.sliced(cut).indexOf(gmtSuffix) >= 0)) {
528 if (
auto match = matchOffsetText(text.sliced(cut), posHourForm, locale, flags);
529 gmtSize + match.size > best.size) {
530 if (text.sliced(cut + match.size).startsWith(gmtSuffix))
531 best = { gmtSize + match.size, match.offset };
533 if (
auto match = matchOffsetText(text.sliced(cut), negHourForm, locale, flags)) {
534 if (gmtSize + match.size > best.size
535 && text.sliced(cut + match.size).startsWith(gmtSuffix)) {
536 best = { gmtSize + match.size, -match.offset };
539 const QString mapped = negHourForm.toString()
540 .replace(u'\u2212', u'-').replace(locale.negativeSign(),
"-"_L1);
541 if (
auto match = matchOffsetText(text.sliced(cut), mapped, locale, flags);
542 gmtSize + match.size > best.size) {
543 if (text.sliced(cut + match.size).startsWith(gmtSuffix))
544 best = { gmtSize + match.size, -match.offset };
548 if (gmtSize > best.size && text.sliced(cut).startsWith(gmtSuffix))
549 return { gmtSize, 0 };
562 QList<QByteArrayView> result;
564 const TerritoryZone *row =
565 std::lower_bound(
std::begin(territoryZoneMap),
std::end(territoryZoneMap),
567 [](
const TerritoryZone &row, qint16 territory) {
568 return row.territory < territory;
570 if (row != std::end(territoryZoneMap) && QLocale::Territory(row->territory) == territory)
571 result << row->ianaId();
573 for (
const MetaZoneData &row : metaZoneTable) {
574 if (QLocale::Territory(row.territory) == territory)
575 result << row.ianaId();
580#if QT_CONFIG(datestring)
616QString QTimeZonePrivate::localeName(qint64 atMSecsSinceEpoch,
int offsetFromUtc,
617 QTimeZone::TimeType timeType,
618 QTimeZone::NameType nameType,
619 const QLocale &locale)
const
621#if QT_CONFIG(datestring)
622 if (nameType == QTimeZone::OffsetName
624 || QUtcTimeZonePrivate::offsetFromUtcString(m_id) != invalidSeconds()) {
625 using Flag = QtTemporalPattern::TemporalFieldFlag;
626 constexpr QtTemporalPattern::TemporalFieldFlags flags
627 = Flag::Numeric | Flag::Abbreviated | Flag::AcceptUtcPrefix | Flag::ZeroPad;
629 return QtTimeZoneLocale::zoneOffsetFormat(locale, locale.d->m_index, flags,
630 QDateTime(), offsetFromUtc);
636 QByteArray ianaAbbrev, ianaTail;
637 const auto scanIana = [&](QByteArrayView iana) {
641 if (!ianaAbbrev.isEmpty() && !ianaTail.isEmpty())
643 qsizetype cut = iana.lastIndexOf(
'/');
644 QByteArrayView tail = cut < 0 ? iana : iana.sliced(cut + 1);
646 if (tail ==
"McMurdo") {
647 if (ianaTail.isEmpty())
648 ianaTail =
"McMurdo"_ba;
650 }
else if (tail ==
"DumontDUrville") {
651 if (ianaTail.isEmpty())
652 ianaTail =
"Dumont d'Urville"_ba;
654 }
else if (tail.isEmpty()) {
658 const auto isMixedCaseAbbrev = [tail](
char ch) {
662 switch (tail.size()) {
663 case 2:
return tail ==
"Gk";
664 case 3:
return tail ==
"CuT";
666 if (tail[0] ==
'C' && tail[1] == ch && tail[3] ==
'T') {
668 case 'h':
return tail[2] ==
'S';
669 case 'u':
return tail[2] ==
'S' || tail[2] ==
'D';
681 bool maybeAbbr = ianaAbbrev.isEmpty(), maybeCityName = ianaTail.isEmpty(), inword =
false;
683 for (
char ch : tail) {
684 if (ch ==
'+' || ch ==
'-') {
685 if (ch ==
'+' || !inword)
686 maybeCityName =
false;
694 }
else if (ch ==
'_') {
697 maybeCityName =
false;
699 }
else if (QChar::isLower(ch)) {
700 maybeAbbr = isMixedCaseAbbrev(ch);
703 }
else if (QChar::isUpper(ch)) {
707 maybeCityName =
false;
709 }
else if (QChar::isDigit(ch)) {
712 maybeCityName =
false;
716 if (!maybeAbbr && !maybeCityName)
719 if (maybeAbbr && maybeCityName)
723 if (tail.endsWith(
"-0") || tail.endsWith(
"+0"))
724 tail = tail.chopped(2);
725 ianaAbbrev = tail.toByteArray();
726 if (sign && iana.startsWith(
"Etc/")) {
728 ianaAbbrev = ianaAbbrev.replace(
'-',
'+');
729 else if (sign ==
'+')
730 ianaAbbrev = ianaAbbrev.replace(
'+',
'-');
736 ianaTail = tail.toByteArray().replace(
'_',
' ');
740 if (QByteArrayView iana = aliasToIana(m_id); !iana.isEmpty() && iana != m_id)
744#define tableLookup(table, member, sought, test)
745 findTableEntryFor(QSpan(table).first(nextData.member).sliced(locData.member), sought, test)
749 const QList<qsizetype> indices = fallbackLocalesFor(locale.d->m_index);
750 QString exemplarCity;
751 const auto metaIdBefore = [](
auto &row, quint16 key) {
return row.metaIdIndex < key; };
754 for (
const qsizetype locInd : indices) {
755 const LocaleZoneData &locData = localeZoneData[locInd];
757 Q_ASSERT(std::size_t(locInd) < std::size(localeZoneData) - 1);
758 const LocaleZoneData &nextData = localeZoneData[locInd + 1];
760 QByteArrayView iana{m_id};
761 if (quint16 metaKey = metaZoneAt(iana, atMSecsSinceEpoch)) {
762 if (
const MetaZoneData *metaFrom = metaZoneStart(metaKey)) {
763 quint16 metaIdIndex = metaFrom->metaIdIndex;
764 QLocaleData::DataRange range{0, 0};
765 const char16_t *strings =
nullptr;
766 if (nameType == QTimeZone::ShortName) {
767 auto row =
tableLookup(localeMetaZoneShortNameTable, m_metaShortTableStart,
768 metaIdIndex, metaIdBefore);
769 if (row && row->metaIdIndex == metaIdIndex) {
770 range = row->shortName(timeType);
771 strings = shortMetaZoneNameTable;
774 auto row =
tableLookup(localeMetaZoneLongNameTable, m_metaLongTableStart,
775 metaIdIndex, metaIdBefore);
776 if (row && row->metaIdIndex == metaIdIndex) {
777 range = row->longName(timeType);
778 strings = longMetaZoneNameTable;
781 Q_ASSERT(strings || !range.size);
784 return range.getData(strings);
786 if (
const auto *metaRow = metaZoneDataFor(metaFrom, locale.territory()))
787 iana = metaRow->ianaId();
792 if (exemplarCity.isEmpty()) {
793 exemplarCity = exemplarCityFor(locData, nextData, m_id);
794 if (exemplarCity.isEmpty())
795 exemplarCity = exemplarCityFor(locData, nextData, iana);
804 localeZoneNameTable, m_zoneTableStart,
805 iana, [](
auto &row, QByteArrayView key) {
return row.ianaId() < key; });
806 if (row && row->ianaId() == iana) {
807 QLocaleData::DataRange range = row->name(nameType, timeType);
809 auto table = nameType == QTimeZone::ShortName
812 return range.getData(table);
815 }
while (std::exchange(iana, QByteArrayView{m_id}) != m_id);
820 if (exemplarCity.isEmpty() && !ianaTail.isEmpty())
821 exemplarCity = QString::fromLatin1(ianaTail);
824 case QTimeZone::DefaultName:
825 case QTimeZone::LongName:
826 for (
const qsizetype locInd : indices) {
827 const LocaleZoneData &locData = localeZoneData[locInd];
828 QStringView regionFormat
829 = locData.regionFormatRange(timeType).viewData(regionFormatTable);
830 if (!regionFormat.isEmpty()) {
831 QString where = exemplarCity;
833 if (!where.isEmpty())
834 return regionFormat.arg(where);
838 for (
const qsizetype locInd : indices) {
839 const LocaleZoneData &locData = localeZoneData[locInd];
840 QStringView fallbackFormat = locData.fallbackFormat().viewData(fallbackFormatTable);
848 case QTimeZone::ShortName:
850 if (!ianaAbbrev.isEmpty())
851 return QString::fromLatin1(ianaAbbrev);
854 case QTimeZone::OffsetName:
855 Q_UNREACHABLE_RETURN(QString());
863#if QT_CONFIG(datestring)
864 using Flag = QtTemporalPattern::TemporalFieldFlag;
865 constexpr QtTemporalPattern::TemporalFieldFlags compact
866 = Flag::Numeric | Flag::Abbreviated | Flag::AcceptUtcPrefix;
867 return QtTimeZoneLocale::zoneOffsetFormat(locale, locale.d->m_index, compact,
868 QDateTime(), offsetFromUtc);
876QTimeZonePrivate::NamePrefixMatch
877QTimeZonePrivate::findLongNamePrefix(QStringView text,
const QLocale &locale,
878 std::optional<qint64> atEpochMillis)
880 constexpr std::size_t invalidMetaId = std::size(metaIdData);
881 constexpr std::size_t invalidIanaId = std::size(ianaIdData);
882 constexpr QTimeZone::TimeType timeTypes[] = {
884 QTimeZone::GenericTime,
885 QTimeZone::StandardTime,
886 QTimeZone::DaylightTime,
889 qsizetype nameLength = 0;
890 QTimeZone::TimeType timeType = QTimeZone::GenericTime;
891 quint16 ianaIdIndex = invalidIanaId;
892 quint16 metaIdIndex = invalidMetaId;
893 QLocale::Territory where = QLocale::AnyTerritory;
895#define localeRows(table, member) QSpan(table).first(nextData.member).sliced(locData.member)
897 const QList<qsizetype> indices = fallbackLocalesFor(locale.d->m_index);
898 for (
const qsizetype locInd : indices) {
899 const LocaleZoneData &locData = localeZoneData[locInd];
901 Q_ASSERT(std::size_t(locInd) < std::size(localeZoneData) - 1);
902 const LocaleZoneData &nextData = localeZoneData[locInd + 1];
905 const auto metaRows =
localeRows(localeMetaZoneLongNameTable, m_metaLongTableStart);
906 for (
const LocaleMetaZoneLongNames &row : metaRows) {
907 for (
const QTimeZone::TimeType type : timeTypes) {
908 QLocaleData::DataRange range = row.longName(type);
909 if (range.size > best.nameLength) {
910 QStringView name = range.viewData(longMetaZoneNameTable);
911 if (text.startsWith(name)) {
912 best = {
static_cast<qsizetype>(range.size), type,
913 invalidIanaId, row.metaIdIndex };
914 if (best.nameLength >= text.size())
919 if (best.nameLength >= text.size())
923 const auto ianaRows =
localeRows(localeZoneNameTable, m_zoneTableStart);
924 for (
const LocaleZoneNames &row : ianaRows) {
925 for (
const QTimeZone::TimeType type : timeTypes) {
926 QLocaleData::DataRange range = row.longName(type);
927 if (range.size > best.nameLength) {
928 QStringView name = range.viewData(longZoneNameTable);
930 bool gotZone = row.ianaIdIndex == best.ianaIdIndex
931 || QTimeZone::isTimeZoneIdAvailable(row.ianaId());
932 if (text.startsWith(name) && gotZone)
933 best = {
static_cast<qsizetype>(range.size), type, row.ianaIdIndex };
939 if (best.metaIdIndex != invalidMetaId) {
940 const auto metaIdBefore = [](
auto &row, quint16 key) {
return row.metaIdIndex < key; };
943 const MetaZoneData *metaRow =
944 std::lower_bound(std::begin(metaZoneTable), std::end(metaZoneTable),
945 best.metaIdIndex, metaIdBefore);
947 for (; metaRow < std::end(metaZoneTable)
948 && metaRow->metaIdIndex == best.metaIdIndex; ++metaRow) {
949 auto metaLand = QLocale::Territory(metaRow->territory);
952 if ((best.where == QLocale::AnyTerritory || metaLand == QLocale::World)
954 ? metaRow->metaZoneKey == metaZoneAt(metaRow->ianaId(), *atEpochMillis)
955 : zoneEverInMeta(metaRow->ianaId(), metaRow->metaZoneKey))) {
956 if (metaRow->ianaIdIndex == best.ianaIdIndex
957 || QTimeZone::isTimeZoneIdAvailable(metaRow->ianaId())) {
958 best.ianaIdIndex = metaRow->ianaIdIndex;
959 best.where = metaLand;
960 if (best.where == QLocale::World)
966 if (best.ianaIdIndex != invalidIanaId)
967 return { QByteArray(ianaIdData + best.ianaIdIndex), best.nameLength, best.timeType };
973 NamePrefixMatch found;
974 for (
const qsizetype locInd : indices) {
975 const LocaleZoneData &locData = localeZoneData[locInd];
976 const LocaleZoneData &nextData = localeZoneData[locInd + 1];
977 for (
const QTimeZone::TimeType timeType : timeTypes) {
978 QStringView regionFormat
979 = locData.regionFormatRange(timeType).viewData(regionFormatTable);
981 const qsizetype cut = regionFormat.indexOf(u"%0");
985 QStringView prefix = regionFormat.first(cut);
987 if (cut > 0 && !text.startsWith(prefix))
989 QStringView suffix = regionFormat.sliced(cut + 2);
991 QStringView tail = text.sliced(cut);
994 if (suffix.size() && tail.indexOf(suffix) < 0)
999 const auto textMatches = [tail, suffix](QStringView where) {
1000 return (where.isEmpty() || tail.startsWith(where))
1001 && (suffix.isEmpty() || tail.sliced(where.size()).startsWith(suffix));
1004 const auto cityRows =
localeRows(localeZoneExemplarTable, m_exemplarTableStart);
1005 for (
const LocaleZoneExemplar &row : cityRows) {
1006 QStringView city = row.exemplarCity().viewData(exemplarCityTable);
1007 if (textMatches(city)) {
1008 qsizetype length = cut + city.size() + suffix.size();
1009 if (length > found.nameLength) {
1010 bool gotZone = row.ianaId() == found.ianaId
1011 || QTimeZone::isTimeZoneIdAvailable(row.ianaId());
1013 found = { row.ianaId().toByteArray(), length, timeType };
1018 const QList<QByteArray> allZones = QTimeZone::availableTimeZoneIds();
1019 for (
const auto &iana : allZones) {
1020 Q_ASSERT(!iana.isEmpty());
1021 qsizetype slash = iana.lastIndexOf(
'/');
1022 QByteArray local = slash > 0 ? iana.sliced(slash + 1) : iana;
1023 QString city = QString::fromLatin1(local.replace(
'_',
' '));
1024 if (textMatches(city)) {
1025 qsizetype length = cut + city.size() + suffix.size();
1026 if (length > found.nameLength)
1027 found = { iana, length, timeType };
1038QTimeZonePrivate::NamePrefixMatch
1039QTimeZonePrivate::findNarrowOffsetPrefix(QStringView text,
const QLocale &locale)
1041 using Flag = QtTemporalPattern::TemporalFieldFlag;
1042 constexpr auto narrowOffset = Flag::Numeric | Flag::Abbreviated | Flag::AcceptUtcPrefix;
1043 if (
const auto match = matchOffsetFormat(text, locale, locale.d->m_index, narrowOffset)) {
1045 if (QTimeZone::MinUtcOffsetSecs <= match.offset
1046 && match.offset <= QTimeZone::MaxUtcOffsetSecs) {
1051 return { isoOffsetFormat(match.offset, QTimeZone::OffsetName).toLatin1(),
1052 match.size, QTimeZone::GenericTime };
1058QTimeZonePrivate::NamePrefixMatch
1059QTimeZonePrivate::findOffsetPrefix(QStringView text,
const QLocale &locale,
1060 QtTemporalPattern::TemporalFieldFlags flags)
1062 NamePrefixMatch best;
1066 const auto idForOffset = [](
int offsetSeconds) -> QByteArray {
1069 return isoOffsetFormat(offsetSeconds, QTimeZone::OffsetName).toLatin1();
1072 const auto match = matchOffsetFormat(text, locale, locale.d->m_index, flags);
1073 if (match && match.size > best.nameLength
1074 && QTimeZone::MinUtcOffsetSecs <= match.offset
1075 && match.offset <= QTimeZone::MaxUtcOffsetSecs) {
1076 best = { idForOffset(match.offset), match.size, QTimeZone::GenericTime };
QList< QByteArrayView > ianaIdsForTerritory(QLocale::Territory territory)
#define tableLookup(table, member, sought, test)
#define localeRows(table, member)