5#include "qplatformdefs.h"
6#include "private/qdatetimeparser_p.h"
15#include "private/qlocale_p.h"
17#include "private/qstringiterator_p.h"
18#include "private/qtenvironmentvariables_p.h"
21#if defined (QDATETIMEPARSER_DEBUG) && !defined(QT_NO_DEBUG_STREAM)
22# define QDTPDEBUG qDebug()
23# define QDTPDEBUGN qDebug
25# define QDTPDEBUG if (false) qDebug()
26# define QDTPDEBUGN if (false) qDebug
31constexpr int QDateTimeParser::NoSectionIndex;
32constexpr int QDateTimeParser::FirstSectionIndex;
33constexpr int QDateTimeParser::LastSectionIndex;
35using namespace Qt::StringLiterals;
40QDateTimeParser::~QDateTimeParser()
45
46
47
48
49
50
51
53int QDateTimeParser::getDigit(
const QDateTime &t,
int index)
const
55 if (index < 0 || index >= sectionNodes.size()) {
56 qWarning(
"QDateTimeParser::getDigit() Internal error (%ls %d)",
57 qUtf16Printable(t.toString()), index);
60 const SectionNode &node = sectionNodes.at(index);
62 case TimeZoneSection:
return t.offsetFromUtc();
63 case Hour24Section:
case Hour12Section:
return t.time().hour();
64 case MinuteSection:
return t.time().minute();
65 case SecondSection:
return t.time().second();
66 case MSecSection:
return t.time().msec();
67 case YearSection2Digits:
68 case YearSection:
return t.date().year(calendar);
69 case MonthSection:
return t.date().month(calendar);
70 case DaySection:
return t.date().day(calendar);
71 case DayOfWeekSectionShort:
72 case DayOfWeekSectionLong:
return calendar.dayOfWeek(t.date());
73 case AmPmSection:
return t.time().hour() > 11 ? 1 : 0;
78 qWarning(
"QDateTimeParser::getDigit() Internal error 2 (%ls %d)",
79 qUtf16Printable(t.toString()), index);
84
85
86
87
88
89
90
94 const int diff = sought - held;
95 return diff < -3 ? diff + 7 : diff > 3 ? diff - 7 : diff;
102 for (
const auto &node : nodes) {
103 if (node.type & QDateTimeParser::DaySection)
105 if (node.type & QDateTimeParser::DayOfWeekSectionMask)
112
113
114
115
116
117
118
119
120
121
123bool QDateTimeParser::setDigit(QDateTime &v,
int index,
int newVal)
const
125 if (index < 0 || index >= sectionNodes.size()) {
126 qWarning(
"QDateTimeParser::setDigit() Internal error (%ls %d %d)",
127 qUtf16Printable(v.toString()), index, newVal);
131 const QDate oldDate = v.date();
132 QCalendar::YearMonthDay date = calendar.partsFromDate(oldDate);
135 int weekDay = calendar.dayOfWeek(oldDate);
136 enum { NoFix, MonthDay, WeekDay } fixDay = NoFix;
138 const QTime time = v.time();
139 int hour = time.hour();
140 int minute = time.minute();
141 int second = time.second();
142 int msec = time.msec();
143 QTimeZone timeZone = v.timeRepresentation();
145 const SectionNode &node = sectionNodes.at(index);
147 case Hour24Section:
case Hour12Section: hour = newVal;
break;
148 case MinuteSection: minute = newVal;
break;
149 case SecondSection: second = newVal;
break;
150 case MSecSection: msec = newVal;
break;
151 case YearSection2Digits:
152 case YearSection: date.year = newVal;
break;
153 case MonthSection: date.month = newVal;
break;
164 case DayOfWeekSectionShort:
165 case DayOfWeekSectionLong:
166 if (newVal > 7 || newVal <= 0)
168 date.day += dayOfWeekDiff(newVal, weekDay);
172 case TimeZoneSection:
173 if (newVal < absoluteMin(index) || newVal > absoluteMax(index))
176 timeZone = QTimeZone::fromSecondsAheadOfUtc(newVal);
178 case AmPmSection: hour = (newVal == 0 ? hour % 12 : (hour % 12) + 12);
break;
180 qWarning(
"QDateTimeParser::setDigit() Internal error (%ls)",
181 qUtf16Printable(node.name()));
185 if (!(node.type & DaySectionMask)) {
186 if (date.day < cachedDay)
187 date.day = cachedDay;
189 if (weekDay > 0 && weekDay <= 7 && preferDayOfWeek(sectionNodes)) {
190 const int max = calendar.daysInMonth(date.month, date.year);
191 if (max > 0 && date.day > max)
193 const int newDoW = calendar.dayOfWeek(calendar.dateFromParts(date));
194 if (newDoW > 0 && newDoW <= 7)
195 date.day += dayOfWeekDiff(weekDay, newDoW);
200 if (fixDay != NoFix) {
201 const int max = calendar.daysInMonth(date.month, date.year);
203 if (max > 0 && date.day > max)
204 date.day = fixDay == WeekDay ? date.day - 7 : max;
205 else if (date.day < 1)
206 date.day = fixDay == WeekDay ? date.day + 7 : 1;
207 Q_ASSERT(fixDay != WeekDay
208 || calendar.dayOfWeek(calendar.dateFromParts(date)) == weekDay);
211 const QDate newDate = calendar.dateFromParts(date);
212 const QTime newTime(hour, minute, second, msec);
213 if (!newDate.isValid() || !newTime.isValid())
216 v = QDateTime(newDate, newTime, timeZone);
223
224
225
226
228int QDateTimeParser::absoluteMax(
int s,
const QDateTime &cur)
const
230 const SectionNode &sn = sectionNode(s);
232 case TimeZoneSection:
233 return QTimeZone::MaxUtcOffsetSecs;
244 case YearSection2Digits:
250 return calendar.maximumMonthsInYear();
252 return cur.isValid() ? cur.date().daysInMonth(calendar) : calendar.maximumDaysInMonth();
253 case DayOfWeekSectionShort:
254 case DayOfWeekSectionLong:
257 return int(UpperCase);
261 qWarning(
"QDateTimeParser::absoluteMax() Internal error (%ls)",
262 qUtf16Printable(sn.name()));
267
268
269
270
272int QDateTimeParser::absoluteMin(
int s)
const
274 const SectionNode &sn = sectionNode(s);
276 case TimeZoneSection:
277 return QTimeZone::MinUtcOffsetSecs;
283 case YearSection2Digits:
289 case DayOfWeekSectionShort:
290 case DayOfWeekSectionLong:
return 1;
291 case AmPmSection:
return int(NativeCase);
294 qWarning(
"QDateTimeParser::absoluteMin() Internal error (%ls, %0x)",
295 qUtf16Printable(sn.name()), sn.type);
300
301
302
303
305const QDateTimeParser::SectionNode &QDateTimeParser::sectionNode(
int sectionIndex)
const
307 static constexpr SectionNode first{FirstSection, 0, -1, 0};
308 static constexpr SectionNode last{LastSection, -1, -1, 0};
309 static constexpr SectionNode none{NoSection, -1, -1, 0};
310 if (sectionIndex < 0) {
311 switch (sectionIndex) {
312 case FirstSectionIndex:
314 case LastSectionIndex:
319 }
else if (sectionIndex < sectionNodes.size()) {
320 return sectionNodes.at(sectionIndex);
323 qWarning(
"QDateTimeParser::sectionNode() Internal error (%d)",
328QDateTimeParser::Section QDateTimeParser::sectionType(
int sectionIndex)
const
330 return sectionNode(sectionIndex).type;
335
336
337
338
340int QDateTimeParser::sectionPos(
int sectionIndex)
const
342 return sectionPos(sectionNode(sectionIndex));
345int QDateTimeParser::sectionPos(SectionNode sn)
const
348 case FirstSection:
return 0;
349 case LastSection:
return displayText().size() - 1;
353 qWarning(
"QDateTimeParser::sectionPos Internal error (%ls)", qUtf16Printable(sn.name()));
360
361
362
363
367 qsizetype digits = 0;
368 for (QStringIterator it(str); it.hasNext();) {
369 if (!QChar::isDigit(it.next()))
377
378
379
380
381
382
386 const QLatin1Char quote(
'\'');
387 const QLatin1Char slash(
'\\');
388 const QLatin1Char zero(
'0');
391 const int max = str.size();
392 for (
int i=0; i<max; ++i) {
393 if (str.at(i) == quote) {
396 else if (!ret.isEmpty() && str.at(i - 1) == slash)
397 ret[ret.size() - 1] = quote;
407static inline int countRepeat(QStringView str,
int index,
int maxCount)
409 str = str.sliced(index);
410 if (maxCount < str.size())
411 str = str.first(maxCount);
413 return qt_repeatCount(str);
417 int from,
int size,
int lastQuote)
419 Q_ASSERT(size >= 0 && from + size <= string.size());
420 const QStringView separator = string.sliced(from, size);
421 list->append(lastQuote >= from ? unquote(separator) : separator.toString());
425
426
427
428
429
430bool QDateTimeParser::parseFormat(QStringView newFormat)
432 const QLatin1Char quote(
'\'');
433 const QLatin1Char slash(
'\\');
434 const QLatin1Char zero(
'0');
435 if (newFormat == displayFormat && !newFormat.isEmpty())
438 QDTPDEBUGN(
"parseFormat: %s", newFormat.toLatin1().constData());
440 QList<SectionNode> newSectionNodes;
442 QStringList newSeparators;
445 QLatin1Char status(zero);
446 const int max = newFormat.size();
448 for (i = 0; i<max; ++i) {
449 if (newFormat.at(i) == quote) {
454 else if (i > 0 && newFormat.at(i - 1) != slash)
456 }
else if (status != quote) {
457 const char sect = newFormat.at(i).toLatin1();
461 if (parserType != QMetaType::QDate) {
462 const Section hour = (sect ==
'h') ? Hour12Section : Hour24Section;
463 const SectionNode sn{hour, i - add, countRepeat(newFormat, i, 2)};
464 newSectionNodes.append(sn);
465 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
472 if (parserType != QMetaType::QDate) {
473 const SectionNode sn{MinuteSection, i - add, countRepeat(newFormat, i, 2)};
474 newSectionNodes.append(sn);
475 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
478 newDisplay |= MinuteSection;
482 if (parserType != QMetaType::QDate) {
483 const SectionNode sn{SecondSection, i - add, countRepeat(newFormat, i, 2)};
484 newSectionNodes.append(sn);
485 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
488 newDisplay |= SecondSection;
493 if (parserType != QMetaType::QDate) {
494 const int repeat = countRepeat(newFormat, i, 3);
495 const SectionNode sn{MSecSection, i - add, repeat < 3 ? 1 : 3};
496 newSectionNodes.append(sn);
497 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
500 newDisplay |= MSecSection;
505 if (parserType != QMetaType::QDate) {
506 const int pos = i - add;
507 Case caseOpt = sect ==
'A' ? UpperCase : LowerCase;
508 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
509 newDisplay |= AmPmSection;
510 if (i + 1 < newFormat.size()
511 && newFormat.sliced(i + 1).startsWith(u'p', Qt::CaseInsensitive)) {
513 if (newFormat.at(i) != QLatin1Char(caseOpt == UpperCase ?
'P' :
'p'))
514 caseOpt = NativeCase;
516 const SectionNode sn{AmPmSection, pos,
int(caseOpt)};
517 newSectionNodes.append(sn);
522 if (parserType != QMetaType::QTime) {
523 const int repeat = countRepeat(newFormat, i, 4);
525 const SectionNode sn{repeat == 4 ? YearSection : YearSection2Digits,
526 i - add, repeat == 4 ? 4 : 2};
527 newSectionNodes.append(sn);
528 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
531 newDisplay |= sn.type;
536 if (parserType != QMetaType::QTime) {
537 const SectionNode sn{MonthSection, i - add, countRepeat(newFormat, i, 4)};
538 newSectionNodes.append(sn);
539 newSeparators.append(unquote(newFormat.first(i).sliced(index)));
542 newDisplay |= MonthSection;
546 if (parserType != QMetaType::QTime) {
547 const int repeat = countRepeat(newFormat, i, 4);
548 const Section sectionType = (repeat == 4 ? DayOfWeekSectionLong
549 : (repeat == 3 ? DayOfWeekSectionShort : DaySection));
550 const SectionNode sn{sectionType, i - add, repeat};
551 newSectionNodes.append(sn);
552 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
555 newDisplay |= sn.type;
559 if (parserType == QMetaType::QDateTime) {
560 const SectionNode sn{TimeZoneSection, i - add, countRepeat(newFormat, i, 4)};
561 newSectionNodes.append(sn);
562 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
565 newDisplay |= TimeZoneSection;
573 if (newSectionNodes.isEmpty() && context == DateTimeEdit)
576 if ((newDisplay & (AmPmSection|Hour12Section)) == Hour12Section) {
577 const int count = newSectionNodes.size();
578 for (
int i = 0; i < count; ++i) {
579 SectionNode &node = newSectionNodes[i];
580 if (node.type == Hour12Section)
581 node.type = Hour24Section;
586 appendSeparator(&newSeparators, newFormat, index, max - index, lastQuote);
588 newSeparators.append(QString());
590 displayFormat = newFormat.toString();
591 separators = newSeparators;
592 sectionNodes = newSectionNodes;
593 display = newDisplay;
600 QDTPDEBUGN(
"separators:\n'%s'", separators.join(
"\n"_L1).toLatin1().constData());
606
607
608
609
611int QDateTimeParser::sectionSize(
int sectionIndex)
const
613 if (sectionIndex < 0)
616 if (sectionIndex >= sectionNodes.size()) {
617 qWarning(
"QDateTimeParser::sectionSize Internal error (%d)", sectionIndex);
621 if (sectionIndex == sectionNodes.size() - 1) {
626 int sizeAdjustment = 0;
627 const int displayTextSize = displayText().size();
628 if (displayTextSize != m_text.size()) {
630 int preceedingZeroesAdded = 0;
631 if (sectionNodes.size() > 1 && context == DateTimeEdit) {
632 const auto begin = sectionNodes.cbegin();
633 const auto end = begin + sectionIndex;
634 for (
auto sectionIt = begin; sectionIt != end; ++sectionIt)
635 preceedingZeroesAdded += sectionIt->zeroesAdded;
637 sizeAdjustment = preceedingZeroesAdded;
640 return displayTextSize + sizeAdjustment - sectionPos(sectionIndex) - separators.last().size();
642 return sectionPos(sectionIndex + 1) - sectionPos(sectionIndex)
643 - separators.at(sectionIndex + 1).size();
648int QDateTimeParser::sectionMaxSize(Section s,
int count)
const
650#if QT_CONFIG(textdate)
651 int mcount = calendar.maximumMonthsInYear();
662 return qMax(getAmPmText(AmText, Case(count)).size(),
663 getAmPmText(PmText, Case(count)).size());
672 case DayOfWeekSectionShort:
673 case DayOfWeekSectionLong:
674#if QT_CONFIG(textdate)
679#if QT_CONFIG(textdate)
685 const QLocale l = locale();
686 const QLocale::FormatType format = count == 4 ? QLocale::LongFormat : QLocale::ShortFormat;
687 for (
int i=1; i<=mcount; ++i) {
688 const QString str = (s == MonthSection
689 ? calendar.monthName(l, i, QCalendar::Unspecified, format)
690 : l.dayName(i, format));
691 ret = qMax(str.size(), ret);
702 case YearSection2Digits:
704 case TimeZoneSection:
706 return std::numeric_limits<
int>::max();
708 case CalendarPopupSection:
710 case TimeSectionMask:
711 case DateSectionMask:
712 case HourSectionMask:
713 case YearSectionMask:
714 case DayOfWeekSectionMask:
716 qWarning(
"QDateTimeParser::sectionMaxSize: Invalid section %s",
717 SectionNode::name(s).toLatin1().constData());
724int QDateTimeParser::sectionMaxSize(
int index)
const
726 const SectionNode &sn = sectionNode(index);
727 return sectionMaxSize(sn.type, sn.count);
738 const auto isSimpleSpace = [](
char32_t ch) {
740 return ch == u' ' || (ch > 127 && QChar::isSpace(ch));
744 if (!text.startsWith(separator)) {
746 QStringIterator given(text), sep(separator);
747 while (sep.hasNext()) {
748 if (!given.hasNext())
750 char32_t s = sep.next(), g = given.next();
751 if (s != g && !(isSimpleSpace(s) && isSimpleSpace(g)))
755 return given.index();
757 return separator.size();
761
762
763
764
765
768QString QDateTimeParser::sectionText(
const QString &text,
int sectionIndex,
int index)
const
770 return text.mid(index, sectionSize(sectionIndex));
773QString QDateTimeParser::sectionText(
int sectionIndex)
const
775 const SectionNode &sn = sectionNode(sectionIndex);
776 return sectionText(displayText(), sectionIndex, sn.pos);
779QDateTimeParser::ParsedSection
780QDateTimeParser::parseSection(
const QDateTime ¤tValue,
int sectionIndex,
int offset)
const
782 ParsedSection result;
783 const SectionNode &sn = sectionNode(sectionIndex);
784 Q_ASSERT_X(!(sn.type & Internal),
785 "QDateTimeParser::parseSection",
"Internal error");
787 const int sectionmaxsize = sectionMaxSize(sectionIndex);
788 const bool negate = (sn.type == YearSection && m_text.size() > offset
789 && calendar.isProleptic() && m_text.at(offset) == u'-');
790 const int negativeYearOffset = negate ? 1 : 0;
792 QStringView sectionTextRef =
793 QStringView { m_text }.mid(offset + negativeYearOffset, sectionmaxsize);
795 QDTPDEBUG <<
"sectionValue for" << sn.name()
796 <<
"with text" << m_text <<
"and (at" << offset
797 <<
") st:" << sectionTextRef;
801 QString sectiontext = sectionTextRef.toString();
803 const int ampm = findAmPm(sectiontext, sectionIndex, &used);
807 result = ParsedSection(Acceptable, ampm, used);
811 result = ParsedSection(Intermediate, ampm - 2, used);
814 result = ParsedSection(Intermediate, 0, used);
817 QDTPDEBUG <<
"invalid because findAmPm(" << sectiontext <<
") returned -1";
820 QDTPDEBUGN(
"This should never happen (findAmPm returned %d)", ampm);
823 if (result.state != Invalid)
824 m_text.replace(offset, used, sectiontext.constData(), used);
826 case TimeZoneSection:
827 result = findTimeZone(sectionTextRef, currentValue,
828 absoluteMax(sectionIndex),
829 absoluteMin(sectionIndex), sn.count);
832 case DayOfWeekSectionShort:
833 case DayOfWeekSectionLong:
835 QString sectiontext = sectionTextRef.toString();
836 int num = 0, used = 0;
837 if (sn.type == MonthSection) {
838 const QDate minDate = getMinimum(currentValue.timeRepresentation()).date();
839 const int year = currentValue.date().year(calendar);
840 const int min = (year == minDate.year(calendar)) ? minDate.month(calendar) : 1;
841 num = findMonth(sectiontext.toLower(), min, sectionIndex, year, §iontext, &used);
843 num = findDay(sectiontext.toLower(), 1, sectionIndex, §iontext, &used);
846 result = ParsedSection(Intermediate, num, used);
848 m_text.replace(offset, used, sectiontext.constData(), used);
849 if (used == sectiontext.size())
850 result = ParsedSection(Acceptable, num, used);
858 case YearSection2Digits:
864 const auto checkSeparator = [&result, field=QStringView{m_text}.sliced(offset),
865 negativeYearOffset, sectionIndex,
this]() {
867 const auto &sep = separators.at(sectionIndex + 1);
868 if (matchesSeparator(field.sliced(negativeYearOffset), sep) != -1)
869 result = ParsedSection(Intermediate, 0, negativeYearOffset);
870 else if (negativeYearOffset && matchesSeparator(field, sep) != -1)
871 result = ParsedSection(Intermediate, 0, 0);
876 int used = negativeYearOffset;
879 if (sectionTextRef.startsWith(u'-')
880 || sectionTextRef.startsWith(u'+')) {
886 QStringView digitsStr = sectionTextRef.left(digitCount(sectionTextRef));
888 if (digitsStr.isEmpty()) {
889 result = ParsedSection(Intermediate, 0, used);
891 const QLocale loc = locale();
892 const int absMax = absoluteMax(sectionIndex);
893 const int absMin = absoluteMin(sectionIndex);
897 for (; digitsStr.size(); digitsStr.chop(1)) {
899 int value =
int(loc.toUInt(digitsStr, &ok));
900 if (!ok || (negate ? -value < absMin : value > absMax))
903 if (sn.type == Hour12Section) {
910 QDTPDEBUG << digitsStr << value << digitsStr.size();
912 used += digitsStr.size();
917 if (!checkSeparator()) {
918 QDTPDEBUG <<
"invalid because" << sectionTextRef <<
"can't become a uint"
924 const FieldInfo fi = fieldInfo(sectionIndex);
925 const bool unfilled = used - negativeYearOffset < sectionmaxsize;
926 if (unfilled && fi & Fraction) {
927 for (
int i = used; i < sectionmaxsize; ++i)
931 Q_ASSERT(negate ? lastVal >= absMin : lastVal <= absMax);
932 if (negate ? lastVal > absMax : lastVal < absMin) {
934 result = ParsedSection(Intermediate, lastVal, used);
936 QDTPDEBUG <<
"invalid because" << lastVal <<
"is greater than absoluteMax"
939 QDTPDEBUG <<
"invalid because" << lastVal <<
"is less than absoluteMin"
943 }
else if (unfilled && (fi & (FixedWidth | Numeric)) == (FixedWidth | Numeric)) {
944 if (skipToNextSection(sectionIndex, currentValue, digitsStr)) {
945 const int missingZeroes = sectionmaxsize - digitsStr.size();
946 result = ParsedSection(Acceptable, lastVal, sectionmaxsize, missingZeroes);
947 m_text.insert(offset, QString(missingZeroes, u'0'));
948 ++(
const_cast<QDateTimeParser*>(
this)->sectionNodes[sectionIndex].zeroesAdded);
950 result = ParsedSection(Intermediate, lastVal, used);
952 }
else if (!lastVal && !calendar.hasYearZero()
953 && (sn.type == YearSection
954 || (sn.type == YearSection2Digits && currentValue.isValid()
955 && currentValue.date().year() / 100 == 0))) {
957 result = ParsedSection(unfilled ? Acceptable : Invalid, lastVal, used);
959 result = ParsedSection(Acceptable, lastVal, used);
965 qWarning(
"QDateTimeParser::parseSection Internal error (%ls %d)",
966 qUtf16Printable(sn.name()), sectionIndex);
969 Q_ASSERT(result.state != Invalid || result.value == -1);
975
976
977
978
979
980
986 const int maxDay = calendar.daysInMonth(month, year);
987 day = maxDay > 1 ? qBound(1, day, maxDay) : qMax(1, day);
988 day += dayOfWeekDiff(weekDay, calendar.dayOfWeek(QDate(year, month, day, calendar)));
989 return day <= 0 ? day + 7 : maxDay > 0 && day > maxDay ? day - 7 : day;
993
994
995
998 Q_ASSERT(0 <= y2d && y2d < 100);
999 const int year = baseYear - baseYear % 100 + y2d;
1000 return year < baseYear ? year + 100 : year;
1004
1005
1006
1007
1008
1009
1011static QDate
actualDate(QDateTimeParser::Sections known, QCalendar calendar,
int baseYear,
1012 int year,
int year2digits,
int month,
int day,
int dayofweek)
1014 QDate actual(year, month, day, calendar);
1015 if (actual.isValid() && year % 100 == year2digits && calendar.dayOfWeek(actual) == dayofweek)
1018 if (dayofweek < 1 || dayofweek > 7)
1019 known &= ~QDateTimeParser::DayOfWeekSectionMask;
1022 if (year % 100 != year2digits) {
1023 if (known & QDateTimeParser::YearSection2Digits) {
1026 known &= ~QDateTimeParser::YearSection;
1028 year2digits = year % 100;
1031 Q_ASSERT(year % 100 == year2digits);
1035 known &= ~QDateTimeParser::MonthSection;
1036 }
else if (month > 12) {
1038 known &= ~QDateTimeParser::MonthSection;
1040 if (!actual.isValid() && !known.testAnyFlag(QDateTimeParser::YearSectionMask)
1041 && known.testFlags(QDateTimeParser::DaySection | QDateTimeParser::MonthSection)
1042 && !calendar.isLeapYear(year) && day > calendar.daysInMonth(month, year)) {
1044 int leap = year + 1, stop = year + 47;
1047 while (!calendar.isLeapYear(leap) && leap < stop)
1049 if (day <= calendar.daysInMonth(month, leap))
1053 QDate first(year, month, 1, calendar);
1054 int last = known & QDateTimeParser::MonthSection
1055 ? (known.testAnyFlag(QDateTimeParser::YearSectionMask)
1056 ? calendar.daysInMonth(month, year) : calendar.daysInMonth(month))
1059 const bool fixDayOfWeek = last && known & QDateTimeParser::YearSection
1060 && known & QDateTimeParser::DayOfWeekSectionMask;
1063 const int diff = (dayofweek - calendar.dayOfWeek(first) - last) % 7;
1064 Q_ASSERT(diff <= 0);
1069 day = 1 + dayofweek - calendar.dayOfWeek(first);
1075 known &= ~QDateTimeParser::DaySection;
1076 }
else if (day > calendar.maximumDaysInMonth()) {
1078 known &= ~QDateTimeParser::DaySection;
1079 }
else if (last && day > last && (known & QDateTimeParser::DaySection) == 0) {
1083 actual = QDate(year, month, day, calendar);
1084 if (!actual.isValid()
1085 || (known & QDateTimeParser::DaySection
1086 && known & QDateTimeParser::MonthSection
1087 && known & QDateTimeParser::YearSection)
1088 || calendar.dayOfWeek(actual) == dayofweek
1089 || (known & QDateTimeParser::DayOfWeekSectionMask) == 0) {
1094
1095
1096
1097
1098
1099
1101 if ((known & QDateTimeParser::DaySection) == 0) {
1103 day = weekDayWithinMonth(calendar, year, month, day, dayofweek);
1104 actual = QDate(year, month, day, calendar);
1108 if ((known & QDateTimeParser::MonthSection) == 0) {
1110
1111
1112
1113
1114 for (
int m = 1; m < 12; m++) {
1116 actual = QDate(year, month - m, day, calendar);
1117 if (calendar.dayOfWeek(actual) == dayofweek)
1120 if (m + month <= 12) {
1121 actual = QDate(year, month + m, day, calendar);
1122 if (calendar.dayOfWeek(actual) == dayofweek)
1127 actual = QDate(year, month, day, calendar);
1130 if ((known & QDateTimeParser::YearSection) == 0) {
1131 if (known & QDateTimeParser::YearSection2Digits) {
1132 actual = calendar.matchCenturyToWeekday({year, month, day}, dayofweek);
1133 if (actual.isValid()) {
1134 Q_ASSERT(calendar.dayOfWeek(actual) == dayofweek);
1139 for (
int y = 1; y < 12; y++) {
1140 actual = QDate(year - y, month, day, calendar);
1141 if (calendar.dayOfWeek(actual) == dayofweek)
1143 actual = QDate(year + y, month, day, calendar);
1144 if (calendar.dayOfWeek(actual) == dayofweek)
1148 actual = QDate(year, month, day, calendar);
1155
1156
1159 int hour,
int hour12,
int ampm,
1160 int minute,
int second,
int msec)
1163 QTime actual(hour, minute, second, msec);
1164 if (hour12 < 0 || hour12 > 12) {
1165 known &= ~QDateTimeParser::Hour12Section;
1169 if (ampm == -1 || (known & QDateTimeParser::AmPmSection) == 0) {
1170 if ((known & QDateTimeParser::Hour12Section) == 0 || hour % 12 == hour12)
1173 if ((known & QDateTimeParser::Hour24Section) == 0)
1174 hour = hour12 + (hour > 12 ? 12 : 0);
1176 Q_ASSERT(ampm == 0 || ampm == 1);
1177 if (hour - hour12 == ampm * 12)
1180 if ((known & QDateTimeParser::Hour24Section) == 0
1181 && known & QDateTimeParser::Hour12Section) {
1182 hour = hour12 + ampm * 12;
1185 actual = QTime(hour, minute, second, msec);
1190
1191
1195 qsizetype longest = 0;
1197 for (
int i = 0; i < 2; ++i) {
1198 const QString zone(qTzName(i));
1199 if (zone.size() > longest && name.startsWith(zone))
1200 longest = zone.size();
1203 const auto consider = [name, &longest](QStringView zone) {
1204 if (name.startsWith(zone)) {
1206 if (9 > longest && zone.size() == 6 && zone.startsWith(
"UTC"_L1)
1207 && name.sliced(6, 3) ==
":00"_L1) {
1209 }
else if (zone.size() > longest) {
1210 longest = zone.size();
1214#if QT_CONFIG(timezone)
1216
1217
1219 const auto localWhen = QDateTime(when.date(), when.time());
1220 consider(localWhen.timeRepresentation().displayName(
1221 localWhen, QTimeZone::ShortName, locale));
1226 consider(QDateTime(when.date(), when.time()).timeZoneAbbreviation());
1227 Q_ASSERT(longest <= INT_MAX);
1228 return int(longest);
1232
1233
1234QDateTimeParser::StateNode
1235QDateTimeParser::scanString(
const QDateTime &defaultValue,
bool fixup)
const
1237 State state = Acceptable;
1238 bool conflicts =
false;
1239 const int sectionNodesCount = sectionNodes.size();
1242 int year, month, day;
1243 const QDate defaultDate = defaultValue.date();
1244 const QTime defaultTime = defaultValue.time();
1245 defaultDate.getDate(&year, &month, &day);
1246 int year2digits = year % 100;
1247 int hour = defaultTime.hour();
1249 int minute = defaultTime.minute();
1250 int second = defaultTime.second();
1251 int msec = defaultTime.msec();
1252 int dayofweek = calendar.dayOfWeek(defaultDate);
1253 QTimeZone timeZone = defaultValue.timeRepresentation();
1256 Sections isSet = NoSection;
1258 for (
int index = 0; index < sectionNodesCount; ++index) {
1259 Q_ASSERT(state != Invalid);
1260 const QString &separator = separators.at(index);
1261 int step = matchesSeparator(QStringView{m_text}.sliced(pos), separator);
1263 QDTPDEBUG <<
"invalid because" << QStringView{m_text}.sliced(pos)
1264 <<
"does not start with" << separator
1265 << index << pos << currentSectionIndex;
1269 sectionNodes[index].pos = pos;
1270 int *current =
nullptr;
1272 const SectionNode sn = sectionNodes.at(index);
1273 const QDateTime usedDateTime = [&] {
1274 const QDate date = actualDate(isSet, calendar, defaultCenturyStart,
1275 year, year2digits, month, day, dayofweek);
1276 const QTime time = actualTime(isSet, hour, hour12, ampm, minute, second, msec);
1277 return QDateTime(date, time, timeZone);
1279 ParsedSection sect = parseSection(usedDateTime, index, pos);
1281 QDTPDEBUG <<
"sectionValue" << sn.name() << m_text
1282 <<
"pos" << pos <<
"used" << sect.used << stateName(sect.state);
1284 padding += sect.zeroes;
1285 if (fixup && sect.state == Intermediate && sect.used < sn.count) {
1286 const FieldInfo fi = fieldInfo(index);
1287 if ((fi & (Numeric|FixedWidth)) == (Numeric|FixedWidth)) {
1288 const QString newText = QString::asprintf(
"%0*d", sn.count, sect.value);
1289 m_text.replace(pos, sect.used, newText);
1290 sect.used = sn.count;
1294 state = qMin<State>(state, sect.state);
1296 if (state == Invalid || (context == FromString && (state == Intermediate || sect.zeroes)))
1300 case TimeZoneSection:
1301 current = &zoneOffset;
1302 if (sect.used > 0) {
1304 QStringView zoneName = QStringView{m_text}.sliced(pos, sect.used);
1305 Q_ASSERT(!zoneName.isEmpty());
1307 const QStringView offsetStr
1308 = zoneName.startsWith(
"UTC"_L1) ? zoneName.sliced(3) : zoneName;
1309 const bool isUtcOffset = offsetStr.startsWith(u'+') || offsetStr.startsWith(u'-');
1310 const bool isUtc = zoneName ==
"Z"_L1 || zoneName ==
"UTC"_L1;
1312 if (isUtc || isUtcOffset) {
1313 timeZone = QTimeZone::fromSecondsAheadOfUtc(sect.value);
1314#if QT_CONFIG(timezone)
1315 }
else if (startsWithLocalTimeZone(zoneName, usedDateTime, locale()) != sect.used) {
1316 QTimeZone namedZone = QTimeZone(zoneName.toLatin1());
1317 Q_ASSERT(namedZone.isValid());
1318 timeZone = namedZone;
1321 timeZone = QTimeZone::LocalTime;
1325 case Hour24Section: current = &hour;
break;
1326 case Hour12Section: current = &hour12;
break;
1327 case MinuteSection: current = &minute;
break;
1328 case SecondSection: current = &second;
break;
1329 case MSecSection: current = &msec;
break;
1330 case YearSection: current = &year;
break;
1331 case YearSection2Digits: current = &year2digits;
break;
1332 case MonthSection: current = &month;
break;
1333 case DayOfWeekSectionShort:
1334 case DayOfWeekSectionLong: current = &dayofweek;
break;
1335 case DaySection: current = &day; sect.value = qMax<
int>(1, sect.value);
break;
1336 case AmPmSection: current = &m;
break;
1338 qWarning(
"QDateTimeParser::parse Internal error (%ls)",
1339 qUtf16Printable(sn.name()));
1343 Q_ASSERT(sect.state != Invalid);
1347 QDTPDEBUG << index << sn.name() <<
"is set to"
1348 << pos <<
"state is" << stateName(state);
1350 if (isSet & sn.type && *current != sect.value) {
1351 QDTPDEBUG <<
"CONFLICT " << sn.name() << *current << sect.value;
1353 if (index != currentSectionIndex)
1356 *current = sect.value;
1362 int step = matchesSeparator(QStringView{m_text}.sliced(pos), separators.last());
1363 if (step == -1 || step + pos < m_text.size()) {
1364 QDTPDEBUG <<
"invalid because" << QStringView{m_text}.sliced(pos)
1365 <<
"does not match" << separators.last() << pos;
1369 if (parserType != QMetaType::QTime) {
1370 if (year % 100 != year2digits && (isSet & YearSection2Digits)) {
1371 const QDate date = actualDate(isSet, calendar, defaultCenturyStart,
1372 year, year2digits, month, day, dayofweek);
1373 if (!date.isValid()) {
1375 }
else if (!(isSet & YearSection)) {
1379 const SectionNode &sn = sectionNode(currentSectionIndex);
1380 if (sn.type == YearSection2Digits)
1385 const auto fieldType = sectionType(currentSectionIndex);
1386 const QDate date(year, month, day, calendar);
1387 if ((!date.isValid() || dayofweek != calendar.dayOfWeek(date))
1388 && state == Acceptable && isSet & DayOfWeekSectionMask) {
1389 if (isSet & DaySection)
1393 if (currentSectionIndex == -1 || fieldType & DayOfWeekSectionMask
1394 || (!conflicts && (fieldType & (YearSectionMask | MonthSection)))) {
1395 day = weekDayWithinMonth(calendar, year, month, day, dayofweek);
1396 QDTPDEBUG << year << month << day << dayofweek
1397 << calendar.dayOfWeek(QDate(year, month, day, calendar));
1401 bool needfixday =
false;
1402 if (fieldType & DaySectionMask) {
1404 }
else if (cachedDay > day && !(isSet & DayOfWeekSectionMask && state == Acceptable)) {
1409 if (!calendar.isDateValid(year, month, day)) {
1410 if (day <= calendar.maximumDaysInMonth())
1412 if (day > calendar.minimumDaysInMonth() && calendar.isDateValid(year, month, 1))
1416 if (context == FromString)
1418 if (state == Acceptable && fixday) {
1419 day = qMin<
int>(day, calendar.daysInMonth(month, year));
1421 const QLocale loc = locale();
1422 for (
int i=0; i<sectionNodesCount; ++i) {
1423 const SectionNode sn = sectionNode(i);
1424 if (sn.type & DaySection) {
1425 m_text.replace(sectionPos(sn), sectionSize(i), loc.toString(day));
1426 }
else if (sn.type & DayOfWeekSectionMask) {
1427 const int dayOfWeek = calendar.dayOfWeek(QDate(year, month, day, calendar));
1428 const QLocale::FormatType dayFormat =
1429 (sn.type == DayOfWeekSectionShort
1430 ? QLocale::ShortFormat : QLocale::LongFormat);
1431 const QString dayName(loc.dayName(dayOfWeek, dayFormat));
1432 m_text.replace(sectionPos(sn), sectionSize(i), dayName);
1435 }
else if (state > Intermediate) {
1436 state = Intermediate;
1441 if (parserType != QMetaType::QDate) {
1442 if (isSet & Hour12Section) {
1443 const bool hasHour = isSet.testAnyFlag(Hour24Section);
1445 ampm = !hasHour || hour < 12 ? 0 : 1;
1446 hour12 = hour12 % 12 + ampm * 12;
1449 else if (hour != hour12)
1451 }
else if (ampm != -1) {
1452 if (!(isSet & (Hour24Section)))
1454 else if ((ampm == 0) != (hour < 12))
1459 QDTPDEBUG << year << month << day << hour << minute << second << msec;
1460 Q_ASSERT(state != Invalid);
1462 const QDate date(year, month, day, calendar);
1463 const QTime time(hour, minute, second, msec);
1464 const QDateTime when = QDateTime(date, time, timeZone);
1466 if (when.time() != time || when.date() != date) {
1472 if (!(isSet & HourSectionMask)) {
1473 switch (parserType) {
1474 case QMetaType::QDateTime: {
1475 qint64 msecs = when.toMSecsSinceEpoch();
1477 const QDateTime replace = QDateTime::fromMSecsSinceEpoch(msecs, timeZone);
1478 const QTime tick = replace.time();
1479 if (replace.date() == date
1480 && (!(isSet & MinuteSection) || tick.minute() == minute)
1481 && (!(isSet & SecondSection) || tick.second() == second)
1482 && (!(isSet & MSecSection) || tick.msec() == msec)) {
1483 return StateNode(replace, state, padding, conflicts);
1486 case QMetaType::QDate:
1488 return StateNode(date.startOfDay(QTimeZone::UTC),
1489 state, padding, conflicts);
1491 case QMetaType::QTime:
1493 return StateNode(QDateTime(date, time, QTimeZone::UTC),
1494 state, padding, conflicts);
1496 Q_UNREACHABLE_RETURN(StateNode());
1498 }
else if (state > Intermediate) {
1499 state = Intermediate;
1503 return StateNode(when, state, padding, conflicts);
1507
1508
1510QDateTimeParser::StateNode
1511QDateTimeParser::parse(
const QString &input,
int position,
1512 const QDateTime &defaultValue,
bool fixup)
const
1514 const QDateTime minimum = getMinimum(defaultValue.timeRepresentation());
1515 const QDateTime maximum = getMaximum(defaultValue.timeRepresentation());
1519 StateNode scan = scanString(defaultValue, fixup);
1520 QDTPDEBUGN(
"'%s' => '%s'(%s)", m_text.toLatin1().constData(),
1521 scan.value.toString(
"yyyy/MM/dd hh:mm:ss.zzz"_L1).toLatin1().constData(),
1522 stateName(scan.state).toLatin1().constData());
1524 if (scan.value.isValid() && scan.state != Invalid) {
1525 if (context != FromString && scan.value < minimum) {
1526 const QLatin1Char space(
' ');
1527 if (scan.value >= minimum)
1528 qWarning(
"QDateTimeParser::parse Internal error 3 (%ls %ls)",
1529 qUtf16Printable(scan.value.toString()), qUtf16Printable(minimum.toString()));
1532 scan.state = Invalid;
1533 const int sectionNodesCount = sectionNodes.size();
1534 for (
int i=0; i<sectionNodesCount && !done; ++i) {
1535 const SectionNode &sn = sectionNodes.at(i);
1536 QString t = sectionText(m_text, i, sn.pos).toLower();
1537 if ((t.size() < sectionMaxSize(i)
1538 && ((fieldInfo(i) & (FixedWidth|Numeric)) != Numeric))
1539 || t.contains(space)) {
1542 switch (findAmPm(t, i)) {
1545 scan.state = Acceptable;
1549 scan.state = Invalid;
1554 case PossibleBoth: {
1555 const QDateTime copy(scan.value.addSecs(12 * 60 * 60));
1556 if (copy >= minimum && copy <= maximum) {
1557 scan.state = Intermediate;
1564 if (sn.count >= 3) {
1565 const QDate when = scan.value.date();
1566 const int finalMonth = when.month(calendar);
1567 int tmp = finalMonth;
1569 while ((tmp = findMonth(t, tmp + 1, i, when.year(calendar))) != -1) {
1570 const QDateTime copy(scan.value.addMonths(tmp - finalMonth));
1571 if (copy >= minimum && copy <= maximum)
1575 scan.state = Intermediate;
1585 if (sn.type & TimeSectionMask) {
1586 if (scan.value.daysTo(minimum) != 0)
1589 const QTime time = scan.value.time();
1590 toMin = time.msecsTo(minimum.time());
1591 if (scan.value.daysTo(maximum) > 0)
1594 toMax = time.msecsTo(maximum.time());
1596 toMin = scan.value.daysTo(minimum);
1597 toMax = scan.value.daysTo(maximum);
1599 const int maxChange = sn.maxChange();
1600 if (toMin > maxChange) {
1601 QDTPDEBUG <<
"invalid because toMin > maxChange" << toMin
1602 << maxChange << t << scan.value << minimum;
1603 scan.state = Invalid;
1606 }
else if (toMax > maxChange) {
1610 const int min = getDigit(minimum, i);
1612 qWarning(
"QDateTimeParser::parse Internal error 4 (%ls)",
1613 qUtf16Printable(sn.name()));
1614 scan.state = Invalid;
1619 int max = toMax != -1 ? getDigit(maximum, i) : absoluteMax(i, scan.value);
1620 int pos = position + scan.padded - sn.pos;
1621 if (pos < 0 || pos >= t.size())
1623 if (!potentialValue(t.simplified(), min, max, i, scan.value, pos)) {
1624 QDTPDEBUG <<
"invalid because potentialValue(" << t.simplified() << min << max
1625 << sn.name() <<
"returned" << toMax << toMin << pos;
1626 scan.state = Invalid;
1630 scan.state = Intermediate;
1637 if (scan.value > maximum)
1638 scan.state = Invalid;
1640 QDTPDEBUG <<
"not checking intermediate because scanned value is"
1641 << scan.value << minimum << maximum;
1646 Q_ASSERT(scan.value.isValid() || scan.state != Acceptable);
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662static int findTextEntry(QStringView text,
const ShortVector<QString> &entries, QString *usedText,
int *used)
1669 for (
int n = 0; n < entries.size(); ++n)
1671 const QString &name = entries.at(n);
1673 const int limit = qMin(text.size(), name.size());
1675 while (i < limit && text.at(i) == name.at(i).toLower())
1678 if (i > bestCount || (i == bestCount && i == name.size())) {
1681 if (i == name.size() && i == text.size())
1685 if (usedText && bestMatch != -1)
1686 *usedText = entries.at(bestMatch);
1694
1695
1696
1697
1699int QDateTimeParser::findMonth(QStringView str,
int startMonth,
int sectionIndex,
1700 int year, QString *usedMonth,
int *used)
const
1702 const SectionNode &sn = sectionNode(sectionIndex);
1703 if (sn.type != MonthSection) {
1704 qWarning(
"QDateTimeParser::findMonth Internal error");
1708 QLocale::FormatType type = sn.count == 3 ? QLocale::ShortFormat : QLocale::LongFormat;
1709 QLocale l = locale();
1710 ShortVector<QString> monthNames;
1711 monthNames.reserve(13 - startMonth);
1712 for (
int month = startMonth; month <= 12; ++month)
1713 monthNames.append(calendar.monthName(l, month, year, type));
1715 const int index = findTextEntry(str, monthNames, usedMonth, used);
1716 return index < 0 ? index : index + startMonth;
1719int QDateTimeParser::findDay(QStringView str,
int startDay,
int sectionIndex, QString *usedDay,
int *used)
const
1721 const SectionNode &sn = sectionNode(sectionIndex);
1722 if (!(sn.type & DaySectionMask)) {
1723 qWarning(
"QDateTimeParser::findDay Internal error");
1727 QLocale::FormatType type = sn.count == 4 ? QLocale::LongFormat : QLocale::ShortFormat;
1728 QLocale l = locale();
1729 ShortVector<QString> daysOfWeek;
1730 daysOfWeek.reserve(8 - startDay);
1731 for (
int day = startDay; day <= 7; ++day)
1732 daysOfWeek.append(l.dayName(day, type));
1734 const int index = findTextEntry(str, daysOfWeek, usedDay, used);
1735 return index < 0 ? index : index + startDay;
1739
1740
1741
1742
1743
1744
1745
1746QDateTimeParser::ParsedSection QDateTimeParser::findUtcOffset(QStringView str,
int mode)
const
1748 Q_ASSERT(mode > 0 && mode < 4);
1749 const bool startsWithUtc = str.startsWith(
"UTC"_L1);
1751 if (startsWithUtc) {
1753 return ParsedSection();
1754 str = str.sliced(3);
1756 return ParsedSection(Acceptable, 0, 3);
1759 const bool negativeSign = str.startsWith(u'-');
1761 if (!negativeSign && !str.startsWith(u'+'))
1762 return ParsedSection();
1763 str = str.sliced(1);
1765 const int colonPosition = str.indexOf(u':');
1767 bool hasColon = (colonPosition >= 0 && colonPosition < 3);
1770 const int digits = hasColon ? colonPosition + 3 : 4;
1772 for (
const int offsetLength = qMin(qsizetype(digits), str.size()); i < offsetLength; ++i) {
1773 if (i != colonPosition && !str.at(i).isDigit())
1776 const int hoursLength = qMin(i, hasColon ? colonPosition : 2);
1777 if (hoursLength < 1)
1778 return ParsedSection();
1782 if (!startsWithUtc && hoursLength != 2)
1783 return ParsedSection();
1787 if (mode == (hasColon ? 2 : 3))
1788 return ParsedSection();
1792 const int hours = str.first(hoursLength).toInt(&isInt);
1794 return ParsedSection();
1795 const QStringView minutesStr = str.mid(hasColon ? colonPosition + 1 : 2, 2);
1796 const int minutes = minutesStr.isEmpty() ? 0 : minutesStr.toInt(&isInt);
1798 return ParsedSection();
1803 const State status = (hours > 14 || minutes >= 60) ? Invalid
1804 : (hours == 14 && minutes > 0) ? Intermediate : Acceptable;
1806 int offset = 3600 * hours + 60 * minutes;
1811 const int usedSymbols = (startsWithUtc ? 3 : 0) + 1 + hoursLength + (hasColon ? 1 : 0)
1812 + minutesStr.size();
1814 return ParsedSection(status, offset, usedSymbols);
1818
1819
1820
1821
1822
1823
1824QDateTimeParser::ParsedSection
1825QDateTimeParser::findTimeZoneName(QStringView str,
const QDateTime &when)
const
1827 const int systemLength = startsWithLocalTimeZone(str, when, locale());
1828#if QT_CONFIG(timezone)
1831 const auto invalidZoneNameCharacter = [] (
const QChar &c) {
1832 const auto cu = c.unicode();
1833 return cu >= 127u || !(memchr(
"+-./:_",
char(cu), 6) || c.isLetterOrNumber());
1835 int index = std::distance(str.cbegin(),
1836 std::find_if(str.cbegin(), str.cend(), invalidZoneNameCharacter));
1843 Q_ASSERT(index <= str.size());
1844 while (lastSlash < index) {
1845 int slash = str.indexOf(u'/', lastSlash + 1);
1846 if (slash < 0 || slash > index)
1848 else if (++count > 5)
1850 if (slash - lastSlash > 20)
1851 index = lastSlash + 20;
1856 for (; index > systemLength; --index) {
1857 str.truncate(index);
1858 QTimeZone zone(str.toLatin1());
1860 return ParsedSection(Acceptable, zone.offsetFromUtc(when), index);
1863 if (systemLength > 0)
1864 return ParsedSection(Acceptable, when.toLocalTime().offsetFromUtc(), systemLength);
1865 return ParsedSection();
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880QDateTimeParser::ParsedSection
1881QDateTimeParser::findTimeZone(QStringView str,
const QDateTime &when,
1882 int maxVal,
int minVal,
int mode)
const
1884 Q_ASSERT(mode > 0 && mode <= 4);
1886 if (mode == 1 && str == u'Z')
1887 return ParsedSection(Acceptable, 0, 1);
1889 ParsedSection section;
1891 section = findUtcOffset(str, mode);
1892 if (mode != 2 && mode != 3 && section.used <= 0)
1893 section = findTimeZoneName(str, when);
1895 if (section.state == Acceptable && (section.value < minVal || section.value > maxVal))
1896 section.state = Intermediate;
1897 if (section.used > 0)
1902 if (str.startsWith(
"UTC"_L1))
1903 return ParsedSection(Acceptable, 0, 3);
1904 if (str.startsWith(u'Z'))
1905 return ParsedSection(Acceptable, 0, 1);
1908 return ParsedSection();
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925QDateTimeParser::AmPmFinder QDateTimeParser::findAmPm(QString &str,
int sectionIndex,
int *used)
const
1927 const SectionNode &s = sectionNode(sectionIndex);
1928 if (s.type != AmPmSection) {
1929 qWarning(
"QDateTimeParser::findAmPm Internal error");
1934 if (QStringView(str).trimmed().isEmpty())
1935 return PossibleBoth;
1937 const QLatin1Char space(
' ');
1938 int size = sectionMaxSize(sectionIndex);
1945 ampm[amindex] = getAmPmText(AmText, Case(s.count));
1946 ampm[pmindex] = getAmPmText(PmText, Case(s.count));
1947 for (
int i = 0; i < 2; ++i)
1948 ampm[i].truncate(size);
1950 QDTPDEBUG <<
"findAmPm" << str << ampm[0] << ampm[1];
1952 if (str.startsWith(ampm[amindex], Qt::CaseInsensitive)) {
1953 str = ampm[amindex];
1955 }
else if (str.startsWith(ampm[pmindex], Qt::CaseInsensitive)) {
1956 str = ampm[pmindex];
1958 }
else if (context == FromString || (str.count(space) == 0 && str.size() >= size)) {
1961 size = qMin(size, str.size());
1963 bool broken[2] = {
false,
false};
1964 for (
int i=0; i<size; ++i) {
1965 const QChar ch = str.at(i);
1967 for (
int j=0; j<2; ++j) {
1969 int index = ampm[j].indexOf(ch);
1971 <<
"in" << ampm[j] <<
"and got" << index;
1973 if (ch.category() == QChar::Letter_Uppercase) {
1974 index = ampm[j].indexOf(ch.toLower());
1975 QDTPDEBUG <<
"trying with" << ch.toLower()
1976 <<
"in" << ampm[j] <<
"and got" << index;
1977 }
else if (ch.category() == QChar::Letter_Lowercase) {
1978 index = ampm[j].indexOf(ch.toUpper());
1979 QDTPDEBUG <<
"trying with" << ch.toUpper()
1980 <<
"in" << ampm[j] <<
"and got" << index;
1984 if (broken[amindex] && broken[pmindex]) {
1990 str[i] = ampm[j].at(index);
1993 ampm[j].remove(index, 1);
1998 if (!broken[pmindex] && !broken[amindex])
1999 return PossibleBoth;
2000 return (!broken[amindex] ? PossibleAM : PossiblePM);
2004
2005
2006
2008int QDateTimeParser::SectionNode::maxChange()
const
2012 case MSecSection:
return 999;
2013 case SecondSection:
return 59 * 1000;
2014 case MinuteSection:
return 59 * 60 * 1000;
2015 case Hour24Section:
case Hour12Section:
return 59 * 60 * 60 * 1000;
2018 case DayOfWeekSectionShort:
2019 case DayOfWeekSectionLong:
return 7;
2020 case DaySection:
return 30;
2021 case MonthSection:
return 365 - 31;
2022 case YearSection:
return 9999 * 365;
2023 case YearSection2Digits:
return 100 * 365;
2025 qWarning(
"QDateTimeParser::maxChange() Internal error (%ls)",
2026 qUtf16Printable(name()));
2032QDateTimeParser::FieldInfo QDateTimeParser::fieldInfo(
int index)
const
2035 const SectionNode &sn = sectionNode(index);
2044 case YearSection2Digits:
2045 ret |= AllowPartial;
2059 ret |= (Numeric|AllowPartial);
2063 case DayOfWeekSectionShort:
2064 case DayOfWeekSectionLong:
2070 if (getAmPmText(AmText, Case(sn.count)).size()
2071 == getAmPmText(PmText, Case(sn.count)).size()) {
2076 case TimeZoneSection:
2079 qWarning(
"QDateTimeParser::fieldInfo Internal error 2 (%d %ls %d)",
2080 index, qUtf16Printable(sn.name()), sn.count);
2086QString QDateTimeParser::SectionNode::format()
const
2090 case AmPmSection:
return count == 1 ?
"ap"_L1 : count == 2 ?
"AP"_L1 :
"Ap"_L1;
2091 case MSecSection: fillChar = u'z';
break;
2092 case SecondSection: fillChar = u's';
break;
2093 case MinuteSection: fillChar = u'm';
break;
2094 case Hour24Section: fillChar = u'H';
break;
2095 case Hour12Section: fillChar = u'h';
break;
2096 case DayOfWeekSectionShort:
2097 case DayOfWeekSectionLong:
2098 case DaySection: fillChar = u'd';
break;
2099 case MonthSection: fillChar = u'M';
break;
2100 case YearSection2Digits:
2101 case YearSection: fillChar = u'y';
break;
2103 qWarning(
"QDateTimeParser::sectionFormat Internal error (%ls)",
2104 qUtf16Printable(name(type)));
2107 if (fillChar.isNull()) {
2108 qWarning(
"QDateTimeParser::sectionFormat Internal error 2");
2111 return QString(count, fillChar);
2116
2117
2118
2119
2120
2122bool QDateTimeParser::potentialValue(QStringView str,
int min,
int max,
int index,
2123 const QDateTime ¤tValue,
int insert)
const
2128 const int size = sectionMaxSize(index);
2129 int val = (
int)locale().toUInt(str);
2130 const SectionNode &sn = sectionNode(index);
2131 if (sn.type == YearSection2Digits) {
2132 const int year = currentValue.date().year(calendar);
2133 val += year - (year % 100);
2135 if (val >= min && val <= max && str.size() == size)
2137 if (val > max || (str.size() == size && val < min))
2140 const int len = size - str.size();
2141 for (
int i=0; i<len; ++i) {
2142 for (
int j=0; j<10; ++j) {
2143 if (potentialValue(str + QLatin1Char(
'0' + j), min, max, index, currentValue, insert)) {
2145 }
else if (insert >= 0) {
2146 const QString tmp = str.left(insert) + QLatin1Char(
'0' + j) + str.mid(insert);
2147 if (potentialValue(tmp, min, max, index, currentValue, insert))
2157
2158
2159bool QDateTimeParser::skipToNextSection(
int index,
const QDateTime ¤t, QStringView text)
const
2161 Q_ASSERT(text.size() < sectionMaxSize(index));
2162 const SectionNode &node = sectionNode(index);
2163 int min = absoluteMin(index);
2164 int max = absoluteMax(index, current);
2166 if (node.type != TimeZoneSection || current.timeSpec() == Qt::OffsetFromUTC) {
2167 const QDateTime maximum = getMaximum(current.timeRepresentation());
2168 const QDateTime minimum = getMinimum(current.timeRepresentation());
2174 QDateTime tmp = current;
2175 if (!setDigit(tmp, index, min) || tmp < minimum)
2176 min = getDigit(minimum, index);
2178 if (!setDigit(tmp, index, max) || tmp > maximum)
2179 max = getDigit(maximum, index);
2181 int pos = cursorPosition() - node.pos;
2182 if (pos < 0 || pos >= text.size())
2186
2187
2188
2189
2190
2191 return !potentialValue(text, min, max, index, current, pos);
2195
2196
2197
2199QString QDateTimeParser::SectionNode::name(QDateTimeParser::Section s)
2202 case AmPmSection:
return "AmPmSection"_L1;
2203 case DaySection:
return "DaySection"_L1;
2204 case DayOfWeekSectionShort:
return "DayOfWeekSectionShort"_L1;
2205 case DayOfWeekSectionLong:
return "DayOfWeekSectionLong"_L1;
2206 case Hour24Section:
return "Hour24Section"_L1;
2207 case Hour12Section:
return "Hour12Section"_L1;
2208 case MSecSection:
return "MSecSection"_L1;
2209 case MinuteSection:
return "MinuteSection"_L1;
2210 case MonthSection:
return "MonthSection"_L1;
2211 case SecondSection:
return "SecondSection"_L1;
2212 case TimeZoneSection:
return "TimeZoneSection"_L1;
2213 case YearSection:
return "YearSection"_L1;
2214 case YearSection2Digits:
return "YearSection2Digits"_L1;
2215 case NoSection:
return "NoSection"_L1;
2216 case FirstSection:
return "FirstSection"_L1;
2217 case LastSection:
return "LastSection"_L1;
2218 default:
return "Unknown section "_L1 + QString::number(
int(s));
2223
2224
2225
2227QString QDateTimeParser::stateName(State s)
const
2230 case Invalid:
return "Invalid"_L1;
2231 case Intermediate:
return "Intermediate"_L1;
2232 case Acceptable:
return "Acceptable"_L1;
2233 default:
return "Unknown state "_L1 + QString::number(s);
2239
2240
2241
2242QDateTime QDateTimeParser::baseDate(
const QTimeZone &zone)
const
2244 QDateTime when = QDate(defaultCenturyStart, 1, 1).startOfDay(zone);
2245 if (
const QDateTime start = getMinimum(zone); when < start)
2247 if (
const QDateTime end = getMaximum(zone); when > end)
2253bool QDateTimeParser::fromString(
const QString &t, QDate *date, QTime *time,
int baseYear)
const
2255 defaultCenturyStart = baseYear;
2256 const StateNode tmp = parse(t, -1, baseDate(QTimeZone::UTC),
false);
2257 if (tmp.state != Acceptable || tmp.conflicts)
2262 const QTime t = tmp.value.time();
2270 const QDate d = tmp.value.date();
2279bool QDateTimeParser::fromString(
const QString &t, QDateTime *datetime,
int baseYear)
const
2281 defaultCenturyStart = baseYear;
2282 const StateNode tmp = parse(t, -1, baseDate(QTimeZone::LocalTime),
false);
2284 *datetime = tmp.value;
2285 return tmp.state >= Intermediate && !tmp.conflicts && tmp.value.isValid();
2288QDateTime QDateTimeParser::getMinimum(
const QTimeZone &zone)
const
2295 static const QDateTime localTimeMin(QDATETIMEEDIT_DATE_MIN.startOfDay());
2296 static const QDateTime utcTimeMin = localTimeMin.toUTC();
2297 switch (zone.timeSpec()) {
2299 return localTimeMin;
2302 case Qt::OffsetFromUTC:
2306 return utcTimeMin.toTimeZone(zone);
2309QDateTime QDateTimeParser::getMaximum(
const QTimeZone &zone)
const
2316 static const QDateTime localTimeMax(QDATETIMEEDIT_DATE_MAX.endOfDay());
2317 static const QDateTime utcTimeMax = localTimeMax.toUTC();
2318 switch (zone.timeSpec()) {
2320 return localTimeMax;
2323 case Qt::OffsetFromUTC:
2327 return utcTimeMax.toTimeZone(zone);
2330QString QDateTimeParser::getAmPmText(AmPm ap, Case cs)
const
2332 const QLocale loc = locale();
2333 QString raw = ap == AmText ? loc.amText() : loc.pmText();
2336 case UpperCase:
return std::move(raw).toUpper();
2337 case LowerCase:
return std::move(raw).toLower();
2338 case NativeCase:
return raw;
2340 Q_UNREACHABLE_RETURN(raw);
2344
2345
2347bool operator==(QDateTimeParser::SectionNode s1, QDateTimeParser::SectionNode s2)
2349 return (s1.type == s2.type) && (s1.pos == s2.pos) && (s1.count == s2.count);
2353
2354
2356void 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)