5#include "private/qdatetimeparser_p.h"
11#include "private/qlocale_p.h"
13#include "private/qstringiterator_p.h"
14#include "private/qtenvironmentvariables_p.h"
16#if QT_CONFIG(timezone)
17#include "private/qtimezoneprivate_p.h"
23#if defined (QDATETIMEPARSER_DEBUG) && !defined(QT_NO_DEBUG_STREAM)
24# define QDTPDEBUG qDebug()
25# define QDTPDEBUGN qDebug
27# define QDTPDEBUG if (false) qDebug()
28# define QDTPDEBUGN if (false) qDebug
33constexpr int QDateTimeParser::NoSectionIndex;
34constexpr int QDateTimeParser::FirstSectionIndex;
35constexpr int QDateTimeParser::LastSectionIndex;
37using namespace Qt::StringLiterals;
42QDateTimeParser::~QDateTimeParser()
47
48
49
50
51
52
53
55int QDateTimeParser::getDigit(
const QDateTime &t,
int index)
const
57 if (index < 0 || index >= sectionNodes.size()) {
58 qWarning(
"QDateTimeParser::getDigit() Internal error (%ls %d)",
59 qUtf16Printable(t.toString()), index);
62 const SectionNode &node = sectionNodes.at(index);
64 case TimeZoneSection:
return t.offsetFromUtc();
65 case Hour24Section:
case Hour12Section:
return t.time().hour();
66 case MinuteSection:
return t.time().minute();
67 case SecondSection:
return t.time().second();
68 case MSecSection:
return t.time().msec();
69 case YearSection2Digits:
70 case YearSection:
return t.date().year(calendar);
71 case MonthSection:
return t.date().month(calendar);
72 case DaySection:
return t.date().day(calendar);
73 case DayOfWeekSectionShort:
74 case DayOfWeekSectionLong:
return calendar.dayOfWeek(t.date());
75 case AmPmSection:
return t.time().hour() > 11 ? 1 : 0;
80 qWarning(
"QDateTimeParser::getDigit() Internal error 2 (%ls %d)",
81 qUtf16Printable(t.toString()), index);
86
87
88
89
90
91
92
96 const int diff = sought - held;
97 return diff < -3 ? diff + 7 : diff > 3 ? diff - 7 : diff;
104 for (
const auto &node : nodes) {
105 if (node.type & QDateTimeParser::DaySection)
107 if (node.type & QDateTimeParser::DayOfWeekSectionMask)
114
115
116
117
118
119
120
121
122
123
125bool QDateTimeParser::setDigit(QDateTime &v,
int index,
int newVal)
const
127 if (index < 0 || index >= sectionNodes.size()) {
128 qWarning(
"QDateTimeParser::setDigit() Internal error (%ls %d %d)",
129 qUtf16Printable(v.toString()), index, newVal);
133 const QDate oldDate = v.date();
134 QCalendar::YearMonthDay date = calendar.partsFromDate(oldDate);
137 int weekDay = calendar.dayOfWeek(oldDate);
138 enum { NoFix, MonthDay, WeekDay } fixDay = NoFix;
140 const QTime time = v.time();
141 int hour = time.hour();
142 int minute = time.minute();
143 int second = time.second();
144 int msec = time.msec();
145 QTimeZone timeZone = v.timeRepresentation();
147 const SectionNode &node = sectionNodes.at(index);
149 case Hour24Section:
case Hour12Section: hour = newVal;
break;
150 case MinuteSection: minute = newVal;
break;
151 case SecondSection: second = newVal;
break;
152 case MSecSection: msec = newVal;
break;
153 case YearSection2Digits:
154 case YearSection: date.year = newVal;
break;
155 case MonthSection: date.month = newVal;
break;
166 case DayOfWeekSectionShort:
167 case DayOfWeekSectionLong:
168 if (newVal > 7 || newVal <= 0)
170 date.day += dayOfWeekDiff(newVal, weekDay);
174 case TimeZoneSection:
175 if (newVal < absoluteMin(index) || newVal > absoluteMax(index))
178 timeZone = QTimeZone::fromSecondsAheadOfUtc(newVal);
180 case AmPmSection: hour = (newVal == 0 ? hour % 12 : (hour % 12) + 12);
break;
182 qWarning(
"QDateTimeParser::setDigit() Internal error (%ls)",
183 qUtf16Printable(node.name()));
187 if (!(node.type & DaySectionMask)) {
188 if (date.day < cachedDay)
189 date.day = cachedDay;
191 if (weekDay > 0 && weekDay <= 7 && preferDayOfWeek(sectionNodes)) {
192 const int max = calendar.daysInMonth(date.month, date.year);
193 if (max > 0 && date.day > max)
195 const int newDoW = calendar.dayOfWeek(calendar.dateFromParts(date));
196 if (newDoW > 0 && newDoW <= 7)
197 date.day += dayOfWeekDiff(weekDay, newDoW);
202 if (fixDay != NoFix) {
203 const int max = calendar.daysInMonth(date.month, date.year);
205 if (max > 0 && date.day > max)
206 date.day = fixDay == WeekDay ? date.day - 7 : max;
207 else if (date.day < 1)
208 date.day = fixDay == WeekDay ? date.day + 7 : 1;
209 Q_ASSERT(fixDay != WeekDay
210 || calendar.dayOfWeek(calendar.dateFromParts(date)) == weekDay);
213 const QDate newDate = calendar.dateFromParts(date);
214 const QTime newTime(hour, minute, second, msec);
215 if (!newDate.isValid() || !newTime.isValid())
218 v = QDateTime(newDate, newTime, timeZone);
225
226
227
228
230int QDateTimeParser::absoluteMax(
int s,
const QDateTime &cur)
const
232 const SectionNode &sn = sectionNode(s);
234 case TimeZoneSection:
235 return QTimeZone::MaxUtcOffsetSecs;
246 case YearSection2Digits:
252 return calendar.maximumMonthsInYear();
254 return cur.isValid() ? cur.date().daysInMonth(calendar) : calendar.maximumDaysInMonth();
255 case DayOfWeekSectionShort:
256 case DayOfWeekSectionLong:
259 return int(UpperCase);
263 qWarning(
"QDateTimeParser::absoluteMax() Internal error (%ls)",
264 qUtf16Printable(sn.name()));
269
270
271
272
274int QDateTimeParser::absoluteMin(
int s)
const
276 const SectionNode &sn = sectionNode(s);
278 case TimeZoneSection:
279 return QTimeZone::MinUtcOffsetSecs;
285 case YearSection2Digits:
291 case DayOfWeekSectionShort:
292 case DayOfWeekSectionLong:
return 1;
293 case AmPmSection:
return int(NativeCase);
296 qWarning(
"QDateTimeParser::absoluteMin() Internal error (%ls, %0x)",
297 qUtf16Printable(sn.name()), sn.type);
302
303
304
305
307const QDateTimeParser::SectionNode &QDateTimeParser::sectionNode(
int sectionIndex)
const
309 static constexpr SectionNode first{FirstSection, 0, -1, 0};
310 static constexpr SectionNode last{LastSection, -1, -1, 0};
311 static constexpr SectionNode none{NoSection, -1, -1, 0};
312 if (sectionIndex < 0) {
313 switch (sectionIndex) {
314 case FirstSectionIndex:
316 case LastSectionIndex:
321 }
else if (sectionIndex < sectionNodes.size()) {
322 return sectionNodes.at(sectionIndex);
325 qWarning(
"QDateTimeParser::sectionNode() Internal error (%d)",
330QDateTimeParser::Section QDateTimeParser::sectionType(
int sectionIndex)
const
332 return sectionNode(sectionIndex).type;
337
338
339
340
342int QDateTimeParser::sectionPos(
int sectionIndex)
const
344 return sectionPos(sectionNode(sectionIndex));
347int QDateTimeParser::sectionPos(SectionNode sn)
const
350 case FirstSection:
return 0;
351 case LastSection:
return displayText().size() - 1;
355 qWarning(
"QDateTimeParser::sectionPos Internal error (%ls)", qUtf16Printable(sn.name()));
362
363
364
365
369 qsizetype digits = 0;
370 for (QStringIterator it(str); it.hasNext();) {
371 if (!QChar::isDigit(it.next()))
379
380
381
382
383
384
388 const QLatin1Char quote(
'\'');
389 const QLatin1Char slash(
'\\');
390 const QLatin1Char zero(
'0');
393 const int max = str.size();
394 for (
int i=0; i<max; ++i) {
395 if (str.at(i) == quote) {
398 else if (!ret.isEmpty() && str.at(i - 1) == slash)
399 ret[ret.size() - 1] = quote;
409static inline int countRepeat(QStringView str,
int index,
int maxCount)
411 str = str.sliced(index);
412 if (maxCount < str.size())
413 str = str.first(maxCount);
415 return qt_repeatCount(str);
419 int from,
int size,
int lastQuote)
421 Q_ASSERT(size >= 0 && from + size <= string.size());
422 const QStringView separator = string.sliced(from, size);
423 list->append(lastQuote >= from ? unquote(separator) : separator.toString());
427
428
429
430
431
432bool QDateTimeParser::parseFormat(QStringView newFormat)
434 const QLatin1Char quote(
'\'');
435 const QLatin1Char slash(
'\\');
436 const QLatin1Char zero(
'0');
437 if (newFormat == displayFormat && !newFormat.isEmpty())
440 QDTPDEBUGN(
"parseFormat: %s", newFormat.toLatin1().constData());
442 QList<SectionNode> newSectionNodes;
444 QStringList newSeparators;
447 QLatin1Char status(zero);
448 const int max = newFormat.size();
450 for (i = 0; i<max; ++i) {
451 if (newFormat.at(i) == quote) {
456 else if (i > 0 && newFormat.at(i - 1) != slash)
458 }
else if (status != quote) {
459 const char sect = newFormat.at(i).toLatin1();
463 if (parserType != QMetaType::QDate) {
464 const Section hour = (sect ==
'h') ? Hour12Section : Hour24Section;
465 const SectionNode sn{hour, i - add, countRepeat(newFormat, i, 2)};
466 newSectionNodes.append(sn);
467 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
474 if (parserType != QMetaType::QDate) {
475 const SectionNode sn{MinuteSection, i - add, countRepeat(newFormat, i, 2)};
476 newSectionNodes.append(sn);
477 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
480 newDisplay |= MinuteSection;
484 if (parserType != QMetaType::QDate) {
485 const SectionNode sn{SecondSection, i - add, countRepeat(newFormat, i, 2)};
486 newSectionNodes.append(sn);
487 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
490 newDisplay |= SecondSection;
495 if (parserType != QMetaType::QDate) {
496 const int repeat = countRepeat(newFormat, i, 3);
497 const SectionNode sn{MSecSection, i - add, repeat < 3 ? 1 : 3};
498 newSectionNodes.append(sn);
499 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
502 newDisplay |= MSecSection;
507 if (parserType != QMetaType::QDate) {
508 const int pos = i - add;
509 Case caseOpt = sect ==
'A' ? UpperCase : LowerCase;
510 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
511 newDisplay |= AmPmSection;
512 if (i + 1 < newFormat.size()
513 && newFormat.sliced(i + 1).startsWith(u'p', Qt::CaseInsensitive)) {
515 if (newFormat.at(i) != QLatin1Char(caseOpt == UpperCase ?
'P' :
'p'))
516 caseOpt = NativeCase;
518 const SectionNode sn{AmPmSection, pos,
int(caseOpt)};
519 newSectionNodes.append(sn);
524 if (parserType != QMetaType::QTime) {
525 const int repeat = countRepeat(newFormat, i, 4);
527 const SectionNode sn{repeat == 4 ? YearSection : YearSection2Digits,
528 i - add, repeat == 4 ? 4 : 2};
529 newSectionNodes.append(sn);
530 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
533 newDisplay |= sn.type;
538 if (parserType != QMetaType::QTime) {
539 const SectionNode sn{MonthSection, i - add, countRepeat(newFormat, i, 4)};
540 newSectionNodes.append(sn);
541 newSeparators.append(unquote(newFormat.first(i).sliced(index)));
544 newDisplay |= MonthSection;
548 if (parserType != QMetaType::QTime) {
549 const int repeat = countRepeat(newFormat, i, 4);
550 const Section sectionType = (repeat == 4 ? DayOfWeekSectionLong
551 : (repeat == 3 ? DayOfWeekSectionShort : DaySection));
552 const SectionNode sn{sectionType, i - add, repeat};
553 newSectionNodes.append(sn);
554 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
557 newDisplay |= sn.type;
561 if (parserType == QMetaType::QDateTime) {
562 const SectionNode sn{TimeZoneSection, i - add, countRepeat(newFormat, i, 4)};
563 newSectionNodes.append(sn);
564 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
567 newDisplay |= TimeZoneSection;
575 if (newSectionNodes.isEmpty() && context == DateTimeEdit)
578 if ((newDisplay & (AmPmSection|Hour12Section)) == Hour12Section) {
579 const int count = newSectionNodes.size();
580 for (
int i = 0; i < count; ++i) {
581 SectionNode &node = newSectionNodes[i];
582 if (node.type == Hour12Section)
583 node.type = Hour24Section;
588 appendSeparator(&newSeparators, newFormat, index, max - index, lastQuote);
590 newSeparators.append(QString());
592 displayFormat = newFormat.toString();
593 separators = newSeparators;
594 sectionNodes = newSectionNodes;
595 display = newDisplay;
602 QDTPDEBUGN(
"separators:\n'%s'", separators.join(
"\n"_L1).toLatin1().constData());
608
609
610
611
613int QDateTimeParser::sectionSize(
int sectionIndex)
const
615 if (sectionIndex < 0)
618 if (sectionIndex >= sectionNodes.size()) {
619 qWarning(
"QDateTimeParser::sectionSize Internal error (%d)", sectionIndex);
623 if (sectionIndex == sectionNodes.size() - 1) {
628 int sizeAdjustment = 0;
629 const int displayTextSize = displayText().size();
630 if (displayTextSize != m_text.size()) {
632 int preceedingZeroesAdded = 0;
633 if (sectionNodes.size() > 1 && context == DateTimeEdit) {
634 const auto begin = sectionNodes.cbegin();
635 const auto end = begin + sectionIndex;
636 for (
auto sectionIt = begin; sectionIt != end; ++sectionIt)
637 preceedingZeroesAdded += sectionIt->zeroesAdded;
639 sizeAdjustment = preceedingZeroesAdded;
642 return displayTextSize + sizeAdjustment - sectionPos(sectionIndex) - separators.last().size();
644 return sectionPos(sectionIndex + 1) - sectionPos(sectionIndex)
645 - separators.at(sectionIndex + 1).size();
650int QDateTimeParser::sectionMaxSize(Section s,
int count)
const
652#if QT_CONFIG(textdate)
653 int mcount = calendar.maximumMonthsInYear();
664 return qMax(getAmPmText(AmText, Case(count)).size(),
665 getAmPmText(PmText, Case(count)).size());
674 case DayOfWeekSectionShort:
675 case DayOfWeekSectionLong:
676#if QT_CONFIG(textdate)
681#if QT_CONFIG(textdate)
687 const QLocale l = locale();
688 const QLocale::FormatType format = count == 4 ? QLocale::LongFormat : QLocale::ShortFormat;
689 for (
int i=1; i<=mcount; ++i) {
690 const QString str = (s == MonthSection
691 ? calendar.monthName(l, i, QCalendar::Unspecified, format)
692 : l.dayName(i, format));
693 ret = qMax(str.size(), ret);
704 case YearSection2Digits:
706 case TimeZoneSection:
708 return std::numeric_limits<
int>::max();
710 case CalendarPopupSection:
712 case TimeSectionMask:
713 case DateSectionMask:
714 case HourSectionMask:
715 case YearSectionMask:
716 case DayOfWeekSectionMask:
718 qWarning(
"QDateTimeParser::sectionMaxSize: Invalid section %s",
719 SectionNode::name(s).toLatin1().constData());
726int QDateTimeParser::sectionMaxSize(
int index)
const
728 const SectionNode &sn = sectionNode(index);
729 return sectionMaxSize(sn.type, sn.count);
740 const auto isSimpleSpace = [](
char32_t ch) {
742 return ch == u' ' || (ch > 127 && QChar::isSpace(ch));
746 if (!text.startsWith(separator)) {
748 QStringIterator given(text), sep(separator);
749 while (sep.hasNext()) {
750 if (!given.hasNext())
752 char32_t s = sep.next(), g = given.next();
753 if (s != g && !(isSimpleSpace(s) && isSimpleSpace(g)))
757 return given.index();
759 return separator.size();
763
764
765
766
767
770QString QDateTimeParser::sectionText(
const QString &text,
int sectionIndex,
int index)
const
772 return text.mid(index, sectionSize(sectionIndex));
775QString QDateTimeParser::sectionText(
int sectionIndex)
const
777 const SectionNode &sn = sectionNode(sectionIndex);
778 return sectionText(displayText(), sectionIndex, sn.pos);
781QDateTimeParser::ParsedSection
782QDateTimeParser::parseSection(
const QDateTime ¤tValue,
int sectionIndex,
int offset)
const
784 ParsedSection result;
785 const SectionNode &sn = sectionNode(sectionIndex);
786 Q_ASSERT_X(!(sn.type & Internal),
787 "QDateTimeParser::parseSection",
"Internal error");
789 const int sectionmaxsize = sectionMaxSize(sectionIndex);
790 const bool negate = (sn.type == YearSection && m_text.size() > offset
791 && calendar.isProleptic() && m_text.at(offset) == u'-');
792 const int negativeYearOffset = negate ? 1 : 0;
794 QStringView sectionTextRef =
795 QStringView { m_text }.mid(offset + negativeYearOffset, sectionmaxsize);
797 QDTPDEBUG <<
"sectionValue for" << sn.name()
798 <<
"with text" << m_text <<
"and (at" << offset
799 <<
") st:" << sectionTextRef;
803 QString sectiontext = sectionTextRef.toString();
805 const int ampm = findAmPm(sectiontext, sectionIndex, &used);
809 result = ParsedSection(Acceptable, ampm, used);
813 result = ParsedSection(Intermediate, ampm - 2, used);
816 result = ParsedSection(Intermediate, 0, used);
819 QDTPDEBUG <<
"invalid because findAmPm(" << sectiontext <<
") returned -1";
822 QDTPDEBUGN(
"This should never happen (findAmPm returned %d)", ampm);
825 if (result.state != Invalid)
826 m_text.replace(offset, used, sectiontext.constData(), used);
828 case TimeZoneSection:
829 result = findTimeZone(sectionTextRef, currentValue,
830 absoluteMax(sectionIndex),
831 absoluteMin(sectionIndex), sn.count);
834 case DayOfWeekSectionShort:
835 case DayOfWeekSectionLong:
837 QString sectiontext = sectionTextRef.toString();
838 int num = 0, used = 0;
839 if (sn.type == MonthSection) {
840 const QDate minDate = getMinimum(currentValue.timeRepresentation()).date();
841 const int year = currentValue.date().year(calendar);
842 const int min = (year == minDate.year(calendar)) ? minDate.month(calendar) : 1;
843 num = findMonth(sectiontext.toLower(), min, sectionIndex, year, §iontext, &used);
845 num = findDay(sectiontext.toLower(), 1, sectionIndex, §iontext, &used);
848 result = ParsedSection(Intermediate, num, used);
850 m_text.replace(offset, used, sectiontext.constData(), used);
851 if (used == sectiontext.size())
852 result = ParsedSection(Acceptable, num, used);
860 case YearSection2Digits:
866 const auto checkSeparator = [&result, field=QStringView{m_text}.sliced(offset),
867 negativeYearOffset, sectionIndex,
this]() {
869 const auto &sep = separators.at(sectionIndex + 1);
870 if (matchesSeparator(field.sliced(negativeYearOffset), sep) != -1)
871 result = ParsedSection(Intermediate, 0, negativeYearOffset);
872 else if (negativeYearOffset && matchesSeparator(field, sep) != -1)
873 result = ParsedSection(Intermediate, 0, 0);
878 int used = negativeYearOffset;
881 if (sectionTextRef.startsWith(u'-')
882 || sectionTextRef.startsWith(u'+')) {
888 QStringView digitsStr = sectionTextRef.left(digitCount(sectionTextRef));
890 if (digitsStr.isEmpty()) {
891 result = ParsedSection(Intermediate, 0, used);
893 const QLocale loc = locale();
894 const int absMax = absoluteMax(sectionIndex);
895 const int absMin = absoluteMin(sectionIndex);
899 for (; digitsStr.size(); digitsStr.chop(1)) {
901 int value =
int(loc.toUInt(digitsStr, &ok));
902 if (!ok || (negate ? -value < absMin : value > absMax))
905 if (sn.type == Hour12Section) {
912 QDTPDEBUG << digitsStr << value << digitsStr.size();
914 used += digitsStr.size();
919 if (!checkSeparator()) {
920 QDTPDEBUG <<
"invalid because" << sectionTextRef <<
"can't become a uint"
926 const FieldInfo fi = fieldInfo(sectionIndex);
927 const bool unfilled = used - negativeYearOffset < sectionmaxsize;
928 if (unfilled && fi & Fraction) {
929 for (
int i = used; i < sectionmaxsize; ++i)
933 Q_ASSERT(negate ? lastVal >= absMin : lastVal <= absMax);
934 if (negate ? lastVal > absMax : lastVal < absMin) {
936 result = ParsedSection(Intermediate, lastVal, used);
938 QDTPDEBUG <<
"invalid because" << lastVal <<
"is greater than absoluteMax"
941 QDTPDEBUG <<
"invalid because" << lastVal <<
"is less than absoluteMin"
945 }
else if (unfilled && (fi & (FixedWidth | Numeric)) == (FixedWidth | Numeric)) {
946 if (skipToNextSection(sectionIndex, currentValue, digitsStr)) {
947 const int missingZeroes = sectionmaxsize - digitsStr.size();
948 result = ParsedSection(Acceptable, lastVal, sectionmaxsize, missingZeroes);
949 m_text.insert(offset, QString(missingZeroes, u'0'));
950 ++(
const_cast<QDateTimeParser*>(
this)->sectionNodes[sectionIndex].zeroesAdded);
952 result = ParsedSection(Intermediate, lastVal, used);
954 }
else if (!lastVal && !calendar.hasYearZero()
955 && (sn.type == YearSection
956 || (sn.type == YearSection2Digits && currentValue.isValid()
957 && currentValue.date().year() / 100 == 0))) {
959 result = ParsedSection(unfilled ? Acceptable : Invalid, lastVal, used);
961 result = ParsedSection(Acceptable, lastVal, used);
967 qWarning(
"QDateTimeParser::parseSection Internal error (%ls %d)",
968 qUtf16Printable(sn.name()), sectionIndex);
971 Q_ASSERT(result.state != Invalid || result.value == -1);
977
978
979
980
981
982
988 const int maxDay = calendar.daysInMonth(month, year);
989 day = maxDay > 1 ? qBound(1, day, maxDay) : qMax(1, day);
990 day += dayOfWeekDiff(weekDay, calendar.dayOfWeek(QDate(year, month, day, calendar)));
991 return day <= 0 ? day + 7 : maxDay > 0 && day > maxDay ? day - 7 : day;
995
996
997
1000 Q_ASSERT(0 <= y2d && y2d < 100);
1001 const int year = baseYear - baseYear % 100 + y2d;
1002 return year < baseYear ? year + 100 : year;
1006
1007
1008
1009
1010
1011
1013static QDate
actualDate(QDateTimeParser::Sections known, QCalendar calendar,
int baseYear,
1014 int year,
int year2digits,
int month,
int day,
int dayofweek)
1016 QDate actual(year, month, day, calendar);
1017 if (actual.isValid() && year % 100 == year2digits && calendar.dayOfWeek(actual) == dayofweek)
1020 if (dayofweek < 1 || dayofweek > 7)
1021 known &= ~QDateTimeParser::DayOfWeekSectionMask;
1024 if (year % 100 != year2digits) {
1025 if (known & QDateTimeParser::YearSection2Digits) {
1028 known &= ~QDateTimeParser::YearSection;
1030 year2digits = year % 100;
1033 Q_ASSERT(year % 100 == year2digits);
1037 known &= ~QDateTimeParser::MonthSection;
1038 }
else if (month > 12) {
1040 known &= ~QDateTimeParser::MonthSection;
1042 if (!actual.isValid() && !known.testAnyFlag(QDateTimeParser::YearSectionMask)
1043 && known.testFlags(QDateTimeParser::DaySection | QDateTimeParser::MonthSection)
1044 && !calendar.isLeapYear(year) && day > calendar.daysInMonth(month, year)) {
1046 int leap = year + 1, stop = year + 47;
1049 while (!calendar.isLeapYear(leap) && leap < stop)
1051 if (day <= calendar.daysInMonth(month, leap))
1055 QDate first(year, month, 1, calendar);
1056 int last = known & QDateTimeParser::MonthSection
1057 ? (known.testAnyFlag(QDateTimeParser::YearSectionMask)
1058 ? calendar.daysInMonth(month, year) : calendar.daysInMonth(month))
1061 const bool fixDayOfWeek = last && known & QDateTimeParser::YearSection
1062 && known & QDateTimeParser::DayOfWeekSectionMask;
1065 const int diff = (dayofweek - calendar.dayOfWeek(first) - last) % 7;
1066 Q_ASSERT(diff <= 0);
1071 day = 1 + dayofweek - calendar.dayOfWeek(first);
1077 known &= ~QDateTimeParser::DaySection;
1078 }
else if (day > calendar.maximumDaysInMonth()) {
1080 known &= ~QDateTimeParser::DaySection;
1081 }
else if (last && day > last && (known & QDateTimeParser::DaySection) == 0) {
1085 actual = QDate(year, month, day, calendar);
1086 if (!actual.isValid()
1087 || (known & QDateTimeParser::DaySection
1088 && known & QDateTimeParser::MonthSection
1089 && known & QDateTimeParser::YearSection)
1090 || calendar.dayOfWeek(actual) == dayofweek
1091 || (known & QDateTimeParser::DayOfWeekSectionMask) == 0) {
1096
1097
1098
1099
1100
1101
1103 if ((known & QDateTimeParser::DaySection) == 0) {
1105 day = weekDayWithinMonth(calendar, year, month, day, dayofweek);
1106 actual = QDate(year, month, day, calendar);
1110 if ((known & QDateTimeParser::MonthSection) == 0) {
1112
1113
1114
1115
1116 for (
int m = 1; m < 12; m++) {
1118 actual = QDate(year, month - m, day, calendar);
1119 if (calendar.dayOfWeek(actual) == dayofweek)
1122 if (m + month <= 12) {
1123 actual = QDate(year, month + m, day, calendar);
1124 if (calendar.dayOfWeek(actual) == dayofweek)
1129 actual = QDate(year, month, day, calendar);
1132 if ((known & QDateTimeParser::YearSection) == 0) {
1133 if (known & QDateTimeParser::YearSection2Digits) {
1134 actual = calendar.matchCenturyToWeekday({year, month, day}, dayofweek);
1135 if (actual.isValid()) {
1136 Q_ASSERT(calendar.dayOfWeek(actual) == dayofweek);
1141 for (
int y = 1; y < 12; y++) {
1142 actual = QDate(year - y, month, day, calendar);
1143 if (calendar.dayOfWeek(actual) == dayofweek)
1145 actual = QDate(year + y, month, day, calendar);
1146 if (calendar.dayOfWeek(actual) == dayofweek)
1150 actual = QDate(year, month, day, calendar);
1157
1158
1161 int hour,
int hour12,
int ampm,
1162 int minute,
int second,
int msec)
1165 QTime actual(hour, minute, second, msec);
1166 if (hour12 < 0 || hour12 > 12) {
1167 known &= ~QDateTimeParser::Hour12Section;
1171 if (ampm == -1 || (known & QDateTimeParser::AmPmSection) == 0) {
1172 if ((known & QDateTimeParser::Hour12Section) == 0 || hour % 12 == hour12)
1175 if ((known & QDateTimeParser::Hour24Section) == 0)
1176 hour = hour12 + (hour > 12 ? 12 : 0);
1178 Q_ASSERT(ampm == 0 || ampm == 1);
1179 if (hour - hour12 == ampm * 12)
1182 if ((known & QDateTimeParser::Hour24Section) == 0
1183 && known & QDateTimeParser::Hour12Section) {
1184 hour = hour12 + ampm * 12;
1187 actual = QTime(hour, minute, second, msec);
1192
1193
1197 qsizetype longest = 0;
1199 for (
int i = 0; i < 2; ++i) {
1200 const QString zone(qTzName(i));
1201 if (zone.size() > longest && name.startsWith(zone))
1202 longest = zone.size();
1205 const auto consider = [name, &longest](QStringView zone) {
1206 if (name.startsWith(zone)) {
1208 if (9 > longest && zone.size() == 6 && zone.startsWith(
"UTC"_L1)
1209 && name.sliced(6, 3) ==
":00"_L1) {
1211 }
else if (zone.size() > longest) {
1212 longest = zone.size();
1216#if QT_CONFIG(timezone)
1218
1219
1221 const auto localWhen = QDateTime(when.date(), when.time());
1222 consider(localWhen.timeRepresentation().displayName(
1223 localWhen, QTimeZone::ShortName, locale));
1228 consider(QDateTime(when.date(), when.time()).timeZoneAbbreviation());
1229 Q_ASSERT(longest <= INT_MAX);
1230 return int(longest);
1233#if QT_CONFIG(timezone)
1234static auto findZoneByLongName(QStringView str,
const QLocale &locale,
const QDateTime &when)
1239 qsizetype nameLength = 0;
1240 bool isValid()
const {
return nameLength > 0 && zone.isValid(); }
1242 auto pfx = QTimeZonePrivate::findLongNamePrefix(str, locale, when.toMSecsSinceEpoch());
1244 pfx = QTimeZonePrivate::findLongNamePrefix(str, locale);
1248 pfx = QTimeZonePrivate::findNarrowOffsetPrefix(str, locale, QLocale::NarrowFormat);
1250 pfx = QTimeZonePrivate::findLongUtcPrefix(str);
1252 result = R{ QTimeZone(pfx.ianaId), pfx.nameLength };
1253 Q_ASSERT(result.zone.isValid());
1261
1262
1263QDateTimeParser::StateNode
1264QDateTimeParser::scanString(
const QDateTime &defaultValue,
bool fixup)
const
1266 State state = Acceptable;
1267 bool conflicts =
false;
1268 const int sectionNodesCount = sectionNodes.size();
1271 int year, month, day;
1272 const QDate defaultDate = defaultValue.date();
1273 const QTime defaultTime = defaultValue.time();
1274 defaultDate.getDate(&year, &month, &day);
1275 int year2digits = year % 100;
1276 int hour = defaultTime.hour();
1278 int minute = defaultTime.minute();
1279 int second = defaultTime.second();
1280 int msec = defaultTime.msec();
1281 int dayofweek = calendar.dayOfWeek(defaultDate);
1282 QTimeZone timeZone = defaultValue.timeRepresentation();
1285 Sections isSet = NoSection;
1287 for (
int index = 0; index < sectionNodesCount; ++index) {
1288 Q_ASSERT(state != Invalid);
1289 const QString &separator = separators.at(index);
1290 int step = matchesSeparator(QStringView{m_text}.sliced(pos), separator);
1292 QDTPDEBUG <<
"invalid because" << QStringView{m_text}.sliced(pos)
1293 <<
"does not start with" << separator
1294 << index << pos << currentSectionIndex;
1298 sectionNodes[index].pos = pos;
1299 int *current =
nullptr;
1301 const SectionNode sn = sectionNodes.at(index);
1302 const QDateTime usedDateTime = [&] {
1303 const QDate date = actualDate(isSet, calendar, defaultCenturyStart,
1304 year, year2digits, month, day, dayofweek);
1305 const QTime time = actualTime(isSet, hour, hour12, ampm, minute, second, msec);
1306 return QDateTime(date, time, timeZone);
1308 ParsedSection sect = parseSection(usedDateTime, index, pos);
1310 QDTPDEBUG <<
"sectionValue" << sn.name() << m_text
1311 <<
"pos" << pos <<
"used" << sect.used << stateName(sect.state);
1313 padding += sect.zeroes;
1314 if (fixup && sect.state == Intermediate && sect.used < sn.count) {
1315 const FieldInfo fi = fieldInfo(index);
1316 if ((fi & (Numeric|FixedWidth)) == (Numeric|FixedWidth)) {
1317 const QString newText = QString::asprintf(
"%0*d", sn.count, sect.value);
1318 m_text.replace(pos, sect.used, newText);
1319 sect.used = sn.count;
1323 state = qMin<State>(state, sect.state);
1325 if (state == Invalid || (context == FromString && (state == Intermediate || sect.zeroes)))
1329 case TimeZoneSection:
1330 current = &zoneOffset;
1331 if (sect.used > 0) {
1333 QStringView zoneName = QStringView{m_text}.sliced(pos, sect.used);
1334 Q_ASSERT(!zoneName.isEmpty());
1336 const QStringView offsetStr
1337 = zoneName.startsWith(
"UTC"_L1) ? zoneName.sliced(3) : zoneName;
1338 const bool isUtcOffset = offsetStr.startsWith(u'+') || offsetStr.startsWith(u'-');
1339 const bool isUtc = zoneName ==
"Z"_L1 || zoneName ==
"UTC"_L1;
1341 if (isUtc || isUtcOffset) {
1342 timeZone = QTimeZone::fromSecondsAheadOfUtc(sect.value);
1343#if QT_CONFIG(timezone)
1344 }
else if (startsWithLocalTimeZone(zoneName, usedDateTime, locale()) != sect.used) {
1345 if (QTimeZone namedZone = QTimeZone(zoneName.toLatin1()); namedZone.isValid()) {
1346 timeZone = namedZone;
1348 auto found = findZoneByLongName(zoneName, locale(), usedDateTime);
1349 Q_ASSERT(found.isValid());
1350 Q_ASSERT(found.nameLength == zoneName.length());
1351 timeZone = found.zone;
1355 timeZone = QTimeZone::LocalTime;
1359 case Hour24Section: current = &hour;
break;
1360 case Hour12Section: current = &hour12;
break;
1361 case MinuteSection: current = &minute;
break;
1362 case SecondSection: current = &second;
break;
1363 case MSecSection: current = &msec;
break;
1364 case YearSection: current = &year;
break;
1365 case YearSection2Digits: current = &year2digits;
break;
1366 case MonthSection: current = &month;
break;
1367 case DayOfWeekSectionShort:
1368 case DayOfWeekSectionLong: current = &dayofweek;
break;
1369 case DaySection: current = &day; sect.value = qMax<
int>(1, sect.value);
break;
1370 case AmPmSection: current = &m;
break;
1372 qWarning(
"QDateTimeParser::parse Internal error (%ls)",
1373 qUtf16Printable(sn.name()));
1377 Q_ASSERT(sect.state != Invalid);
1381 QDTPDEBUG << index << sn.name() <<
"is set to"
1382 << pos <<
"state is" << stateName(state);
1384 if (isSet & sn.type && *current != sect.value) {
1385 QDTPDEBUG <<
"CONFLICT " << sn.name() << *current << sect.value;
1387 if (index != currentSectionIndex)
1390 *current = sect.value;
1396 int step = matchesSeparator(QStringView{m_text}.sliced(pos), separators.last());
1397 if (step == -1 || step + pos < m_text.size()) {
1398 QDTPDEBUG <<
"invalid because" << QStringView{m_text}.sliced(pos)
1399 <<
"does not match" << separators.last() << pos;
1403 if (parserType != QMetaType::QTime) {
1404 if (year % 100 != year2digits && (isSet & YearSection2Digits)) {
1405 const QDate date = actualDate(isSet, calendar, defaultCenturyStart,
1406 year, year2digits, month, day, dayofweek);
1407 if (!date.isValid()) {
1409 }
else if (!(isSet & YearSection)) {
1413 const SectionNode &sn = sectionNode(currentSectionIndex);
1414 if (sn.type == YearSection2Digits)
1419 const auto fieldType = sectionType(currentSectionIndex);
1420 const QDate date(year, month, day, calendar);
1421 if ((!date.isValid() || dayofweek != calendar.dayOfWeek(date))
1422 && state == Acceptable && isSet & DayOfWeekSectionMask) {
1423 if (isSet & DaySection)
1427 if (currentSectionIndex == -1 || fieldType & DayOfWeekSectionMask
1428 || (!conflicts && (fieldType & (YearSectionMask | MonthSection)))) {
1429 day = weekDayWithinMonth(calendar, year, month, day, dayofweek);
1430 QDTPDEBUG << year << month << day << dayofweek
1431 << calendar.dayOfWeek(QDate(year, month, day, calendar));
1435 bool needfixday =
false;
1436 if (fieldType & DaySectionMask) {
1438 }
else if (cachedDay > day && !(isSet & DayOfWeekSectionMask && state == Acceptable)) {
1443 if (!calendar.isDateValid(year, month, day)) {
1444 if (day <= calendar.maximumDaysInMonth())
1446 if (day > calendar.minimumDaysInMonth() && calendar.isDateValid(year, month, 1))
1450 if (context == FromString)
1452 if (state == Acceptable && fixday) {
1453 day = qMin<
int>(day, calendar.daysInMonth(month, year));
1455 const QLocale loc = locale();
1456 for (
int i=0; i<sectionNodesCount; ++i) {
1457 const SectionNode sn = sectionNode(i);
1458 if (sn.type & DaySection) {
1459 m_text.replace(sectionPos(sn), sectionSize(i), loc.toString(day));
1460 }
else if (sn.type & DayOfWeekSectionMask) {
1461 const int dayOfWeek = calendar.dayOfWeek(QDate(year, month, day, calendar));
1462 const QLocale::FormatType dayFormat =
1463 (sn.type == DayOfWeekSectionShort
1464 ? QLocale::ShortFormat : QLocale::LongFormat);
1465 const QString dayName(loc.dayName(dayOfWeek, dayFormat));
1466 m_text.replace(sectionPos(sn), sectionSize(i), dayName);
1469 }
else if (state > Intermediate) {
1470 state = Intermediate;
1475 if (parserType != QMetaType::QDate) {
1476 if (isSet & Hour12Section) {
1477 const bool hasHour = isSet.testAnyFlag(Hour24Section);
1479 ampm = !hasHour || hour < 12 ? 0 : 1;
1480 hour12 = hour12 % 12 + ampm * 12;
1483 else if (hour != hour12)
1485 }
else if (ampm != -1) {
1486 if (!(isSet & (Hour24Section)))
1488 else if ((ampm == 0) != (hour < 12))
1493 QDTPDEBUG << year << month << day << hour << minute << second << msec;
1494 Q_ASSERT(state != Invalid);
1496 const QDate date(year, month, day, calendar);
1497 const QTime time(hour, minute, second, msec);
1498 const QDateTime when = QDateTime(date, time, timeZone);
1500 if (when.time() != time || when.date() != date) {
1506 if (!(isSet & HourSectionMask)) {
1507 switch (parserType) {
1508 case QMetaType::QDateTime: {
1509 qint64 msecs = when.toMSecsSinceEpoch();
1511 const QDateTime replace = QDateTime::fromMSecsSinceEpoch(msecs, timeZone);
1512 const QTime tick = replace.time();
1513 if (replace.date() == date
1514 && (!(isSet & MinuteSection) || tick.minute() == minute)
1515 && (!(isSet & SecondSection) || tick.second() == second)
1516 && (!(isSet & MSecSection) || tick.msec() == msec)) {
1517 return StateNode(replace, state, padding, conflicts);
1520 case QMetaType::QDate:
1522 return StateNode(date.startOfDay(QTimeZone::UTC),
1523 state, padding, conflicts);
1525 case QMetaType::QTime:
1527 return StateNode(QDateTime(date, time, QTimeZone::UTC),
1528 state, padding, conflicts);
1530 Q_UNREACHABLE_RETURN(StateNode());
1532 }
else if (state > Intermediate) {
1533 state = Intermediate;
1537 return StateNode(when, state, padding, conflicts);
1541
1542
1544QDateTimeParser::StateNode
1545QDateTimeParser::parse(
const QString &input,
int position,
1546 const QDateTime &defaultValue,
bool fixup)
const
1548 const QDateTime minimum = getMinimum(defaultValue.timeRepresentation());
1549 const QDateTime maximum = getMaximum(defaultValue.timeRepresentation());
1553 StateNode scan = scanString(defaultValue, fixup);
1554 QDTPDEBUGN(
"'%s' => '%s'(%s)", m_text.toLatin1().constData(),
1555 scan.value.toString(
"yyyy/MM/dd hh:mm:ss.zzz"_L1).toLatin1().constData(),
1556 stateName(scan.state).toLatin1().constData());
1558 if (scan.value.isValid() && scan.state != Invalid) {
1559 if (context != FromString && scan.value < minimum) {
1560 const QLatin1Char space(
' ');
1561 if (scan.value >= minimum)
1562 qWarning(
"QDateTimeParser::parse Internal error 3 (%ls %ls)",
1563 qUtf16Printable(scan.value.toString()), qUtf16Printable(minimum.toString()));
1566 scan.state = Invalid;
1567 const int sectionNodesCount = sectionNodes.size();
1568 for (
int i=0; i<sectionNodesCount && !done; ++i) {
1569 const SectionNode &sn = sectionNodes.at(i);
1570 QString t = sectionText(m_text, i, sn.pos).toLower();
1571 if ((t.size() < sectionMaxSize(i)
1572 && ((fieldInfo(i) & (FixedWidth|Numeric)) != Numeric))
1573 || t.contains(space)) {
1576 switch (findAmPm(t, i)) {
1579 scan.state = Acceptable;
1583 scan.state = Invalid;
1588 case PossibleBoth: {
1589 const QDateTime copy(scan.value.addSecs(12 * 60 * 60));
1590 if (copy >= minimum && copy <= maximum) {
1591 scan.state = Intermediate;
1598 if (sn.count >= 3) {
1599 const QDate when = scan.value.date();
1600 const int finalMonth = when.month(calendar);
1601 int tmp = finalMonth;
1603 while ((tmp = findMonth(t, tmp + 1, i, when.year(calendar))) != -1) {
1604 const QDateTime copy(scan.value.addMonths(tmp - finalMonth));
1605 if (copy >= minimum && copy <= maximum)
1609 scan.state = Intermediate;
1619 if (sn.type & TimeSectionMask) {
1620 if (scan.value.daysTo(minimum) != 0)
1623 const QTime time = scan.value.time();
1624 toMin = time.msecsTo(minimum.time());
1625 if (scan.value.daysTo(maximum) > 0)
1628 toMax = time.msecsTo(maximum.time());
1630 toMin = scan.value.daysTo(minimum);
1631 toMax = scan.value.daysTo(maximum);
1633 const int maxChange = sn.maxChange();
1634 if (toMin > maxChange) {
1635 QDTPDEBUG <<
"invalid because toMin > maxChange" << toMin
1636 << maxChange << t << scan.value << minimum;
1637 scan.state = Invalid;
1640 }
else if (toMax > maxChange) {
1644 const int min = getDigit(minimum, i);
1646 qWarning(
"QDateTimeParser::parse Internal error 4 (%ls)",
1647 qUtf16Printable(sn.name()));
1648 scan.state = Invalid;
1653 int max = toMax != -1 ? getDigit(maximum, i) : absoluteMax(i, scan.value);
1654 int pos = position + scan.padded - sn.pos;
1655 if (pos < 0 || pos >= t.size())
1657 if (!potentialValue(t.simplified(), min, max, i, scan.value, pos)) {
1658 QDTPDEBUG <<
"invalid because potentialValue(" << t.simplified() << min << max
1659 << sn.name() <<
"returned" << toMax << toMin << pos;
1660 scan.state = Invalid;
1664 scan.state = Intermediate;
1671 if (scan.value > maximum)
1672 scan.state = Invalid;
1674 QDTPDEBUG <<
"not checking intermediate because scanned value is"
1675 << scan.value << minimum << maximum;
1680 Q_ASSERT(scan.value.isValid() || scan.state != Acceptable);
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696static int findTextEntry(QStringView text,
const ShortVector<QString> &entries, QString *usedText,
int *used)
1703 for (
int n = 0; n < entries.size(); ++n)
1705 const QString &name = entries.at(n);
1707 const int limit = qMin(text.size(), name.size());
1709 while (i < limit && text.at(i) == name.at(i).toLower())
1712 if (i > bestCount || (i == bestCount && i == name.size())) {
1715 if (i == name.size() && i == text.size())
1719 if (usedText && bestMatch != -1)
1720 *usedText = entries.at(bestMatch);
1728
1729
1730
1731
1733int QDateTimeParser::findMonth(QStringView str,
int startMonth,
int sectionIndex,
1734 int year, QString *usedMonth,
int *used)
const
1736 const SectionNode &sn = sectionNode(sectionIndex);
1737 if (sn.type != MonthSection) {
1738 qWarning(
"QDateTimeParser::findMonth Internal error");
1742 QLocale::FormatType type = sn.count == 3 ? QLocale::ShortFormat : QLocale::LongFormat;
1743 QLocale l = locale();
1744 ShortVector<QString> monthNames;
1745 monthNames.reserve(13 - startMonth);
1746 for (
int month = startMonth; month <= 12; ++month)
1747 monthNames.append(calendar.monthName(l, month, year, type));
1749 const int index = findTextEntry(str, monthNames, usedMonth, used);
1750 return index < 0 ? index : index + startMonth;
1753int QDateTimeParser::findDay(QStringView str,
int startDay,
int sectionIndex, QString *usedDay,
int *used)
const
1755 const SectionNode &sn = sectionNode(sectionIndex);
1756 if (!(sn.type & DaySectionMask)) {
1757 qWarning(
"QDateTimeParser::findDay Internal error");
1761 QLocale::FormatType type = sn.count == 4 ? QLocale::LongFormat : QLocale::ShortFormat;
1762 QLocale l = locale();
1763 ShortVector<QString> daysOfWeek;
1764 daysOfWeek.reserve(8 - startDay);
1765 for (
int day = startDay; day <= 7; ++day)
1766 daysOfWeek.append(l.dayName(day, type));
1768 const int index = findTextEntry(str, daysOfWeek, usedDay, used);
1769 return index < 0 ? index : index + startDay;
1773
1774
1775
1776
1777
1778
1779
1780QDateTimeParser::ParsedSection QDateTimeParser::findUtcOffset(QStringView str,
int mode)
const
1782 Q_ASSERT(mode > 0 && mode < 4);
1783 const bool startsWithUtc = str.startsWith(
"UTC"_L1);
1785 if (startsWithUtc) {
1787 return ParsedSection();
1788 str = str.sliced(3);
1790 return ParsedSection(Acceptable, 0, 3);
1793 const bool negativeSign = str.startsWith(u'-');
1795 if (!negativeSign && !str.startsWith(u'+'))
1796 return ParsedSection();
1797 str = str.sliced(1);
1799 const int colonPosition = str.indexOf(u':');
1801 bool hasColon = (colonPosition >= 0 && colonPosition < 3);
1804 const int digits = hasColon ? colonPosition + 3 : 4;
1806 for (
const int offsetLength = qMin(qsizetype(digits), str.size()); i < offsetLength; ++i) {
1807 if (i != colonPosition && !str.at(i).isDigit())
1810 const int hoursLength = qMin(i, hasColon ? colonPosition : 2);
1811 if (hoursLength < 1)
1812 return ParsedSection();
1816 if (!startsWithUtc && hoursLength != 2)
1817 return ParsedSection();
1821 if (mode == (hasColon ? 2 : 3))
1822 return ParsedSection();
1826 const int hours = str.first(hoursLength).toInt(&isInt);
1828 return ParsedSection();
1829 const QStringView minutesStr = str.mid(hasColon ? colonPosition + 1 : 2, 2);
1830 const int minutes = minutesStr.isEmpty() ? 0 : minutesStr.toInt(&isInt);
1832 return ParsedSection();
1837 const State status = (hours > 14 || minutes >= 60) ? Invalid
1838 : (hours == 14 && minutes > 0) ? Intermediate : Acceptable;
1840 int offset = 3600 * hours + 60 * minutes;
1845 const int usedSymbols = (startsWithUtc ? 3 : 0) + 1 + hoursLength + (hasColon ? 1 : 0)
1846 + minutesStr.size();
1848 return ParsedSection(status, offset, usedSymbols);
1852
1853
1854
1855
1856
1857
1858QDateTimeParser::ParsedSection
1859QDateTimeParser::findTimeZoneName(QStringView str,
const QDateTime &when)
const
1861 const int systemLength = startsWithLocalTimeZone(str, when, locale());
1862#if QT_CONFIG(timezone)
1865 const auto invalidZoneNameCharacter = [] (
const QChar &c) {
1866 const auto cu = c.unicode();
1867 return cu >= 127u || !(memchr(
"+-./:_",
char(cu), 6) || c.isLetterOrNumber());
1869 int index = std::distance(str.cbegin(),
1870 std::find_if(str.cbegin(), str.cend(), invalidZoneNameCharacter));
1877 Q_ASSERT(index <= str.size());
1878 while (lastSlash < index) {
1879 int slash = str.indexOf(u'/', lastSlash + 1);
1880 if (slash < 0 || slash > index)
1882 else if (++count > 5)
1884 if (slash - lastSlash > 20)
1885 index = lastSlash + 20;
1891 for (QStringView copy = str; index > systemLength; --index) {
1892 copy.truncate(index);
1893 QTimeZone zone(copy.toLatin1());
1895 return ParsedSection(Acceptable, zone.offsetFromUtc(when), index);
1899 if (
auto found = findZoneByLongName(str, locale(), when); found.isValid())
1900 return ParsedSection(Acceptable, found.zone.offsetFromUtc(when), found.nameLength);
1902 if (systemLength > 0)
1903 return ParsedSection(Acceptable, when.toLocalTime().offsetFromUtc(), systemLength);
1904 return ParsedSection();
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919QDateTimeParser::ParsedSection
1920QDateTimeParser::findTimeZone(QStringView str,
const QDateTime &when,
1921 int maxVal,
int minVal,
int mode)
const
1923 Q_ASSERT(mode > 0 && mode <= 4);
1925 if (mode == 1 && str == u'Z')
1926 return ParsedSection(Acceptable, 0, 1);
1928 ParsedSection section;
1930 section = findUtcOffset(str, mode);
1931 if (mode != 2 && mode != 3 && section.used <= 0)
1932 section = findTimeZoneName(str, when);
1934 if (section.state == Acceptable && (section.value < minVal || section.value > maxVal))
1935 section.state = Intermediate;
1936 if (section.used > 0)
1941 if (str.startsWith(
"UTC"_L1))
1942 return ParsedSection(Acceptable, 0, 3);
1943 if (str.startsWith(u'Z'))
1944 return ParsedSection(Acceptable, 0, 1);
1947 return ParsedSection();
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964QDateTimeParser::AmPmFinder QDateTimeParser::findAmPm(QString &str,
int sectionIndex,
int *used)
const
1966 const SectionNode &s = sectionNode(sectionIndex);
1967 if (s.type != AmPmSection) {
1968 qWarning(
"QDateTimeParser::findAmPm Internal error");
1973 if (QStringView(str).trimmed().isEmpty())
1974 return PossibleBoth;
1976 const QLatin1Char space(
' ');
1977 int size = sectionMaxSize(sectionIndex);
1984 ampm[amindex] = getAmPmText(AmText, Case(s.count));
1985 ampm[pmindex] = getAmPmText(PmText, Case(s.count));
1986 for (
int i = 0; i < 2; ++i)
1987 ampm[i].truncate(size);
1989 QDTPDEBUG <<
"findAmPm" << str << ampm[0] << ampm[1];
1991 if (str.startsWith(ampm[amindex], Qt::CaseInsensitive)) {
1992 str = ampm[amindex];
1994 }
else if (str.startsWith(ampm[pmindex], Qt::CaseInsensitive)) {
1995 str = ampm[pmindex];
1997 }
else if (context == FromString || (str.count(space) == 0 && str.size() >= size)) {
2000 size = qMin(size, str.size());
2002 bool broken[2] = {
false,
false};
2003 for (
int i=0; i<size; ++i) {
2004 const QChar ch = str.at(i);
2006 for (
int j=0; j<2; ++j) {
2008 int index = ampm[j].indexOf(ch);
2010 <<
"in" << ampm[j] <<
"and got" << index;
2012 if (ch.category() == QChar::Letter_Uppercase) {
2013 index = ampm[j].indexOf(ch.toLower());
2014 QDTPDEBUG <<
"trying with" << ch.toLower()
2015 <<
"in" << ampm[j] <<
"and got" << index;
2016 }
else if (ch.category() == QChar::Letter_Lowercase) {
2017 index = ampm[j].indexOf(ch.toUpper());
2018 QDTPDEBUG <<
"trying with" << ch.toUpper()
2019 <<
"in" << ampm[j] <<
"and got" << index;
2023 if (broken[amindex] && broken[pmindex]) {
2029 str[i] = ampm[j].at(index);
2032 ampm[j].remove(index, 1);
2037 if (!broken[pmindex] && !broken[amindex])
2038 return PossibleBoth;
2039 return (!broken[amindex] ? PossibleAM : PossiblePM);
2043
2044
2045
2047int QDateTimeParser::SectionNode::maxChange()
const
2051 case MSecSection:
return 999;
2052 case SecondSection:
return 59 * 1000;
2053 case MinuteSection:
return 59 * 60 * 1000;
2054 case Hour24Section:
case Hour12Section:
return 59 * 60 * 60 * 1000;
2057 case DayOfWeekSectionShort:
2058 case DayOfWeekSectionLong:
return 7;
2059 case DaySection:
return 30;
2060 case MonthSection:
return 365 - 31;
2061 case YearSection:
return 9999 * 365;
2062 case YearSection2Digits:
return 100 * 365;
2064 qWarning(
"QDateTimeParser::maxChange() Internal error (%ls)",
2065 qUtf16Printable(name()));
2071QDateTimeParser::FieldInfo QDateTimeParser::fieldInfo(
int index)
const
2074 const SectionNode &sn = sectionNode(index);
2083 case YearSection2Digits:
2084 ret |= AllowPartial;
2098 ret |= (Numeric|AllowPartial);
2102 case DayOfWeekSectionShort:
2103 case DayOfWeekSectionLong:
2109 if (getAmPmText(AmText, Case(sn.count)).size()
2110 == getAmPmText(PmText, Case(sn.count)).size()) {
2115 case TimeZoneSection:
2118 qWarning(
"QDateTimeParser::fieldInfo Internal error 2 (%d %ls %d)",
2119 index, qUtf16Printable(sn.name()), sn.count);
2125QString QDateTimeParser::SectionNode::format()
const
2129 case AmPmSection:
return count == 1 ?
"ap"_L1 : count == 2 ?
"AP"_L1 :
"Ap"_L1;
2130 case MSecSection: fillChar = u'z';
break;
2131 case SecondSection: fillChar = u's';
break;
2132 case MinuteSection: fillChar = u'm';
break;
2133 case Hour24Section: fillChar = u'H';
break;
2134 case Hour12Section: fillChar = u'h';
break;
2135 case DayOfWeekSectionShort:
2136 case DayOfWeekSectionLong:
2137 case DaySection: fillChar = u'd';
break;
2138 case MonthSection: fillChar = u'M';
break;
2139 case YearSection2Digits:
2140 case YearSection: fillChar = u'y';
break;
2142 qWarning(
"QDateTimeParser::sectionFormat Internal error (%ls)",
2143 qUtf16Printable(name(type)));
2146 if (fillChar.isNull()) {
2147 qWarning(
"QDateTimeParser::sectionFormat Internal error 2");
2150 return QString(count, fillChar);
2155
2156
2157
2158
2159
2161bool QDateTimeParser::potentialValue(QStringView str,
int min,
int max,
int index,
2162 const QDateTime ¤tValue,
int insert)
const
2167 const int size = sectionMaxSize(index);
2168 int val = (
int)locale().toUInt(str);
2169 const SectionNode &sn = sectionNode(index);
2170 if (sn.type == YearSection2Digits) {
2171 const int year = currentValue.date().year(calendar);
2172 val += year - (year % 100);
2174 if (val >= min && val <= max && str.size() == size)
2176 if (val > max || (str.size() == size && val < min))
2179 const int len = size - str.size();
2180 for (
int i=0; i<len; ++i) {
2181 for (
int j=0; j<10; ++j) {
2182 if (potentialValue(str + QLatin1Char(
'0' + j), min, max, index, currentValue, insert)) {
2184 }
else if (insert >= 0) {
2185 const QString tmp = str.left(insert) + QLatin1Char(
'0' + j) + str.mid(insert);
2186 if (potentialValue(tmp, min, max, index, currentValue, insert))
2196
2197
2198bool QDateTimeParser::skipToNextSection(
int index,
const QDateTime ¤t, QStringView text)
const
2200 Q_ASSERT(text.size() < sectionMaxSize(index));
2201 const SectionNode &node = sectionNode(index);
2202 int min = absoluteMin(index);
2203 int max = absoluteMax(index, current);
2205 if (node.type != TimeZoneSection || current.timeSpec() == Qt::OffsetFromUTC) {
2206 const QDateTime maximum = getMaximum(current.timeRepresentation());
2207 const QDateTime minimum = getMinimum(current.timeRepresentation());
2213 QDateTime tmp = current;
2214 if (!setDigit(tmp, index, min) || tmp < minimum)
2215 min = getDigit(minimum, index);
2217 if (!setDigit(tmp, index, max) || tmp > maximum)
2218 max = getDigit(maximum, index);
2220 int pos = cursorPosition() - node.pos;
2221 if (pos < 0 || pos >= text.size())
2225
2226
2227
2228
2229
2230 return !potentialValue(text, min, max, index, current, pos);
2234
2235
2236
2238QString QDateTimeParser::SectionNode::name(QDateTimeParser::Section s)
2241 case AmPmSection:
return "AmPmSection"_L1;
2242 case DaySection:
return "DaySection"_L1;
2243 case DayOfWeekSectionShort:
return "DayOfWeekSectionShort"_L1;
2244 case DayOfWeekSectionLong:
return "DayOfWeekSectionLong"_L1;
2245 case Hour24Section:
return "Hour24Section"_L1;
2246 case Hour12Section:
return "Hour12Section"_L1;
2247 case MSecSection:
return "MSecSection"_L1;
2248 case MinuteSection:
return "MinuteSection"_L1;
2249 case MonthSection:
return "MonthSection"_L1;
2250 case SecondSection:
return "SecondSection"_L1;
2251 case TimeZoneSection:
return "TimeZoneSection"_L1;
2252 case YearSection:
return "YearSection"_L1;
2253 case YearSection2Digits:
return "YearSection2Digits"_L1;
2254 case NoSection:
return "NoSection"_L1;
2255 case FirstSection:
return "FirstSection"_L1;
2256 case LastSection:
return "LastSection"_L1;
2257 default:
return "Unknown section "_L1 + QString::number(
int(s));
2262
2263
2264
2266QString QDateTimeParser::stateName(State s)
const
2269 case Invalid:
return "Invalid"_L1;
2270 case Intermediate:
return "Intermediate"_L1;
2271 case Acceptable:
return "Acceptable"_L1;
2272 default:
return "Unknown state "_L1 + QString::number(s);
2278
2279
2280
2281QDateTime QDateTimeParser::baseDate(
const QTimeZone &zone)
const
2283 QDateTime when = QDate(defaultCenturyStart, 1, 1).startOfDay(zone);
2284 if (
const QDateTime start = getMinimum(zone); when < start)
2286 if (
const QDateTime end = getMaximum(zone); when > end)
2292bool QDateTimeParser::fromString(
const QString &t, QDate *date, QTime *time,
int baseYear)
const
2294 defaultCenturyStart = baseYear;
2295 const StateNode tmp = parse(t, -1, baseDate(QTimeZone::UTC),
false);
2296 if (tmp.state != Acceptable || tmp.conflicts)
2301 const QTime t = tmp.value.time();
2309 const QDate d = tmp.value.date();
2318bool QDateTimeParser::fromString(
const QString &t, QDateTime *datetime,
int baseYear)
const
2320 defaultCenturyStart = baseYear;
2321 const StateNode tmp = parse(t, -1, baseDate(QTimeZone::LocalTime),
false);
2323 *datetime = tmp.value;
2324 return tmp.state >= Intermediate && !tmp.conflicts && tmp.value.isValid();
2327QDateTime QDateTimeParser::getMinimum(
const QTimeZone &zone)
const
2334 static const QDateTime localTimeMin(QDATETIMEEDIT_DATE_MIN.startOfDay());
2335 static const QDateTime utcTimeMin = localTimeMin.toUTC();
2336 switch (zone.timeSpec()) {
2338 return localTimeMin;
2341 case Qt::OffsetFromUTC:
2345 return utcTimeMin.toTimeZone(zone);
2348QDateTime QDateTimeParser::getMaximum(
const QTimeZone &zone)
const
2355 static const QDateTime localTimeMax(QDATETIMEEDIT_DATE_MAX.endOfDay());
2356 static const QDateTime utcTimeMax = localTimeMax.toUTC();
2357 switch (zone.timeSpec()) {
2359 return localTimeMax;
2362 case Qt::OffsetFromUTC:
2366 return utcTimeMax.toTimeZone(zone);
2369QString QDateTimeParser::getAmPmText(AmPm ap, Case cs)
const
2371 const QLocale loc = locale();
2372 QString raw = ap == AmText ? loc.amText() : loc.pmText();
2375 case UpperCase:
return std::move(raw).toUpper();
2376 case LowerCase:
return std::move(raw).toLower();
2377 case NativeCase:
return raw;
2379 Q_UNREACHABLE_RETURN(raw);
2383
2384
2386bool operator==(QDateTimeParser::SectionNode s1, QDateTimeParser::SectionNode s2)
2388 return (s1.type == s2.type) && (s1.pos == s2.pos) && (s1.count == s2.count);
2392
2393
2395void QDateTimeParser::setCalendar(QCalendar cal)
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