5#include "private/qdatetimeparser_p.h"
11#include "private/qlocale_p.h"
12#include "private/qlocale_tools_p.h"
14#include "private/qstringiterator_p.h"
15#include "private/qtenvironmentvariables_p.h"
17#if QT_CONFIG(timezone)
18#include "private/qtimezoneprivate_p.h"
24#if defined (QDATETIMEPARSER_DEBUG) && !defined(QT_NO_DEBUG_STREAM)
25# define QDTPDEBUG qDebug()
26# define QDTPDEBUGN qDebug
28# define QDTPDEBUG if (false) qDebug()
29# define QDTPDEBUGN if (false) qDebug
34constexpr int QDateTimeParser::NoSectionIndex;
35constexpr int QDateTimeParser::FirstSectionIndex;
36constexpr int QDateTimeParser::LastSectionIndex;
38using namespace Qt::StringLiterals;
43QDateTimeParser::~QDateTimeParser()
48
49
50
51
52
53
54
56int QDateTimeParser::getDigit(
const QDateTime &t,
int index)
const
58 if (index < 0 || index >= sectionNodes.size()) {
59 qWarning(
"QDateTimeParser::getDigit() Internal error (%ls %d)",
60 qUtf16Printable(t.toString()), index);
63 const SectionNode &node = sectionNodes.at(index);
65 case TimeZoneSection:
return t.offsetFromUtc();
66 case Hour24Section:
case Hour12Section:
return t.time().hour();
67 case MinuteSection:
return t.time().minute();
68 case SecondSection:
return t.time().second();
69 case MSecSection:
return t.time().msec();
70 case YearSection2Digits:
71 case YearSection:
return t.date().year(calendar);
72 case MonthSection:
return t.date().month(calendar);
73 case DaySection:
return t.date().day(calendar);
74 case DayOfWeekSectionShort:
75 case DayOfWeekSectionLong:
return calendar.dayOfWeek(t.date());
76 case AmPmSection:
return t.time().hour() > 11 ? 1 : 0;
81 qWarning(
"QDateTimeParser::getDigit() Internal error 2 (%ls %d)",
82 qUtf16Printable(t.toString()), index);
87
88
89
90
91
92
93
97 const int diff = sought - held;
98 return diff < -3 ? diff + 7 : diff > 3 ? diff - 7 : diff;
105 for (
const auto &node : nodes) {
106 if (node.type & QDateTimeParser::DaySection)
108 if (node.type & QDateTimeParser::DayOfWeekSectionMask)
115
116
117
118
119
120
121
122
123
124
126bool QDateTimeParser::setDigit(QDateTime &v,
int index,
int newVal)
const
128 if (index < 0 || index >= sectionNodes.size()) {
129 qWarning(
"QDateTimeParser::setDigit() Internal error (%ls %d %d)",
130 qUtf16Printable(v.toString()), index, newVal);
134 const QDate oldDate = v.date();
135 QCalendar::YearMonthDay date = calendar.partsFromDate(oldDate);
138 int weekDay = calendar.dayOfWeek(oldDate);
139 enum { NoFix, MonthDay, WeekDay } fixDay = NoFix;
141 const QTime time = v.time();
142 int hour = time.hour();
143 int minute = time.minute();
144 int second = time.second();
145 int msec = time.msec();
146 QTimeZone timeZone = v.timeRepresentation();
148 const SectionNode &node = sectionNodes.at(index);
150 case Hour24Section:
case Hour12Section: hour = newVal;
break;
151 case MinuteSection: minute = newVal;
break;
152 case SecondSection: second = newVal;
break;
153 case MSecSection: msec = newVal;
break;
154 case YearSection2Digits:
155 case YearSection: date.year = newVal;
break;
156 case MonthSection: date.month = newVal;
break;
167 case DayOfWeekSectionShort:
168 case DayOfWeekSectionLong:
169 if (newVal > 7 || newVal <= 0)
171 date.day += dayOfWeekDiff(newVal, weekDay);
175 case TimeZoneSection:
176 if (newVal < absoluteMin(index) || newVal > absoluteMax(index))
179 timeZone = QTimeZone::fromSecondsAheadOfUtc(newVal);
181 case AmPmSection: hour = (newVal == 0 ? hour % 12 : (hour % 12) + 12);
break;
183 qWarning(
"QDateTimeParser::setDigit() Internal error (%ls)",
184 qUtf16Printable(node.name()));
188 if (!(node.type & DaySectionMask)) {
189 if (date.day < cachedDay)
190 date.day = cachedDay;
192 if (weekDay > 0 && weekDay <= 7 && preferDayOfWeek(sectionNodes)) {
193 const int max = calendar.daysInMonth(date.month, date.year);
194 if (max > 0 && date.day > max)
196 const int newDoW = calendar.dayOfWeek(calendar.dateFromParts(date));
197 if (newDoW > 0 && newDoW <= 7)
198 date.day += dayOfWeekDiff(weekDay, newDoW);
203 if (fixDay != NoFix) {
204 const int max = calendar.daysInMonth(date.month, date.year);
206 if (max > 0 && date.day > max)
207 date.day = fixDay == WeekDay ? date.day - 7 : max;
208 else if (date.day < 1)
209 date.day = fixDay == WeekDay ? date.day + 7 : 1;
210 Q_ASSERT(fixDay != WeekDay
211 || calendar.dayOfWeek(calendar.dateFromParts(date)) == weekDay);
214 const QDate newDate = calendar.dateFromParts(date);
215 const QTime newTime(hour, minute, second, msec);
216 if (!newDate.isValid() || !newTime.isValid())
219 v = QDateTime(newDate, newTime, timeZone);
226
227
228
229
231int QDateTimeParser::absoluteMax(
int s,
const QDateTime &cur)
const
233 const SectionNode &sn = sectionNode(s);
235 case TimeZoneSection:
236 return QTimeZone::MaxUtcOffsetSecs;
247 case YearSection2Digits:
253 return calendar.maximumMonthsInYear();
255 return cur.isValid() ? cur.date().daysInMonth(calendar) : calendar.maximumDaysInMonth();
256 case DayOfWeekSectionShort:
257 case DayOfWeekSectionLong:
260 return int(UpperCase);
264 qWarning(
"QDateTimeParser::absoluteMax() Internal error (%ls)",
265 qUtf16Printable(sn.name()));
270
271
272
273
275int QDateTimeParser::absoluteMin(
int s)
const
277 const SectionNode &sn = sectionNode(s);
279 case TimeZoneSection:
280 return QTimeZone::MinUtcOffsetSecs;
286 case YearSection2Digits:
292 case DayOfWeekSectionShort:
293 case DayOfWeekSectionLong:
return 1;
294 case AmPmSection:
return int(NativeCase);
297 qWarning(
"QDateTimeParser::absoluteMin() Internal error (%ls, %0x)",
298 qUtf16Printable(sn.name()), sn.type);
303
304
305
306
308const QDateTimeParser::SectionNode &QDateTimeParser::sectionNode(
int sectionIndex)
const
310 static constexpr SectionNode first{FirstSection, 0, -1, 0};
311 static constexpr SectionNode last{LastSection, -1, -1, 0};
312 static constexpr SectionNode none{NoSection, -1, -1, 0};
313 if (sectionIndex < 0) {
314 switch (sectionIndex) {
315 case FirstSectionIndex:
317 case LastSectionIndex:
322 }
else if (sectionIndex < sectionNodes.size()) {
323 return sectionNodes.at(sectionIndex);
326 qWarning(
"QDateTimeParser::sectionNode() Internal error (%d)",
331QDateTimeParser::Section QDateTimeParser::sectionType(
int sectionIndex)
const
333 return sectionNode(sectionIndex).type;
338
339
340
341
343int QDateTimeParser::sectionPos(
int sectionIndex)
const
345 return sectionPos(sectionNode(sectionIndex));
348int QDateTimeParser::sectionPos(SectionNode sn)
const
351 case FirstSection:
return 0;
352 case LastSection:
return displayText().size() - 1;
356 qWarning(
"QDateTimeParser::sectionPos Internal error (%ls)", qUtf16Printable(sn.name()));
363
364
365
366
370 qsizetype digits = 0;
371 for (QStringIterator it(str); it.hasNext();) {
372 if (!QChar::isDigit(it.next()))
380
381
382
383
384
385
389 const QLatin1Char quote(
'\'');
390 const QLatin1Char slash(
'\\');
391 const QLatin1Char zero(
'0');
394 const int max = str.size();
395 for (
int i=0; i<max; ++i) {
396 if (str.at(i) == quote) {
399 else if (!ret.isEmpty() && str.at(i - 1) == slash)
400 ret[ret.size() - 1] = quote;
410static inline int countRepeat(QStringView str,
int index,
int maxCount)
412 str = str.sliced(index);
413 if (maxCount < str.size())
414 str = str.first(maxCount);
416 return qt_repeatCount(str);
420 int from,
int size,
int lastQuote)
422 Q_ASSERT(size >= 0 && from + size <= string.size());
423 const QStringView separator = string.sliced(from, size);
424 list->append(lastQuote >= from ? unquote(separator) : separator.toString());
428
429
430
431
432
433bool QDateTimeParser::parseFormat(QStringView newFormat)
435 const QLatin1Char quote(
'\'');
436 const QLatin1Char slash(
'\\');
437 const QLatin1Char zero(
'0');
438 if (newFormat == displayFormat && !newFormat.isEmpty())
441 QDTPDEBUGN(
"parseFormat: %s", newFormat.toLatin1().constData());
443 QList<SectionNode> newSectionNodes;
445 QStringList newSeparators;
448 QLatin1Char status(zero);
449 const int max = newFormat.size();
451 for (i = 0; i<max; ++i) {
452 if (newFormat.at(i) == quote) {
457 else if (i > 0 && newFormat.at(i - 1) != slash)
459 }
else if (status != quote) {
460 const char sect = newFormat.at(i).toLatin1();
464 if (parserType != QMetaType::QDate) {
465 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
466 const Section hour = (sect ==
'h') ? Hour12Section : Hour24Section;
467 const SectionNode sn{hour, i - add, countRepeat(newFormat, i, 2)};
468 newSectionNodes.append(sn);
475 if (parserType != QMetaType::QDate) {
476 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
477 const SectionNode sn{MinuteSection, i - add, countRepeat(newFormat, i, 2)};
478 newSectionNodes.append(sn);
481 newDisplay |= MinuteSection;
485 if (parserType != QMetaType::QDate) {
486 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
487 const SectionNode sn{SecondSection, i - add, countRepeat(newFormat, i, 2)};
488 newSectionNodes.append(sn);
491 newDisplay |= SecondSection;
496 if (parserType != QMetaType::QDate) {
497 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
498 const int repeat = countRepeat(newFormat, i, 3);
499 const SectionNode sn{MSecSection, i - add, repeat < 3 ? 1 : 3};
500 newSectionNodes.append(sn);
503 newDisplay |= MSecSection;
508 if (parserType != QMetaType::QDate) {
509 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
510 const int pos = i - add;
511 Case caseOpt = sect ==
'A' ? UpperCase : LowerCase;
512 newDisplay |= AmPmSection;
513 if (i + 1 < newFormat.size()
514 && newFormat.sliced(i + 1).startsWith(u'p', Qt::CaseInsensitive)) {
516 if (newFormat.at(i) != QLatin1Char(caseOpt == UpperCase ?
'P' :
'p'))
517 caseOpt = NativeCase;
519 const SectionNode sn{AmPmSection, pos,
int(caseOpt)};
520 newSectionNodes.append(sn);
525 if (parserType != QMetaType::QTime) {
526 const int repeat = countRepeat(newFormat, i, 4);
528 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
529 const SectionNode sn{repeat == 4 ? YearSection : YearSection2Digits,
530 i - add, repeat == 4 ? 4 : 2};
531 newSectionNodes.append(sn);
534 newDisplay |= sn.type;
539 if (parserType != QMetaType::QTime) {
540 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
541 const SectionNode sn{MonthSection, i - add, countRepeat(newFormat, i, 4)};
542 newSectionNodes.append(sn);
545 newDisplay |= MonthSection;
549 if (parserType != QMetaType::QTime) {
550 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
551 const int repeat = countRepeat(newFormat, i, 4);
552 const Section sectionType = (repeat == 4 ? DayOfWeekSectionLong
553 : (repeat == 3 ? DayOfWeekSectionShort : DaySection));
554 const SectionNode sn{sectionType, i - add, repeat};
555 newSectionNodes.append(sn);
558 newDisplay |= sn.type;
562 if (parserType == QMetaType::QDateTime) {
563 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
564 const SectionNode sn{TimeZoneSection, i - add, countRepeat(newFormat, i, 4)};
565 newSectionNodes.append(sn);
568 newDisplay |= TimeZoneSection;
576 if (newSectionNodes.isEmpty() && context == DateTimeEdit)
579 if ((newDisplay & (AmPmSection|Hour12Section)) == Hour12Section) {
580 const int count = newSectionNodes.size();
581 for (
int i = 0; i < count; ++i) {
582 SectionNode &node = newSectionNodes[i];
583 if (node.type == Hour12Section)
584 node.type = Hour24Section;
589 appendSeparator(&newSeparators, newFormat, index, max - index, lastQuote);
591 newSeparators.append(QString());
593 displayFormat = newFormat.toString();
594 separators = newSeparators;
595 sectionNodes = newSectionNodes;
596 display = newDisplay;
603 QDTPDEBUGN(
"separators:\n'%s'", separators.join(
"\n"_L1).toLatin1().constData());
609
610
611
612
614int QDateTimeParser::sectionSize(
int sectionIndex)
const
616 if (sectionIndex < 0)
619 if (sectionIndex >= sectionNodes.size()) {
620 qWarning(
"QDateTimeParser::sectionSize Internal error (%d)", sectionIndex);
624 if (sectionIndex == sectionNodes.size() - 1) {
629 int sizeAdjustment = 0;
630 const int displayTextSize = displayText().size();
631 if (displayTextSize != m_text.size()) {
633 int preceedingZeroesAdded = 0;
634 if (sectionNodes.size() > 1 && context == DateTimeEdit) {
635 const auto begin = sectionNodes.cbegin();
636 const auto end = begin + sectionIndex;
637 for (
auto sectionIt = begin; sectionIt != end; ++sectionIt)
638 preceedingZeroesAdded += sectionIt->zeroesAdded;
640 sizeAdjustment = preceedingZeroesAdded;
643 return displayTextSize + sizeAdjustment - sectionPos(sectionIndex) - separators.last().size();
645 return sectionPos(sectionIndex + 1) - sectionPos(sectionIndex)
646 - separators.at(sectionIndex + 1).size();
651int QDateTimeParser::sectionMaxSize(Section s,
int count)
const
653#if QT_CONFIG(textdate)
654 int mcount = calendar.maximumMonthsInYear();
665 return qMax(getAmPmText(AmText, Case(count)).size(),
666 getAmPmText(PmText, Case(count)).size());
675 case DayOfWeekSectionShort:
676 case DayOfWeekSectionLong:
677#if QT_CONFIG(textdate)
682#if QT_CONFIG(textdate)
688 const QLocale l = locale();
689 const QLocale::FormatType format = count == 4 ? QLocale::LongFormat : QLocale::ShortFormat;
690 for (
int i=1; i<=mcount; ++i) {
691 const QString str = (s == MonthSection
692 ? calendar.monthName(l, i, QCalendar::Unspecified, format)
693 : l.dayName(i, format));
694 ret = qMax(str.size(), ret);
705 case YearSection2Digits:
707 case TimeZoneSection:
709 return std::numeric_limits<
int>::max();
711 case CalendarPopupSection:
713 case TimeSectionMask:
714 case DateSectionMask:
715 case HourSectionMask:
716 case YearSectionMask:
717 case DayOfWeekSectionMask:
719 qWarning(
"QDateTimeParser::sectionMaxSize: Invalid section %s",
720 SectionNode::name(s).toLatin1().constData());
727int QDateTimeParser::sectionMaxSize(
int index)
const
729 const SectionNode &sn = sectionNode(index);
730 return sectionMaxSize(sn.type, sn.count);
741 const auto isSimpleSpace = [](
char32_t ch) {
743 return ch == u' ' || (ch > 127 && QChar::isSpace(ch));
747 if (!text.startsWith(separator)) {
749 QStringIterator given(text), sep(separator);
750 while (sep.hasNext()) {
751 if (!given.hasNext())
753 char32_t s = sep.next(), g = given.next();
754 if (s != g && !(isSimpleSpace(s) && isSimpleSpace(g)))
758 return given.index();
760 return separator.size();
764
765
766
767
768
771QString QDateTimeParser::sectionText(
const QString &text,
int sectionIndex,
int index)
const
773 return text.mid(index, sectionSize(sectionIndex));
776QString QDateTimeParser::sectionText(
int sectionIndex)
const
778 const SectionNode &sn = sectionNode(sectionIndex);
779 return sectionText(displayText(), sectionIndex, sn.pos);
782QDateTimeParser::ParsedSection
783QDateTimeParser::parseSection(
const QDateTime ¤tValue,
int sectionIndex,
int offset)
const
785 ParsedSection result;
786 const SectionNode &sn = sectionNode(sectionIndex);
787 Q_ASSERT_X(!(sn.type & Internal),
788 "QDateTimeParser::parseSection",
"Internal error");
790 const int sectionmaxsize = sectionMaxSize(sectionIndex);
791 const bool negate = (sn.type == YearSection && m_text.size() > offset
792 && calendar.isProleptic() && m_text.at(offset) == u'-');
793 const int negativeYearOffset = negate ? 1 : 0;
795 QStringView sectionTextRef =
796 QStringView { m_text }.mid(offset + negativeYearOffset, sectionmaxsize);
798 QDTPDEBUG <<
"sectionValue for" << sn.name()
799 <<
"with text" << m_text <<
"and (at" << offset
800 <<
") st:" << sectionTextRef;
804 QString sectiontext = sectionTextRef.toString();
806 const int ampm = findAmPm(sectiontext, sectionIndex, &used);
810 result = ParsedSection(Acceptable, ampm, used);
814 result = ParsedSection(Intermediate, ampm - 2, used);
817 result = ParsedSection(Intermediate, 0, used);
820 QDTPDEBUG <<
"invalid because findAmPm(" << sectiontext <<
") returned -1";
823 QDTPDEBUGN(
"This should never happen (findAmPm returned %d)", ampm);
826 if (result.state != Invalid)
827 m_text.replace(offset, used, sectiontext.constData(), used);
829 case TimeZoneSection:
830 result = findTimeZone(sectionTextRef, currentValue,
831 absoluteMax(sectionIndex),
832 absoluteMin(sectionIndex), sn.count);
835 case DayOfWeekSectionShort:
836 case DayOfWeekSectionLong:
838 QString sectiontext = sectionTextRef.toString();
839 int num = 0, used = 0;
840 if (sn.type == MonthSection) {
841 const QDate minDate = getMinimum(currentValue.timeRepresentation()).date();
842 const int year = currentValue.date().year(calendar);
843 const int min = (year == minDate.year(calendar)) ? minDate.month(calendar) : 1;
844 num = findMonth(sectiontext.toLower(), min, sectionIndex, year, §iontext, &used);
846 num = findDay(sectiontext.toLower(), 1, sectionIndex, §iontext, &used);
849 result = ParsedSection(Intermediate, num, used);
851 m_text.replace(offset, used, sectiontext.constData(), used);
852 if (used == sectiontext.size())
853 result = ParsedSection(Acceptable, num, used);
861 case YearSection2Digits:
867 const auto checkSeparator = [&result, field=QStringView{m_text}.sliced(offset),
868 negativeYearOffset, sectionIndex,
this]() {
870 const auto &sep = separators.at(sectionIndex + 1);
871 if (matchesSeparator(field.sliced(negativeYearOffset), sep) != -1)
872 result = ParsedSection(Intermediate, 0, negativeYearOffset);
873 else if (negativeYearOffset && matchesSeparator(field, sep) != -1)
874 result = ParsedSection(Intermediate, 0, 0);
879 int used = negativeYearOffset;
882 if (sectionTextRef.startsWith(u'-')
883 || sectionTextRef.startsWith(u'+')) {
889 QStringView digitsStr = sectionTextRef.left(digitCount(sectionTextRef));
891 if (digitsStr.isEmpty()) {
892 result = ParsedSection(Intermediate, 0, used);
894 const QLocale loc = locale();
895 const int absMax = absoluteMax(sectionIndex);
896 const int absMin = absoluteMin(sectionIndex);
900 for (; digitsStr.size(); digitsStr.chop(1)) {
902 int value =
int(loc.toUInt(digitsStr, &ok));
903 if (!ok || (negate ? -value < absMin : value > absMax))
906 if (sn.type == Hour12Section) {
913 QDTPDEBUG << digitsStr << value << digitsStr.size();
915 used += digitsStr.size();
920 if (!checkSeparator()) {
921 QDTPDEBUG <<
"invalid because" << sectionTextRef <<
"can't become a uint"
927 const FieldInfo fi = fieldInfo(sectionIndex);
928 const bool unfilled = used - negativeYearOffset < sectionmaxsize;
929 if (unfilled && fi & Fraction) {
930 for (
int i = used; i < sectionmaxsize; ++i)
934 Q_ASSERT(negate ? lastVal >= absMin : lastVal <= absMax);
935 if (negate ? lastVal > absMax : lastVal < absMin) {
937 result = ParsedSection(Intermediate, lastVal, used);
939 QDTPDEBUG <<
"invalid because" << lastVal <<
"is greater than absoluteMax"
942 QDTPDEBUG <<
"invalid because" << lastVal <<
"is less than absoluteMin"
946 }
else if (unfilled && (fi & (FixedWidth | Numeric)) == (FixedWidth | Numeric)) {
947 if (skipToNextSection(sectionIndex, currentValue, digitsStr)) {
948 const int missingZeroes = sectionmaxsize - digitsStr.size();
949 result = ParsedSection(Acceptable, lastVal, sectionmaxsize, missingZeroes);
950 m_text.insert(offset, QString(missingZeroes, u'0'));
951 ++(
const_cast<QDateTimeParser*>(
this)->sectionNodes[sectionIndex].zeroesAdded);
953 result = ParsedSection(Intermediate, lastVal, used);
955 }
else if (!lastVal && !calendar.hasYearZero()
956 && (sn.type == YearSection
957 || (sn.type == YearSection2Digits && currentValue.isValid()
958 && currentValue.date().year(calendar) / 100 == 0))) {
960 result = ParsedSection(unfilled ? Acceptable : Invalid, lastVal, used);
962 result = ParsedSection(Acceptable, lastVal, used);
968 qWarning(
"QDateTimeParser::parseSection Internal error (%ls %d)",
969 qUtf16Printable(sn.name()), sectionIndex);
972 Q_ASSERT(result.state != Invalid || result.value == -1);
978
979
980
981
982
983
989 const int maxDay = calendar.daysInMonth(month, year);
990 day = maxDay > 1 ? qBound(1, day, maxDay) : qMax(1, day);
991 day += dayOfWeekDiff(weekDay, calendar.dayOfWeek(QDate(year, month, day, calendar)));
992 return day <= 0 ? day + 7 : maxDay > 0 && day > maxDay ? day - 7 : day;
996
997
998
1001 Q_ASSERT(0 <= y2d && y2d < 100);
1002 const int year = baseYear - baseYear % 100 + y2d;
1003 return year < baseYear ? year + 100 : year;
1007
1008
1009
1010
1011
1012
1014static QDate
actualDate(QDateTimeParser::Sections known, QCalendar calendar,
int baseYear,
1015 int year,
int year2digits,
int month,
int day,
int dayofweek)
1017 QDate actual(year, month, day, calendar);
1018 if (actual.isValid() && year % 100 == year2digits && calendar.dayOfWeek(actual) == dayofweek)
1021 if (dayofweek < 1 || dayofweek > 7)
1022 known &= ~QDateTimeParser::DayOfWeekSectionMask;
1025 if (year % 100 != year2digits) {
1026 if (known & QDateTimeParser::YearSection2Digits) {
1029 known &= ~QDateTimeParser::YearSection;
1031 year2digits = year % 100;
1034 Q_ASSERT(year % 100 == year2digits);
1038 known &= ~QDateTimeParser::MonthSection;
1039 }
else if (month > 12) {
1041 known &= ~QDateTimeParser::MonthSection;
1043 if (!actual.isValid() && !known.testAnyFlag(QDateTimeParser::YearSectionMask)
1044 && known.testFlags(QDateTimeParser::DaySection | QDateTimeParser::MonthSection)
1045 && !calendar.isLeapYear(year) && day > calendar.daysInMonth(month, year)) {
1047 int leap = year + 1, stop = year + 47;
1050 while (!calendar.isLeapYear(leap) && leap < stop)
1052 if (day <= calendar.daysInMonth(month, leap))
1056 QDate first(year, month, 1, calendar);
1057 int last = known & QDateTimeParser::MonthSection
1058 ? (known.testAnyFlag(QDateTimeParser::YearSectionMask)
1059 ? calendar.daysInMonth(month, year) : calendar.daysInMonth(month))
1062 const bool fixDayOfWeek = last && known & QDateTimeParser::YearSection
1063 && known & QDateTimeParser::DayOfWeekSectionMask;
1066 const int diff = (dayofweek - calendar.dayOfWeek(first) - last) % 7;
1067 Q_ASSERT(diff <= 0);
1072 day = 1 + dayofweek - calendar.dayOfWeek(first);
1078 known &= ~QDateTimeParser::DaySection;
1079 }
else if (day > calendar.maximumDaysInMonth()) {
1081 known &= ~QDateTimeParser::DaySection;
1082 }
else if (last && day > last && (known & QDateTimeParser::DaySection) == 0) {
1086 actual = QDate(year, month, day, calendar);
1087 if (!actual.isValid()
1088 || (known & QDateTimeParser::DaySection
1089 && known & QDateTimeParser::MonthSection
1090 && known & QDateTimeParser::YearSection)
1091 || calendar.dayOfWeek(actual) == dayofweek
1092 || (known & QDateTimeParser::DayOfWeekSectionMask) == 0) {
1097
1098
1099
1100
1101
1102
1104 if ((known & QDateTimeParser::DaySection) == 0) {
1106 day = weekDayWithinMonth(calendar, year, month, day, dayofweek);
1107 actual = QDate(year, month, day, calendar);
1111 if ((known & QDateTimeParser::MonthSection) == 0) {
1113
1114
1115
1116
1117 for (
int m = 1; m < 12; m++) {
1119 actual = QDate(year, month - m, day, calendar);
1120 if (calendar.dayOfWeek(actual) == dayofweek)
1123 if (m + month <= 12) {
1124 actual = QDate(year, month + m, day, calendar);
1125 if (calendar.dayOfWeek(actual) == dayofweek)
1130 actual = QDate(year, month, day, calendar);
1133 if ((known & QDateTimeParser::YearSection) == 0) {
1134 if (known & QDateTimeParser::YearSection2Digits) {
1135 actual = calendar.matchCenturyToWeekday({year, month, day}, dayofweek);
1136 if (actual.isValid()) {
1137 Q_ASSERT(calendar.dayOfWeek(actual) == dayofweek);
1142 for (
int y = 1; y < 12; y++) {
1143 actual = QDate(year - y, month, day, calendar);
1144 if (calendar.dayOfWeek(actual) == dayofweek)
1146 actual = QDate(year + y, month, day, calendar);
1147 if (calendar.dayOfWeek(actual) == dayofweek)
1151 actual = QDate(year, month, day, calendar);
1158
1159
1162 int hour,
int hour12,
int ampm,
1163 int minute,
int second,
int msec)
1166 QTime actual(hour, minute, second, msec);
1167 if (hour12 < 0 || hour12 > 12) {
1168 known &= ~QDateTimeParser::Hour12Section;
1172 if (ampm == -1 || (known & QDateTimeParser::AmPmSection) == 0) {
1173 if ((known & QDateTimeParser::Hour12Section) == 0 || hour % 12 == hour12)
1176 if ((known & QDateTimeParser::Hour24Section) == 0)
1177 hour = hour12 + (hour > 12 ? 12 : 0);
1179 Q_ASSERT(ampm == 0 || ampm == 1);
1180 if (hour - hour12 == ampm * 12)
1183 if ((known & QDateTimeParser::Hour24Section) == 0
1184 && known & QDateTimeParser::Hour12Section) {
1185 hour = hour12 + ampm * 12;
1188 actual = QTime(hour, minute, second, msec);
1193
1194
1198 qsizetype longest = 0;
1200 for (
int i = 0; i < 2; ++i) {
1201 const QString zone(qTzName(i));
1202 if (zone.size() > longest && name.startsWith(zone))
1203 longest = zone.size();
1206 const auto consider = [name, &longest](QStringView zone) {
1207 if (name.startsWith(zone)) {
1209 if (9 > longest && zone.size() == 6 && zone.startsWith(
"UTC"_L1)
1210 && name.sliced(6, 3) ==
":00"_L1) {
1212 }
else if (zone.size() > longest) {
1213 longest = zone.size();
1217#if QT_CONFIG(timezone)
1219
1220
1222 const auto localWhen = QDateTime(when.date(), when.time());
1223 consider(localWhen.timeRepresentation().displayName(
1224 localWhen, QTimeZone::ShortName, locale));
1229 consider(QDateTime(when.date(), when.time()).timeZoneAbbreviation());
1230 Q_ASSERT(longest <= INT_MAX);
1231 return int(longest);
1234#if QT_CONFIG(timezone)
1235static auto findZoneByLongName(QStringView str,
const QLocale &locale,
const QDateTime &when)
1240 qsizetype nameLength = 0;
1241 bool isValid()
const {
return nameLength > 0 && zone.isValid(); }
1243 auto pfx = QTimeZonePrivate::findLongNamePrefix(str, locale, when.toMSecsSinceEpoch());
1245 pfx = QTimeZonePrivate::findLongNamePrefix(str, locale);
1249 pfx = QTimeZonePrivate::findNarrowOffsetPrefix(str, locale);
1251 pfx = QTimeZonePrivate::findLongUtcPrefix(str);
1253 result = R{ QTimeZone(pfx.ianaId), pfx.nameLength };
1254 Q_ASSERT(result.zone.isValid());
1262
1263
1264QDateTimeParser::StateNode
1265QDateTimeParser::scanString(
const QDateTime &defaultValue,
bool fixup)
const
1267 State state = Acceptable;
1268 bool conflicts =
false;
1269 const int sectionNodesCount = sectionNodes.size();
1272 int year, month, day;
1273 const QDate defaultDate = defaultValue.date();
1274 const QTime defaultTime = defaultValue.time();
1275 defaultDate.getDate(&year, &month, &day);
1276 int year2digits = year % 100;
1277 int hour = defaultTime.hour();
1279 int minute = defaultTime.minute();
1280 int second = defaultTime.second();
1281 int msec = defaultTime.msec();
1282 int dayofweek = calendar.dayOfWeek(defaultDate);
1283 QTimeZone timeZone = defaultValue.timeRepresentation();
1286 Sections isSet = NoSection;
1288 for (
int index = 0; index < sectionNodesCount; ++index) {
1289 Q_ASSERT(state != Invalid);
1290 const QString &separator = separators.at(index);
1291 int step = matchesSeparator(QStringView{m_text}.sliced(pos), separator);
1293 QDTPDEBUG <<
"invalid because" << QStringView{m_text}.sliced(pos)
1294 <<
"does not start with" << separator
1295 << index << pos << currentSectionIndex;
1299 sectionNodes[index].pos = pos;
1300 int *current =
nullptr;
1302 const SectionNode sn = sectionNodes.at(index);
1303 const QDateTime usedDateTime = [&] {
1304 const QDate date = actualDate(isSet, calendar, defaultCenturyStart,
1305 year, year2digits, month, day, dayofweek);
1306 const QTime time = actualTime(isSet, hour, hour12, ampm, minute, second, msec);
1307 return QDateTime(date, time, timeZone);
1309 ParsedSection sect = parseSection(usedDateTime, index, pos);
1311 QDTPDEBUG <<
"sectionValue" << sn.name() << m_text
1312 <<
"pos" << pos <<
"used" << sect.used << stateName(sect.state);
1314 padding += sect.zeroes;
1315 if (fixup && sect.state == Intermediate && sect.used < sn.count) {
1316 const FieldInfo fi = fieldInfo(index);
1317 if ((fi & (Numeric|FixedWidth)) == (Numeric|FixedWidth)) {
1318 const QString newText = QString::asprintf(
"%0*d", sn.count, sect.value);
1319 m_text.replace(pos, sect.used, newText);
1320 sect.used = sn.count;
1324 state = qMin<State>(state, sect.state);
1326 if (state == Invalid || (context == FromString && (state == Intermediate || sect.zeroes)))
1330 case TimeZoneSection:
1331 current = &zoneOffset;
1332 if (sect.used > 0) {
1334 QStringView zoneName = QStringView{m_text}.sliced(pos, sect.used);
1335 Q_ASSERT(!zoneName.isEmpty());
1337 const QStringView offsetStr
1338 = zoneName.startsWith(
"UTC"_L1) ? zoneName.sliced(3) : zoneName;
1339 const bool isUtcOffset = offsetStr.startsWith(u'+') || offsetStr.startsWith(u'-');
1340 const bool isUtc = zoneName ==
"Z"_L1 || zoneName ==
"UTC"_L1;
1342 if (isUtc || isUtcOffset) {
1343 timeZone = QTimeZone::fromSecondsAheadOfUtc(sect.value);
1344#if QT_CONFIG(timezone)
1345 }
else if (startsWithLocalTimeZone(zoneName, usedDateTime, locale()) != sect.used) {
1346 if (QTimeZone namedZone = QTimeZone(zoneName.toLatin1()); namedZone.isValid()) {
1347 timeZone = namedZone;
1349 auto found = findZoneByLongName(zoneName, locale(), usedDateTime);
1350 Q_ASSERT(found.isValid());
1351 Q_ASSERT(found.nameLength == zoneName.length());
1352 timeZone = found.zone;
1356 timeZone = QTimeZone::LocalTime;
1360 case Hour24Section: current = &hour;
break;
1361 case Hour12Section: current = &hour12;
break;
1362 case MinuteSection: current = &minute;
break;
1363 case SecondSection: current = &second;
break;
1364 case MSecSection: current = &msec;
break;
1365 case YearSection: current = &year;
break;
1366 case YearSection2Digits: current = &year2digits;
break;
1367 case MonthSection: current = &month;
break;
1368 case DayOfWeekSectionShort:
1369 case DayOfWeekSectionLong: current = &dayofweek;
break;
1370 case DaySection: current = &day; sect.value = qMax<
int>(1, sect.value);
break;
1371 case AmPmSection: current = &m;
break;
1373 qWarning(
"QDateTimeParser::parse Internal error (%ls)",
1374 qUtf16Printable(sn.name()));
1378 Q_ASSERT(sect.state != Invalid);
1382 QDTPDEBUG << index << sn.name() <<
"is set to"
1383 << pos <<
"state is" << stateName(state);
1385 if (isSet & sn.type && *current != sect.value) {
1386 QDTPDEBUG <<
"CONFLICT " << sn.name() << *current << sect.value;
1388 if (index != currentSectionIndex)
1391 *current = sect.value;
1397 int step = matchesSeparator(QStringView{m_text}.sliced(pos), separators.last());
1398 if (step == -1 || step + pos < m_text.size()) {
1399 QDTPDEBUG <<
"invalid because" << QStringView{m_text}.sliced(pos)
1400 <<
"does not match" << separators.last() << pos;
1404 if (parserType != QMetaType::QTime) {
1405 if (year % 100 != year2digits && (isSet & YearSection2Digits)) {
1406 const QDate date = actualDate(isSet, calendar, defaultCenturyStart,
1407 year, year2digits, month, day, dayofweek);
1408 if (!date.isValid()) {
1410 }
else if (!(isSet & YearSection)) {
1411 year = date.year(calendar);
1414 const SectionNode &sn = sectionNode(currentSectionIndex);
1415 if (sn.type == YearSection2Digits)
1416 year = date.year(calendar);
1420 const auto fieldType = sectionType(currentSectionIndex);
1421 const QDate date(year, month, day, calendar);
1422 if ((!date.isValid() || dayofweek != calendar.dayOfWeek(date))
1423 && state == Acceptable && isSet & DayOfWeekSectionMask) {
1424 if (isSet & DaySection)
1428 if (currentSectionIndex == -1 || fieldType & DayOfWeekSectionMask
1429 || (!conflicts && (fieldType & (YearSectionMask | MonthSection)))) {
1430 day = weekDayWithinMonth(calendar, year, month, day, dayofweek);
1431 QDTPDEBUG << year << month << day << dayofweek
1432 << calendar.dayOfWeek(QDate(year, month, day, calendar));
1436 bool needfixday =
false;
1437 if (fieldType & DaySectionMask) {
1439 }
else if (cachedDay > day && !(isSet & DayOfWeekSectionMask && state == Acceptable)) {
1444 if (!calendar.isDateValid(year, month, day)) {
1445 if (day <= calendar.maximumDaysInMonth())
1447 if (day > calendar.minimumDaysInMonth() && calendar.isDateValid(year, month, 1))
1451 if (context == FromString)
1453 if (state == Acceptable && fixday) {
1454 day = qMin<
int>(day, calendar.daysInMonth(month, year));
1456 const QLocale loc = locale();
1457 for (
int i=0; i<sectionNodesCount; ++i) {
1458 const SectionNode sn = sectionNode(i);
1459 if (sn.type & DaySection) {
1460 m_text.replace(sectionPos(sn), sectionSize(i), loc.toString(day));
1461 }
else if (sn.type & DayOfWeekSectionMask) {
1462 const int dayOfWeek = calendar.dayOfWeek(QDate(year, month, day, calendar));
1463 const QLocale::FormatType dayFormat =
1464 (sn.type == DayOfWeekSectionShort
1465 ? QLocale::ShortFormat : QLocale::LongFormat);
1466 const QString dayName(loc.dayName(dayOfWeek, dayFormat));
1467 m_text.replace(sectionPos(sn), sectionSize(i), dayName);
1470 }
else if (state > Intermediate) {
1471 state = Intermediate;
1476 if (parserType != QMetaType::QDate) {
1477 if (isSet & Hour12Section) {
1478 const bool hasHour = isSet.testAnyFlag(Hour24Section);
1480 ampm = !hasHour || hour < 12 ? 0 : 1;
1481 hour12 = hour12 % 12 + ampm * 12;
1484 else if (hour != hour12)
1486 }
else if (ampm != -1) {
1487 if (!(isSet & (Hour24Section)))
1489 else if ((ampm == 0) != (hour < 12))
1494 QDTPDEBUG << year << month << day << hour << minute << second << msec;
1495 Q_ASSERT(state != Invalid);
1497 const QDate date(year, month, day, calendar);
1498 const QTime time(hour, minute, second, msec);
1499 const QDateTime when = QDateTime(date, time, timeZone);
1501 if (when.time() != time || when.date() != date) {
1507 if (!(isSet & HourSectionMask)) {
1508 switch (parserType) {
1509 case QMetaType::QDateTime: {
1510 qint64 msecs = when.toMSecsSinceEpoch();
1512 const QDateTime replace = QDateTime::fromMSecsSinceEpoch(msecs, timeZone);
1513 const QTime tick = replace.time();
1514 if (replace.date() == date
1515 && (!(isSet & MinuteSection) || tick.minute() == minute)
1516 && (!(isSet & SecondSection) || tick.second() == second)
1517 && (!(isSet & MSecSection) || tick.msec() == msec)) {
1518 return StateNode(replace, state, padding, conflicts);
1521 case QMetaType::QDate:
1523 return StateNode(date.startOfDay(QTimeZone::UTC),
1524 state, padding, conflicts);
1526 case QMetaType::QTime:
1528 return StateNode(QDateTime(date, time, QTimeZone::UTC),
1529 state, padding, conflicts);
1531 Q_UNREACHABLE_RETURN(StateNode());
1533 }
else if (state > Intermediate) {
1534 state = Intermediate;
1538 return StateNode(when, state, padding, conflicts);
1542
1543
1545QDateTimeParser::StateNode
1546QDateTimeParser::parse(
const QString &input,
int position,
1547 const QDateTime &defaultValue,
bool fixup)
const
1549 const QDateTime minimum = getMinimum(defaultValue.timeRepresentation());
1550 const QDateTime maximum = getMaximum(defaultValue.timeRepresentation());
1554 StateNode scan = scanString(defaultValue, fixup);
1555 QDTPDEBUGN(
"'%s' => '%s'(%s)", m_text.toLatin1().constData(),
1556 scan.value.toString(
"yyyy/MM/dd hh:mm:ss.zzz"_L1).toLatin1().constData(),
1557 stateName(scan.state).toLatin1().constData());
1559 if (scan.value.isValid() && scan.state != Invalid) {
1560 if (context != FromString && scan.value < minimum) {
1561 const QLatin1Char space(
' ');
1562 if (scan.value >= minimum)
1563 qWarning(
"QDateTimeParser::parse Internal error 3 (%ls %ls)",
1564 qUtf16Printable(scan.value.toString()), qUtf16Printable(minimum.toString()));
1567 scan.state = Invalid;
1568 const int sectionNodesCount = sectionNodes.size();
1569 for (
int i=0; i<sectionNodesCount && !done; ++i) {
1570 const SectionNode &sn = sectionNodes.at(i);
1571 QString t = sectionText(m_text, i, sn.pos).toLower();
1572 if ((t.size() < sectionMaxSize(i)
1573 && ((fieldInfo(i) & (FixedWidth|Numeric)) != Numeric))
1574 || t.contains(space)) {
1577 switch (findAmPm(t, i)) {
1580 scan.state = Acceptable;
1584 scan.state = Invalid;
1589 case PossibleBoth: {
1590 const QDateTime copy(scan.value.addSecs(12 * 60 * 60));
1591 if (copy >= minimum && copy <= maximum) {
1592 scan.state = Intermediate;
1599 if (sn.count >= 3) {
1600 const QDate when = scan.value.date();
1601 const int finalMonth = when.month(calendar);
1602 int tmp = finalMonth;
1604 while ((tmp = findMonth(t, tmp + 1, i, when.year(calendar))) != -1) {
1605 const QDateTime copy(scan.value.addMonths(tmp - finalMonth));
1606 if (copy >= minimum && copy <= maximum)
1610 scan.state = Intermediate;
1620 if (sn.type & TimeSectionMask) {
1621 if (scan.value.daysTo(minimum) != 0)
1624 const QTime time = scan.value.time();
1625 toMin = time.msecsTo(minimum.time());
1626 if (scan.value.daysTo(maximum) > 0)
1629 toMax = time.msecsTo(maximum.time());
1631 toMin = scan.value.daysTo(minimum);
1632 toMax = scan.value.daysTo(maximum);
1634 const int maxChange = sn.maxChange();
1635 if (toMin > maxChange) {
1636 QDTPDEBUG <<
"invalid because toMin > maxChange" << toMin
1637 << maxChange << t << scan.value << minimum;
1638 scan.state = Invalid;
1641 }
else if (toMax > maxChange) {
1645 const int min = getDigit(minimum, i);
1647 qWarning(
"QDateTimeParser::parse Internal error 4 (%ls)",
1648 qUtf16Printable(sn.name()));
1649 scan.state = Invalid;
1654 int max = toMax != -1 ? getDigit(maximum, i) : absoluteMax(i, scan.value);
1655 int pos = position + scan.padded - sn.pos;
1656 if (pos < 0 || pos >= t.size())
1658 if (!potentialValue(t.simplified(), min, max, i, scan.value, pos)) {
1659 QDTPDEBUG <<
"invalid because potentialValue(" << t.simplified() << min << max
1660 << sn.name() <<
"returned" << toMax << toMin << pos;
1661 scan.state = Invalid;
1665 scan.state = Intermediate;
1672 if (scan.value > maximum)
1673 scan.state = Invalid;
1675 QDTPDEBUG <<
"not checking intermediate because scanned value is"
1676 << scan.value << minimum << maximum;
1681 Q_ASSERT(scan.value.isValid() || scan.state != Acceptable);
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697static int findTextEntry(QStringView text,
const ShortVector<QString> &entries, QString *usedText,
int *used)
1704 for (
int n = 0; n < entries.size(); ++n)
1706 const QString &name = entries.at(n);
1708 const int limit = qMin(text.size(), name.size());
1710 while (i < limit && text.at(i) == name.at(i).toLower())
1713 if (i > bestCount || (i == bestCount && i == name.size())) {
1716 if (i == name.size() && i == text.size())
1720 if (usedText && bestMatch != -1)
1721 *usedText = entries.at(bestMatch);
1729
1730
1731
1732
1734int QDateTimeParser::findMonth(QStringView str,
int startMonth,
int sectionIndex,
1735 int year, QString *usedMonth,
int *used)
const
1737 const SectionNode &sn = sectionNode(sectionIndex);
1738 if (sn.type != MonthSection) {
1739 qWarning(
"QDateTimeParser::findMonth Internal error");
1743 QLocale::FormatType type = sn.count == 3 ? QLocale::ShortFormat : QLocale::LongFormat;
1744 QLocale l = locale();
1745 ShortVector<QString> monthNames;
1746 monthNames.reserve(13 - startMonth);
1747 for (
int month = startMonth; month <= 12; ++month)
1748 monthNames.append(calendar.monthName(l, month, year, type));
1750 const int index = findTextEntry(str, monthNames, usedMonth, used);
1751 return index < 0 ? index : index + startMonth;
1754int QDateTimeParser::findDay(QStringView str,
int startDay,
int sectionIndex, QString *usedDay,
int *used)
const
1756 const SectionNode &sn = sectionNode(sectionIndex);
1757 if (!(sn.type & DaySectionMask)) {
1758 qWarning(
"QDateTimeParser::findDay Internal error");
1762 QLocale::FormatType type = sn.count == 4 ? QLocale::LongFormat : QLocale::ShortFormat;
1763 QLocale l = locale();
1764 ShortVector<QString> daysOfWeek;
1765 daysOfWeek.reserve(8 - startDay);
1766 for (
int day = startDay; day <= 7; ++day)
1767 daysOfWeek.append(l.dayName(day, type));
1769 const int index = findTextEntry(str, daysOfWeek, usedDay, used);
1770 return index < 0 ? index : index + startDay;
1774
1775
1776
1777
1778
1779
1780
1781QDateTimeParser::ParsedSection QDateTimeParser::findUtcOffset(QStringView str,
int mode)
const
1783 Q_ASSERT(mode > 0 && mode < 4);
1784 const bool startsWithUtc = str.startsWith(
"UTC"_L1);
1786 if (startsWithUtc) {
1788 return ParsedSection();
1789 str = str.sliced(3);
1791 return ParsedSection(Acceptable, 0, 3);
1794 const bool negativeSign = str.startsWith(u'-');
1796 if (!negativeSign && !str.startsWith(u'+'))
1797 return ParsedSection();
1798 str = str.sliced(1);
1800 const int colonPosition = str.indexOf(u':');
1802 bool hasColon = (colonPosition >= 0 && colonPosition < 3);
1805 const int digits = hasColon ? colonPosition + 3 : 4;
1807 for (
const int offsetLength = qMin(qsizetype(digits), str.size()); i < offsetLength; ++i) {
1808 if (i != colonPosition && !str.at(i).isDigit())
1811 const int hoursLength = qMin(i, hasColon ? colonPosition : 2);
1812 if (hoursLength < 1)
1813 return ParsedSection();
1817 if (!startsWithUtc && hoursLength != 2)
1818 return ParsedSection();
1822 if (mode == (hasColon ? 2 : 3))
1823 return ParsedSection();
1827 const int hours = str.first(hoursLength).toInt(&isInt);
1829 return ParsedSection();
1830 const QStringView minutesStr = str.mid(hasColon ? colonPosition + 1 : 2, 2);
1831 const int minutes = minutesStr.isEmpty() ? 0 : minutesStr.toInt(&isInt);
1833 return ParsedSection();
1838 const State status = (hours > 14 || minutes >= 60) ? Invalid
1839 : (hours == 14 && minutes > 0) ? Intermediate : Acceptable;
1841 int offset = 3600 * hours + 60 * minutes;
1846 const int usedSymbols = (startsWithUtc ? 3 : 0) + 1 + hoursLength + (hasColon ? 1 : 0)
1847 + minutesStr.size();
1849 return ParsedSection(status, offset, usedSymbols);
1853
1854
1855
1856
1857
1858
1859QDateTimeParser::ParsedSection
1860QDateTimeParser::findTimeZoneName(QStringView str,
const QDateTime &when)
const
1862 const int systemLength = startsWithLocalTimeZone(str, when, locale());
1863#if QT_CONFIG(timezone)
1866 const auto invalidZoneNameCharacter = [] (
const QChar &c) {
1867 const auto cu = c.unicode();
1868 return cu >= 127u || !(memchr(
"+-./:_",
char(cu), 6) || c.isLetterOrNumber());
1870 int index = std::distance(str.cbegin(),
1871 std::find_if(str.cbegin(), str.cend(), invalidZoneNameCharacter));
1878 Q_ASSERT(index <= str.size());
1879 while (lastSlash < index) {
1880 int slash = str.indexOf(u'/', lastSlash + 1);
1881 if (slash < 0 || slash > index)
1883 else if (++count > 5)
1885 if (slash - lastSlash > 20)
1886 index = lastSlash + 20;
1892 for (QStringView copy = str; index > systemLength; --index) {
1893 copy.truncate(index);
1894 QTimeZone zone(copy.toLatin1());
1896 return ParsedSection(Acceptable, zone.offsetFromUtc(when), index);
1900 if (
auto found = findZoneByLongName(str, locale(), when); found.isValid())
1901 return ParsedSection(Acceptable, found.zone.offsetFromUtc(when), found.nameLength);
1903 if (systemLength > 0)
1904 return ParsedSection(Acceptable, when.toLocalTime().offsetFromUtc(), systemLength);
1905 return ParsedSection();
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920QDateTimeParser::ParsedSection
1921QDateTimeParser::findTimeZone(QStringView str,
const QDateTime &when,
1922 int maxVal,
int minVal,
int mode)
const
1924 Q_ASSERT(mode > 0 && mode <= 4);
1926 if (mode == 1 && str == u'Z')
1927 return ParsedSection(Acceptable, 0, 1);
1929 ParsedSection section;
1931 section = findUtcOffset(str, mode);
1932 if (mode != 2 && mode != 3 && section.used <= 0)
1933 section = findTimeZoneName(str, when);
1935 if (section.state == Acceptable && (section.value < minVal || section.value > maxVal))
1936 section.state = Intermediate;
1937 if (section.used > 0)
1942 if (str.startsWith(
"UTC"_L1))
1943 return ParsedSection(Acceptable, 0, 3);
1944 if (str.startsWith(u'Z'))
1945 return ParsedSection(Acceptable, 0, 1);
1948 return ParsedSection();
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965QDateTimeParser::AmPmFinder QDateTimeParser::findAmPm(QString &str,
int sectionIndex,
int *used)
const
1967 const SectionNode &s = sectionNode(sectionIndex);
1968 if (s.type != AmPmSection) {
1969 qWarning(
"QDateTimeParser::findAmPm Internal error");
1974 if (QStringView(str).trimmed().isEmpty())
1975 return PossibleBoth;
1977 const QLatin1Char space(
' ');
1978 int size = sectionMaxSize(sectionIndex);
1985 ampm[amindex] = getAmPmText(AmText, Case(s.count));
1986 ampm[pmindex] = getAmPmText(PmText, Case(s.count));
1987 for (
int i = 0; i < 2; ++i)
1988 ampm[i].truncate(size);
1990 QDTPDEBUG <<
"findAmPm" << str << ampm[0] << ampm[1];
1992 if (str.startsWith(ampm[amindex], Qt::CaseInsensitive)) {
1993 str = ampm[amindex];
1995 }
else if (str.startsWith(ampm[pmindex], Qt::CaseInsensitive)) {
1996 str = ampm[pmindex];
1998 }
else if (context == FromString || (str.count(space) == 0 && str.size() >= size)) {
2001 size = qMin(size, str.size());
2003 bool broken[2] = {
false,
false};
2004 for (
int i=0; i<size; ++i) {
2005 const QChar ch = str.at(i);
2007 for (
int j=0; j<2; ++j) {
2009 int index = ampm[j].indexOf(ch);
2011 <<
"in" << ampm[j] <<
"and got" << index;
2013 if (ch.category() == QChar::Letter_Uppercase) {
2014 index = ampm[j].indexOf(ch.toLower());
2015 QDTPDEBUG <<
"trying with" << ch.toLower()
2016 <<
"in" << ampm[j] <<
"and got" << index;
2017 }
else if (ch.category() == QChar::Letter_Lowercase) {
2018 index = ampm[j].indexOf(ch.toUpper());
2019 QDTPDEBUG <<
"trying with" << ch.toUpper()
2020 <<
"in" << ampm[j] <<
"and got" << index;
2024 if (broken[amindex] && broken[pmindex]) {
2030 str[i] = ampm[j].at(index);
2033 ampm[j].remove(index, 1);
2038 if (!broken[pmindex] && !broken[amindex])
2039 return PossibleBoth;
2040 return (!broken[amindex] ? PossibleAM : PossiblePM);
2044
2045
2046
2048int QDateTimeParser::SectionNode::maxChange()
const
2052 case MSecSection:
return 999;
2053 case SecondSection:
return 59 * 1000;
2054 case MinuteSection:
return 59 * 60 * 1000;
2055 case Hour24Section:
case Hour12Section:
return 59 * 60 * 60 * 1000;
2058 case DayOfWeekSectionShort:
2059 case DayOfWeekSectionLong:
return 7;
2060 case DaySection:
return 30;
2061 case MonthSection:
return 365 - 31;
2062 case YearSection:
return 9999 * 365;
2063 case YearSection2Digits:
return 100 * 365;
2065 qWarning(
"QDateTimeParser::maxChange() Internal error (%ls)",
2066 qUtf16Printable(name()));
2072QDateTimeParser::FieldInfo QDateTimeParser::fieldInfo(
int index)
const
2075 const SectionNode &sn = sectionNode(index);
2084 case YearSection2Digits:
2085 ret |= AllowPartial;
2099 ret |= (Numeric|AllowPartial);
2103 case DayOfWeekSectionShort:
2104 case DayOfWeekSectionLong:
2110 if (getAmPmText(AmText, Case(sn.count)).size()
2111 == getAmPmText(PmText, Case(sn.count)).size()) {
2116 case TimeZoneSection:
2119 qWarning(
"QDateTimeParser::fieldInfo Internal error 2 (%d %ls %d)",
2120 index, qUtf16Printable(sn.name()), sn.count);
2126QString QDateTimeParser::SectionNode::format()
const
2130 case AmPmSection:
return count == 1 ?
"ap"_L1 : count == 2 ?
"AP"_L1 :
"Ap"_L1;
2131 case MSecSection: fillChar = u'z';
break;
2132 case SecondSection: fillChar = u's';
break;
2133 case MinuteSection: fillChar = u'm';
break;
2134 case Hour24Section: fillChar = u'H';
break;
2135 case Hour12Section: fillChar = u'h';
break;
2136 case DayOfWeekSectionShort:
2137 case DayOfWeekSectionLong:
2138 case DaySection: fillChar = u'd';
break;
2139 case MonthSection: fillChar = u'M';
break;
2140 case YearSection2Digits:
2141 case YearSection: fillChar = u'y';
break;
2143 qWarning(
"QDateTimeParser::sectionFormat Internal error (%ls)",
2144 qUtf16Printable(name(type)));
2147 if (fillChar.isNull()) {
2148 qWarning(
"QDateTimeParser::sectionFormat Internal error 2");
2151 return QString(count, fillChar);
2156
2157
2158
2159
2160
2162bool QDateTimeParser::potentialValue(QStringView str,
int min,
int max,
int index,
2163 const QDateTime ¤tValue,
int insert)
const
2168 const int size = sectionMaxSize(index);
2169 int val = (
int)locale().toUInt(str);
2170 const SectionNode &sn = sectionNode(index);
2171 if (sn.type == YearSection2Digits) {
2172 const int year = currentValue.date().year(calendar);
2173 val += year - (year % 100);
2175 if (val >= min && val <= max && str.size() == size)
2177 if (val > max || (str.size() == size && val < min))
2180 const int len = size - str.size();
2181 for (
int i=0; i<len; ++i) {
2182 for (
int j=0; j<10; ++j) {
2183 if (potentialValue(str + QLatin1Char(
'0' + j), min, max, index, currentValue, insert)) {
2185 }
else if (insert >= 0) {
2186 const QString tmp = str.left(insert) + QLatin1Char(
'0' + j) + str.mid(insert);
2187 if (potentialValue(tmp, min, max, index, currentValue, insert))
2197
2198
2199bool QDateTimeParser::skipToNextSection(
int index,
const QDateTime ¤t, QStringView text)
const
2201 Q_ASSERT(text.size() < sectionMaxSize(index));
2202 const SectionNode &node = sectionNode(index);
2203 int min = absoluteMin(index);
2204 int max = absoluteMax(index, current);
2206 if (node.type != TimeZoneSection || current.timeSpec() == Qt::OffsetFromUTC) {
2207 const QDateTime maximum = getMaximum(current.timeRepresentation());
2208 const QDateTime minimum = getMinimum(current.timeRepresentation());
2214 QDateTime tmp = current;
2215 if (!setDigit(tmp, index, min) || tmp < minimum)
2216 min = getDigit(minimum, index);
2218 if (!setDigit(tmp, index, max) || tmp > maximum)
2219 max = getDigit(maximum, index);
2221 int pos = cursorPosition() - node.pos;
2222 if (pos < 0 || pos >= text.size())
2226
2227
2228
2229
2230
2231 return !potentialValue(text, min, max, index, current, pos);
2235
2236
2237
2239QString QDateTimeParser::SectionNode::name(QDateTimeParser::Section s)
2242 case AmPmSection:
return "AmPmSection"_L1;
2243 case DaySection:
return "DaySection"_L1;
2244 case DayOfWeekSectionShort:
return "DayOfWeekSectionShort"_L1;
2245 case DayOfWeekSectionLong:
return "DayOfWeekSectionLong"_L1;
2246 case Hour24Section:
return "Hour24Section"_L1;
2247 case Hour12Section:
return "Hour12Section"_L1;
2248 case MSecSection:
return "MSecSection"_L1;
2249 case MinuteSection:
return "MinuteSection"_L1;
2250 case MonthSection:
return "MonthSection"_L1;
2251 case SecondSection:
return "SecondSection"_L1;
2252 case TimeZoneSection:
return "TimeZoneSection"_L1;
2253 case YearSection:
return "YearSection"_L1;
2254 case YearSection2Digits:
return "YearSection2Digits"_L1;
2255 case NoSection:
return "NoSection"_L1;
2256 case FirstSection:
return "FirstSection"_L1;
2257 case LastSection:
return "LastSection"_L1;
2258 default:
return "Unknown section "_L1 + QString::number(
int(s));
2263
2264
2265
2267QString QDateTimeParser::stateName(State s)
const
2270 case Invalid:
return "Invalid"_L1;
2271 case Intermediate:
return "Intermediate"_L1;
2272 case Acceptable:
return "Acceptable"_L1;
2273 default:
return "Unknown state "_L1 + QString::number(s);
2279
2280
2281
2282QDateTime QDateTimeParser::baseDate(
const QTimeZone &zone)
const
2284 QDateTime when = QDate(defaultCenturyStart, 1, 1).startOfDay(zone);
2285 if (
const QDateTime start = getMinimum(zone); when < start)
2287 if (
const QDateTime end = getMaximum(zone); when > end)
2293bool QDateTimeParser::fromString(
const QString &t, QDate *date, QTime *time,
int baseYear)
const
2295 defaultCenturyStart = baseYear;
2296 const StateNode tmp = parse(t, -1, baseDate(QTimeZone::UTC),
false);
2297 if (tmp.state != Acceptable || tmp.conflicts)
2302 const QTime t = tmp.value.time();
2310 const QDate d = tmp.value.date();
2319bool QDateTimeParser::fromString(
const QString &t, QDateTime *datetime,
int baseYear)
const
2321 defaultCenturyStart = baseYear;
2322 const StateNode tmp = parse(t, -1, baseDate(QTimeZone::LocalTime),
false);
2324 *datetime = tmp.value;
2325 return tmp.state >= Intermediate && !tmp.conflicts && tmp.value.isValid();
2328QDateTime QDateTimeParser::getMinimum(
const QTimeZone &zone)
const
2335 static const QDateTime localTimeMin(QDATETIMEEDIT_DATE_MIN.startOfDay());
2336 static const QDateTime utcTimeMin = localTimeMin.toUTC();
2337 switch (zone.timeSpec()) {
2339 return localTimeMin;
2342 case Qt::OffsetFromUTC:
2346 return utcTimeMin.toTimeZone(zone);
2349QDateTime QDateTimeParser::getMaximum(
const QTimeZone &zone)
const
2356 static const QDateTime localTimeMax(QDATETIMEEDIT_DATE_MAX.endOfDay());
2357 static const QDateTime utcTimeMax(QDATETIMEEDIT_DATE_MAX.endOfDay(QTimeZone::UTC));
2358 switch (zone.timeSpec()) {
2360 return localTimeMax;
2363 case Qt::OffsetFromUTC:
2367 return utcTimeMax.toTimeZone(zone);
2370QString QDateTimeParser::getAmPmText(AmPm ap, Case cs)
const
2372 const QLocale loc = locale();
2373 QString raw = ap == AmText ? loc.amText() : loc.pmText();
2376 case UpperCase:
return std::move(raw).toUpper();
2377 case LowerCase:
return std::move(raw).toLower();
2378 case NativeCase:
return raw;
2380 Q_UNREACHABLE_RETURN(raw);
2384
2385
2387bool operator==(QDateTimeParser::SectionNode s1, QDateTimeParser::SectionNode s2)
2389 return (s1.type == s2.type) && (s1.pos == s2.pos) && (s1.count == s2.count);
2393
2394
2395
2397void QDateTimeParser::setCalendar(QCalendar cal)
Combined button and popup list for selecting options.
static int dayOfWeekDiff(int sought, int held)
static void appendSeparator(QStringList *list, QStringView string, int from, int size, int lastQuote)
static int countRepeat(QStringView str, int index, int maxCount)
static int weekDayWithinMonth(QCalendar calendar, int year, int month, int day, int weekDay)
static QString unquote(QStringView str)
static int yearInCenturyFrom(int y2d, int baseYear)
static int startsWithLocalTimeZone(QStringView name, const QDateTime &when, const QLocale &locale)
static bool preferDayOfWeek(const QList< QDateTimeParser::SectionNode > &nodes)
bool operator==(QDateTimeParser::SectionNode s1, QDateTimeParser::SectionNode s2)
static int matchesSeparator(QStringView text, QStringView separator)
static qsizetype digitCount(QStringView str)
static int findTextEntry(QStringView text, const ShortVector< QString > &entries, QString *usedText, int *used)
static QTime actualTime(QDateTimeParser::Sections known, int hour, int hour12, int ampm, int minute, int second, int msec)
static QDate actualDate(QDateTimeParser::Sections known, QCalendar calendar, int baseYear, int year, int year2digits, int month, int day, int dayofweek)
QVarLengthArray< T, 13 > ShortVector