5#include "private/qdatetimeparser_p.h"
8#include "private/qdatetime_p.h"
11#include "private/qlocale_p.h"
12#include "private/qlocale_tools_p.h"
14#include "private/qstringiterator_p.h"
15#include "private/qtenvironmentvariables_p.h"
17#if QT_CONFIG(timezone)
18#include "private/qtimezoneprivate_p.h"
24#if defined (QDATETIMEPARSER_DEBUG) && !defined(QT_NO_DEBUG_STREAM)
25# define QDTPDEBUG qDebug()
26# define QDTPDEBUGN qDebug
28# define QDTPDEBUG if (false) qDebug()
29# define QDTPDEBUGN if (false) qDebug
34constexpr int QDateTimeParser::NoSectionIndex;
35constexpr int QDateTimeParser::FirstSectionIndex;
36constexpr int QDateTimeParser::LastSectionIndex;
38using namespace Qt::StringLiterals;
43QDateTimeParser::~QDateTimeParser()
48
49
50
51
52
53
54
56int QDateTimeParser::getDigit(
const QDateTime &t,
int index)
const
58 if (index < 0 || index >= sectionNodes.size()) {
59 qWarning(
"QDateTimeParser::getDigit() Internal error (%ls %d)",
60 qUtf16Printable(t.toString()), index);
63 const SectionNode &node = sectionNodes.at(index);
65 case TimeZoneSection:
return t.offsetFromUtc();
66 case Hour24Section:
case Hour12Section:
return t.time().hour();
67 case MinuteSection:
return t.time().minute();
68 case SecondSection:
return t.time().second();
69 case MSecSection:
return t.time().msec();
70 case YearSection2Digits:
71 case YearSection:
return t.date().year(calendar);
72 case MonthSection:
return t.date().month(calendar);
73 case DaySection:
return t.date().day(calendar);
74 case DayOfWeekSectionShort:
75 case DayOfWeekSectionLong:
return calendar.dayOfWeek(t.date());
76 case AmPmSection:
return t.time().hour() > 11 ? 1 : 0;
81 qWarning(
"QDateTimeParser::getDigit() Internal error 2 (%ls %d)",
82 qUtf16Printable(t.toString()), index);
87
88
89
90
91
92
93
97 const int diff = sought - held;
98 return diff < -3 ? diff + 7 : diff > 3 ? diff - 7 : diff;
105 for (
const auto &node : nodes) {
106 if (node.type & QDateTimeParser::DaySection)
108 if (node.type & QDateTimeParser::DayOfWeekSectionMask)
115
116
117
118
119
120
121
122
123
124
126bool QDateTimeParser::setDigit(QDateTime &v,
int index,
int newVal)
const
128 if (index < 0 || index >= sectionNodes.size()) {
129 qWarning(
"QDateTimeParser::setDigit() Internal error (%ls %d %d)",
130 qUtf16Printable(v.toString()), index, newVal);
134 const QDate oldDate = v.date();
135 QCalendar::YearMonthDay date = calendar.partsFromDate(oldDate);
138 int weekDay = calendar.dayOfWeek(oldDate);
139 enum { NoFix, MonthDay, WeekDay } fixDay = NoFix;
141 const QTime time = v.time();
142 int hour = time.hour();
143 int minute = time.minute();
144 int second = time.second();
145 int msec = time.msec();
146 QTimeZone timeZone = v.timeRepresentation();
148 const SectionNode &node = sectionNodes.at(index);
150 case Hour24Section:
case Hour12Section: hour = newVal;
break;
151 case MinuteSection: minute = newVal;
break;
152 case SecondSection: second = newVal;
break;
153 case MSecSection: msec = newVal;
break;
154 case YearSection2Digits:
155 case YearSection: date.year = newVal;
break;
156 case MonthSection: date.month = newVal;
break;
167 case DayOfWeekSectionShort:
168 case DayOfWeekSectionLong:
169 if (newVal > 7 || newVal <= 0)
171 date.day += dayOfWeekDiff(newVal, weekDay);
175 case TimeZoneSection:
176 if (newVal < absoluteMin(index) || newVal > absoluteMax(index))
179 timeZone = QTimeZone::fromSecondsAheadOfUtc(newVal);
181 case AmPmSection: hour = (newVal == 0 ? hour % 12 : (hour % 12) + 12);
break;
183 qWarning(
"QDateTimeParser::setDigit() Internal error (%ls)",
184 qUtf16Printable(node.name()));
188 if (!(node.type & DaySectionMask)) {
189 if (date.day < cachedDay)
190 date.day = cachedDay;
192 if (weekDay > 0 && weekDay <= 7 && preferDayOfWeek(sectionNodes)) {
193 const int max = calendar.daysInMonth(date.month, date.year);
194 if (max > 0 && date.day > max)
196 const int newDoW = calendar.dayOfWeek(calendar.dateFromParts(date));
197 if (newDoW > 0 && newDoW <= 7)
198 date.day += dayOfWeekDiff(weekDay, newDoW);
203 if (fixDay != NoFix) {
204 const int max = calendar.daysInMonth(date.month, date.year);
206 if (max > 0 && date.day > max)
207 date.day = fixDay == WeekDay ? date.day - 7 : max;
208 else if (date.day < 1)
209 date.day = fixDay == WeekDay ? date.day + 7 : 1;
210 Q_ASSERT(fixDay != WeekDay
211 || calendar.dayOfWeek(calendar.dateFromParts(date)) == weekDay);
214 const QDate newDate = calendar.dateFromParts(date);
215 const QTime newTime(hour, minute, second, msec);
216 if (!newDate.isValid() || !newTime.isValid())
219 v = QDateTime(newDate, newTime, timeZone);
226
227
228
229
231int QDateTimeParser::absoluteMax(
int s,
const QDateTime &cur)
const
233 const SectionNode &sn = sectionNode(s);
235 case TimeZoneSection:
236 return QTimeZone::MaxUtcOffsetSecs;
247 case YearSection2Digits:
253 return calendar.maximumMonthsInYear();
255 return cur.isValid() ? cur.date().daysInMonth(calendar) : calendar.maximumDaysInMonth();
256 case DayOfWeekSectionShort:
257 case DayOfWeekSectionLong:
260 return int(UpperCase);
264 qWarning(
"QDateTimeParser::absoluteMax() Internal error (%ls)",
265 qUtf16Printable(sn.name()));
270
271
272
273
275int QDateTimeParser::absoluteMin(
int s)
const
277 const SectionNode &sn = sectionNode(s);
279 case TimeZoneSection:
280 return QTimeZone::MinUtcOffsetSecs;
286 case YearSection2Digits:
292 case DayOfWeekSectionShort:
293 case DayOfWeekSectionLong:
return 1;
294 case AmPmSection:
return int(NativeCase);
297 qWarning(
"QDateTimeParser::absoluteMin() Internal error (%ls, %0x)",
298 qUtf16Printable(sn.name()), sn.type);
303
304
305
306
308const QDateTimeParser::SectionNode &QDateTimeParser::sectionNode(
int sectionIndex)
const
310 static constexpr SectionNode first{FirstSection, 0, -1, 0};
311 static constexpr SectionNode last{LastSection, -1, -1, 0};
312 static constexpr SectionNode none{NoSection, -1, -1, 0};
313 if (sectionIndex < 0) {
314 switch (sectionIndex) {
315 case FirstSectionIndex:
317 case LastSectionIndex:
322 }
else if (sectionIndex < sectionNodes.size()) {
323 return sectionNodes.at(sectionIndex);
326 qWarning(
"QDateTimeParser::sectionNode() Internal error (%d)",
331QDateTimeParser::Section QDateTimeParser::sectionType(
int sectionIndex)
const
333 return sectionNode(sectionIndex).type;
338
339
340
341
343int QDateTimeParser::sectionPos(
int sectionIndex)
const
345 return sectionPos(sectionNode(sectionIndex));
348int QDateTimeParser::sectionPos(SectionNode sn)
const
351 case FirstSection:
return 0;
352 case LastSection:
return displayText().size() - 1;
356 qWarning(
"QDateTimeParser::sectionPos Internal error (%ls)", qUtf16Printable(sn.name()));
363
364
365
366
370 qsizetype digits = 0;
371 for (QStringIterator it(str); it.hasNext();) {
372 if (!QChar::isDigit(it.next()))
380
381
382
383
384
385
389 const QLatin1Char quote(
'\'');
390 const QLatin1Char slash(
'\\');
391 const QLatin1Char zero(
'0');
394 const int max = str.size();
395 for (
int i=0; i<max; ++i) {
396 if (str.at(i) == quote) {
399 else if (!ret.isEmpty() && str.at(i - 1) == slash)
400 ret[ret.size() - 1] = quote;
410static inline int countRepeat(QStringView str,
int index,
int maxCount)
412 str = str.sliced(index);
413 if (maxCount < str.size())
414 str = str.first(maxCount);
416 return qt_repeatCount(str);
420 int from,
int size,
int lastQuote)
422 Q_ASSERT(size >= 0 && from + size <= string.size());
423 const QStringView separator = string.sliced(from, size);
424 list->append(lastQuote >= from ? unquote(separator) : separator.toString());
428
429
430
431
432
433bool QDateTimeParser::parseFormat(QStringView newFormat)
435 const QLatin1Char quote(
'\'');
436 const QLatin1Char slash(
'\\');
437 const QLatin1Char zero(
'0');
438 if (newFormat == displayFormat && !newFormat.isEmpty())
441 QDTPDEBUGN(
"parseFormat: %s", newFormat.toLatin1().constData());
443 QList<SectionNode> newSectionNodes;
445 QStringList newSeparators;
448 QLatin1Char status(zero);
449 const int max = newFormat.size();
451 for (i = 0; i<max; ++i) {
452 if (newFormat.at(i) == quote) {
457 else if (i > 0 && newFormat.at(i - 1) != slash)
459 }
else if (status != quote) {
460 const char sect = newFormat.at(i).toLatin1();
464 if (parserType != QMetaType::QDate) {
465 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
466 const Section hour = (sect ==
'h') ? Hour12Section : Hour24Section;
467 const SectionNode sn{hour, i - add, countRepeat(newFormat, i, 2)};
468 newSectionNodes.append(sn);
475 if (parserType != QMetaType::QDate) {
476 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
477 const SectionNode sn{MinuteSection, i - add, countRepeat(newFormat, i, 2)};
478 newSectionNodes.append(sn);
481 newDisplay |= MinuteSection;
485 if (parserType != QMetaType::QDate) {
486 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
487 const SectionNode sn{SecondSection, i - add, countRepeat(newFormat, i, 2)};
488 newSectionNodes.append(sn);
491 newDisplay |= SecondSection;
496 if (parserType != QMetaType::QDate) {
497 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
498 const int repeat = countRepeat(newFormat, i, 3);
499 const SectionNode sn{MSecSection, i - add, repeat < 3 ? 1 : 3};
500 newSectionNodes.append(sn);
503 newDisplay |= MSecSection;
508 if (parserType != QMetaType::QDate) {
509 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
510 const int pos = i - add;
511 Case caseOpt = sect ==
'A' ? UpperCase : LowerCase;
512 newDisplay |= AmPmSection;
513 if (i + 1 < newFormat.size()
514 && newFormat.sliced(i + 1).startsWith(u'p', Qt::CaseInsensitive)) {
516 if (newFormat.at(i) != QLatin1Char(caseOpt == UpperCase ?
'P' :
'p'))
517 caseOpt = NativeCase;
519 const SectionNode sn{AmPmSection, pos,
int(caseOpt)};
520 newSectionNodes.append(sn);
525 if (parserType != QMetaType::QTime) {
526 const int repeat = countRepeat(newFormat, i, 4);
528 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
529 const SectionNode sn{repeat == 4 ? YearSection : YearSection2Digits,
530 i - add, repeat == 4 ? 4 : 2};
531 newSectionNodes.append(sn);
534 newDisplay |= sn.type;
539 if (parserType != QMetaType::QTime) {
540 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
541 const SectionNode sn{MonthSection, i - add, countRepeat(newFormat, i, 4)};
542 newSectionNodes.append(sn);
545 newDisplay |= MonthSection;
549 if (parserType != QMetaType::QTime) {
550 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
551 const int repeat = countRepeat(newFormat, i, 4);
552 const Section sectionType = (repeat == 4 ? DayOfWeekSectionLong
553 : (repeat == 3 ? DayOfWeekSectionShort : DaySection));
554 const SectionNode sn{sectionType, i - add, repeat};
555 newSectionNodes.append(sn);
558 newDisplay |= sn.type;
562 if (parserType == QMetaType::QDateTime) {
563 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
564 const SectionNode sn{TimeZoneSection, i - add, countRepeat(newFormat, i, 4)};
565 newSectionNodes.append(sn);
568 newDisplay |= TimeZoneSection;
576 if (newSectionNodes.isEmpty() && context == DateTimeEdit)
579 if ((newDisplay & (AmPmSection|Hour12Section)) == Hour12Section) {
580 const int count = newSectionNodes.size();
581 for (
int i = 0; i < count; ++i) {
582 SectionNode &node = newSectionNodes[i];
583 if (node.type == Hour12Section)
584 node.type = Hour24Section;
589 appendSeparator(&newSeparators, newFormat, index, max - index, lastQuote);
591 newSeparators.append(QString());
593 displayFormat = newFormat.toString();
594 separators = newSeparators;
595 sectionNodes = newSectionNodes;
596 display = newDisplay;
603 QDTPDEBUGN(
"separators:\n'%s'", separators.join(
"\n"_L1).toLatin1().constData());
609
610
611
612
614int QDateTimeParser::sectionSize(
int sectionIndex)
const
616 if (sectionIndex < 0)
619 if (sectionIndex >= sectionNodes.size()) {
620 qWarning(
"QDateTimeParser::sectionSize Internal error (%d)", sectionIndex);
624 if (sectionIndex == sectionNodes.size() - 1) {
629 int sizeAdjustment = 0;
630 const int displayTextSize = displayText().size();
631 if (displayTextSize != m_text.size()) {
633 int preceedingZeroesAdded = 0;
634 if (sectionNodes.size() > 1 && context == DateTimeEdit) {
635 const auto begin = sectionNodes.cbegin();
636 const auto end = begin + sectionIndex;
637 for (
auto sectionIt = begin; sectionIt != end; ++sectionIt)
638 preceedingZeroesAdded += sectionIt->zeroesAdded;
640 sizeAdjustment = preceedingZeroesAdded;
643 return displayTextSize + sizeAdjustment - sectionPos(sectionIndex) - separators.last().size();
645 return sectionPos(sectionIndex + 1) - sectionPos(sectionIndex)
646 - separators.at(sectionIndex + 1).size();
651int QDateTimeParser::sectionMaxSize(Section s,
int count)
const
653#if QT_CONFIG(textdate)
654 int mcount = calendar.maximumMonthsInYear();
665 return qMax(getAmPmText(AmText, Case(count)).size(),
666 getAmPmText(PmText, Case(count)).size());
675 case DayOfWeekSectionShort:
676 case DayOfWeekSectionLong:
677#if QT_CONFIG(textdate)
682#if QT_CONFIG(textdate)
688 const QLocale l = locale();
689 const QLocale::FormatType format = count == 4 ? QLocale::LongFormat : QLocale::ShortFormat;
690 for (
int i=1; i<=mcount; ++i) {
691 const QString str = (s == MonthSection
692 ? calendar.monthName(l, i, QCalendar::Unspecified, format)
693 : l.dayName(i, format));
694 ret = qMax(str.size(), ret);
705 case YearSection2Digits:
707 case TimeZoneSection:
720 return std::numeric_limits<
int>::max();
722 case CalendarPopupSection:
724 case TimeSectionMask:
725 case DateSectionMask:
726 case HourSectionMask:
727 case YearSectionMask:
728 case DayOfWeekSectionMask:
730 qWarning(
"QDateTimeParser::sectionMaxSize: Invalid section %s",
731 SectionNode::name(s).toLatin1().constData());
738int QDateTimeParser::sectionMaxSize(
int index)
const
740 const SectionNode &sn = sectionNode(index);
741 return sectionMaxSize(sn.type, sn.count);
752 const auto isSimpleSpace = [](
char32_t ch) {
754 return ch == u' ' || (ch > 127 && QChar::isSpace(ch));
758 if (!text.startsWith(separator)) {
760 QStringIterator given(text), sep(separator);
761 while (sep.hasNext()) {
762 if (!given.hasNext())
764 char32_t s = sep.next(), g = given.next();
765 if (s != g && !(isSimpleSpace(s) && isSimpleSpace(g)))
769 return given.index();
771 return separator.size();
775
776
777
778
779
782QString QDateTimeParser::sectionText(
const QString &text,
int sectionIndex,
int index)
const
784 return text.mid(index, sectionSize(sectionIndex));
787QString QDateTimeParser::sectionText(
int sectionIndex)
const
789 const SectionNode &sn = sectionNode(sectionIndex);
790 return sectionText(displayText(), sectionIndex, sn.pos);
793QDateTimeParser::ParsedSection
794QDateTimeParser::parseSection(
const QDateTime ¤tValue,
int sectionIndex,
int offset)
const
796 ParsedSection result;
797 const SectionNode &sn = sectionNode(sectionIndex);
798 Q_ASSERT_X(!(sn.type & Internal),
799 "QDateTimeParser::parseSection",
"Internal error");
801 const int sectionmaxsize = sectionMaxSize(sectionIndex);
802 const bool negate = (sn.type == YearSection && m_text.size() > offset
803 && calendar.isProleptic() && m_text.at(offset) == u'-');
804 const int negativeYearOffset = negate ? 1 : 0;
806 QStringView sectionTextRef =
807 QStringView { m_text }.mid(offset + negativeYearOffset, sectionmaxsize);
809 QDTPDEBUG <<
"sectionValue for" << sn.name()
810 <<
"with text" << m_text <<
"and (at" << offset
811 <<
") st:" << sectionTextRef;
815 QString sectiontext = sectionTextRef.toString();
817 const int ampm = findAmPm(sectiontext, sectionIndex, &used);
821 result = ParsedSection(Acceptable, ampm, used);
825 result = ParsedSection(Intermediate, ampm - 2, used);
828 result = ParsedSection(Intermediate, 0, used);
831 QDTPDEBUG <<
"invalid because findAmPm(" << sectiontext <<
") returned -1";
834 QDTPDEBUGN(
"This should never happen (findAmPm returned %d)", ampm);
837 if (result.state != Invalid)
838 m_text.replace(offset, used, sectiontext.constData(), used);
840 case TimeZoneSection:
841 result = findTimeZone(sectionTextRef, currentValue,
842 absoluteMax(sectionIndex),
843 absoluteMin(sectionIndex), sn.count);
846 case DayOfWeekSectionShort:
847 case DayOfWeekSectionLong:
849 QString sectiontext = sectionTextRef.toString();
850 int num = 0, used = 0;
851 if (sn.type == MonthSection) {
852 const QDate minDate = getMinimum(currentValue.timeRepresentation()).date();
853 const int year = currentValue.date().year(calendar);
854 const int min = (year == minDate.year(calendar)) ? minDate.month(calendar) : 1;
855 num = findMonth(sectiontext.toLower(), min, sectionIndex, year, §iontext, &used);
857 num = findDay(sectiontext.toLower(), 1, sectionIndex, §iontext, &used);
860 result = ParsedSection(Intermediate, num, used);
862 m_text.replace(offset, used, sectiontext.constData(), used);
863 if (used == sectiontext.size())
864 result = ParsedSection(Acceptable, num, used);
872 case YearSection2Digits:
878 const auto checkSeparator = [&result, field=QStringView{m_text}.sliced(offset),
879 negativeYearOffset, sectionIndex,
this]() {
881 const auto &sep = separators.at(sectionIndex + 1);
882 if (matchesSeparator(field.sliced(negativeYearOffset), sep) != -1)
883 result = ParsedSection(Intermediate, 0, negativeYearOffset);
884 else if (negativeYearOffset && matchesSeparator(field, sep) != -1)
885 result = ParsedSection(Intermediate, 0, 0);
890 int used = negativeYearOffset;
893 if (sectionTextRef.startsWith(u'-')
894 || sectionTextRef.startsWith(u'+')) {
900 QStringView digitsStr = sectionTextRef.left(digitCount(sectionTextRef));
902 if (digitsStr.isEmpty()) {
903 result = ParsedSection(Intermediate, 0, used);
905 const QLocale loc = locale();
906 const int absMax = absoluteMax(sectionIndex);
907 const int absMin = absoluteMin(sectionIndex);
911 for (; digitsStr.size(); digitsStr.chop(1)) {
913 int value =
int(loc.toUInt(digitsStr, &ok));
914 if (!ok || (negate ? -value < absMin : value > absMax))
917 if (sn.type == Hour12Section) {
924 QDTPDEBUG << digitsStr << value << digitsStr.size();
926 used += digitsStr.size();
931 if (!checkSeparator()) {
932 QDTPDEBUG <<
"invalid because" << sectionTextRef <<
"can't become a uint"
938 const FieldInfo fi = fieldInfo(sectionIndex);
939 const bool unfilled = used - negativeYearOffset < sectionmaxsize;
940 if (unfilled && fi & Fraction) {
941 for (
int i = used; i < sectionmaxsize; ++i)
945 Q_ASSERT(negate ? lastVal >= absMin : lastVal <= absMax);
946 if (negate ? lastVal > absMax : lastVal < absMin) {
948 result = ParsedSection(Intermediate, lastVal, used);
950 QDTPDEBUG <<
"invalid because" << lastVal <<
"is greater than absoluteMax"
953 QDTPDEBUG <<
"invalid because" << lastVal <<
"is less than absoluteMin"
957 }
else if (unfilled && (fi & (FixedWidth | Numeric)) == (FixedWidth | Numeric)) {
958 if (skipToNextSection(sectionIndex, currentValue, digitsStr)) {
959 const int missingZeroes = sectionmaxsize - digitsStr.size();
960 result = ParsedSection(Acceptable, lastVal, sectionmaxsize, missingZeroes);
961 m_text.insert(offset, QString(missingZeroes, u'0'));
962 ++(
const_cast<QDateTimeParser*>(
this)->sectionNodes[sectionIndex].zeroesAdded);
964 result = ParsedSection(Intermediate, lastVal, used);
966 }
else if (!lastVal && !calendar.hasYearZero()
967 && (sn.type == YearSection
968 || (sn.type == YearSection2Digits && currentValue.isValid()
969 && currentValue.date().year(calendar) / 100 == 0))) {
971 result = ParsedSection(unfilled ? Acceptable : Invalid, lastVal, used);
973 result = ParsedSection(Acceptable, lastVal, used);
979 qWarning(
"QDateTimeParser::parseSection Internal error (%ls %d)",
980 qUtf16Printable(sn.name()), sectionIndex);
983 Q_ASSERT(result.state != Invalid || result.value == -1);
989
990
991
992
993
994
1000 const int maxDay = calendar.daysInMonth(month, year);
1001 day = maxDay > 1 ? qBound(1, day, maxDay) : qMax(1, day);
1002 day += dayOfWeekDiff(weekDay, calendar.dayOfWeek(QDate(year, month, day, calendar)));
1003 return day <= 0 ? day + 7 : maxDay > 0 && day > maxDay ? day - 7 : day;
1007
1008
1009
1012 Q_ASSERT(0 <= y2d && y2d < 100);
1013 const int year = baseYear - baseYear % 100 + y2d;
1014 return year < baseYear ? year + 100 : year;
1018
1019
1020
1021
1022
1023
1025static QDate
actualDate(QDateTimeParser::Sections known, QCalendar calendar,
int baseYear,
1026 int year,
int year2digits,
int month,
int day,
int dayofweek)
1028 QDate actual(year, month, day, calendar);
1029 if (actual.isValid() && year % 100 == year2digits && calendar.dayOfWeek(actual) == dayofweek)
1032 if (dayofweek < 1 || dayofweek > 7)
1033 known &= ~QDateTimeParser::DayOfWeekSectionMask;
1036 if (year % 100 != year2digits) {
1037 if (known & QDateTimeParser::YearSection2Digits) {
1042 known &= ~QDateTimeParser::YearSection;
1044 year2digits = year % 100;
1047 Q_ASSERT(year % 100 == year2digits);
1051 known &= ~QDateTimeParser::MonthSection;
1052 }
else if (month > 12) {
1054 known &= ~QDateTimeParser::MonthSection;
1056 if (!actual.isValid() && !known.testAnyFlag(QDateTimeParser::YearSectionMask)
1057 && known.testFlags(QDateTimeParser::DaySection | QDateTimeParser::MonthSection)
1058 && !calendar.isLeapYear(year) && day > calendar.daysInMonth(month, year)) {
1060 int leap = year + 1, stop = year + 47;
1063 while (!calendar.isLeapYear(leap) && leap < stop)
1065 if (day <= calendar.daysInMonth(month, leap))
1069 QDate first(year, month, 1, calendar);
1070 int last = known & QDateTimeParser::MonthSection
1071 ? (known.testAnyFlag(QDateTimeParser::YearSectionMask)
1072 ? calendar.daysInMonth(month, year) : calendar.daysInMonth(month))
1075 const bool fixDayOfWeek = last && known & QDateTimeParser::YearSection
1076 && known & QDateTimeParser::DayOfWeekSectionMask;
1079 const int diff = (dayofweek - calendar.dayOfWeek(first) - last) % 7;
1080 Q_ASSERT(diff <= 0);
1085 day = 1 + dayofweek - calendar.dayOfWeek(first);
1091 known &= ~QDateTimeParser::DaySection;
1092 }
else if (day > calendar.maximumDaysInMonth()) {
1094 known &= ~QDateTimeParser::DaySection;
1095 }
else if (last && day > last && (known & QDateTimeParser::DaySection) == 0) {
1099 actual = QDate(year, month, day, calendar);
1100 if (!actual.isValid()
1101 || (known & QDateTimeParser::DaySection
1102 && known & QDateTimeParser::MonthSection
1103 && known & QDateTimeParser::YearSection)
1104 || calendar.dayOfWeek(actual) == dayofweek
1105 || (known & QDateTimeParser::DayOfWeekSectionMask) == 0) {
1110
1111
1112
1113
1114
1115
1117 if ((known & QDateTimeParser::DaySection) == 0) {
1119 day = weekDayWithinMonth(calendar, year, month, day, dayofweek);
1120 actual = QDate(year, month, day, calendar);
1124 if ((known & QDateTimeParser::MonthSection) == 0) {
1126
1127
1128
1129
1130 for (
int m = 1; m < 12; m++) {
1132 actual = QDate(year, month - m, day, calendar);
1133 if (calendar.dayOfWeek(actual) == dayofweek)
1136 if (m + month <= 12) {
1137 actual = QDate(year, month + m, day, calendar);
1138 if (calendar.dayOfWeek(actual) == dayofweek)
1143 actual = QDate(year, month, day, calendar);
1146 if ((known & QDateTimeParser::YearSection) == 0) {
1147 if (known & QDateTimeParser::YearSection2Digits) {
1148 actual = calendar.matchCenturyToWeekday({year, month, day}, dayofweek);
1149 if (actual.isValid()) {
1150 Q_ASSERT(calendar.dayOfWeek(actual) == dayofweek);
1155 for (
int y = 1; y < 12; y++) {
1156 actual = QDate(year - y, month, day, calendar);
1157 if (calendar.dayOfWeek(actual) == dayofweek)
1159 actual = QDate(year + y, month, day, calendar);
1160 if (calendar.dayOfWeek(actual) == dayofweek)
1164 actual = QDate(year, month, day, calendar);
1171
1172
1175 int hour,
int hour12,
int ampm,
1176 int minute,
int second,
int msec)
1179 QTime actual(hour, minute, second, msec);
1180 if (hour12 < 0 || hour12 > 12) {
1181 known &= ~QDateTimeParser::Hour12Section;
1185 if (ampm == -1 || (known & QDateTimeParser::AmPmSection) == 0) {
1186 if ((known & QDateTimeParser::Hour12Section) == 0 || hour % 12 == hour12)
1189 if ((known & QDateTimeParser::Hour24Section) == 0)
1190 hour = hour12 + (hour > 12 ? 12 : 0);
1192 Q_ASSERT(ampm == 0 || ampm == 1);
1193 if (hour - hour12 == ampm * 12)
1196 if ((known & QDateTimeParser::Hour24Section) == 0
1197 && known & QDateTimeParser::Hour12Section) {
1198 hour = hour12 + ampm * 12;
1201 actual = QTime(hour, minute, second, msec);
1206
1207
1211 qsizetype longest = 0;
1213 for (
int i = 0; i < 2; ++i) {
1214 const QString zone(qTzName(i));
1215 if (zone.size() > longest && name.startsWith(zone))
1216 longest = zone.size();
1219 const auto consider = [name, &longest](QStringView zone) {
1220 if (name.startsWith(zone)) {
1222 if (9 > longest && zone.size() == 6 && zone.startsWith(
"UTC"_L1)
1223 && name.sliced(6, 3) ==
":00"_L1) {
1225 }
else if (zone.size() > longest) {
1226 longest = zone.size();
1230#if QT_CONFIG(timezone)
1232
1233
1235 const auto localWhen = QDateTime(when.date(), when.time());
1236 consider(localWhen.timeRepresentation().displayName(
1237 localWhen, QTimeZone::ShortName, locale));
1242 consider(QDateTime(when.date(), when.time()).timeZoneAbbreviation());
1243 Q_ASSERT(longest <= INT_MAX);
1244 return int(longest);
1247#if QT_CONFIG(timezone)
1248static auto findZoneByLongName(QStringView str,
const QLocale &locale,
const QDateTime &when)
1253 qsizetype nameLength = 0;
1254 bool isValid()
const {
return nameLength > 0 && zone.isValid(); }
1256 auto pfx = QTimeZonePrivate::findLongNamePrefix(str, locale, when.toMSecsSinceEpoch());
1258 pfx = QTimeZonePrivate::findLongNamePrefix(str, locale);
1262 pfx = QTimeZonePrivate::findNarrowOffsetPrefix(str, locale);
1264 pfx = QTimeZonePrivate::findLongUtcPrefix(str);
1266 result = R{ pfx.offset
1267 ? QTimeZone::fromSecondsAheadOfUtc(*pfx.offset)
1268 : QTimeZone(pfx.ianaId),
1270 Q_ASSERT(result.zone.isValid());
1278
1279
1280QDateTimeParser::StateNode
1281QDateTimeParser::scanString(
const QDateTime &defaultValue,
bool fixup)
const
1283 State state = Acceptable;
1284 bool conflicts =
false;
1285 const int sectionNodesCount = sectionNodes.size();
1288 int year, month, day;
1289 const QDate defaultDate = defaultValue.date();
1290 const QTime defaultTime = defaultValue.time();
1291 defaultDate.getDate(&year, &month, &day);
1292 int year2digits = year % 100;
1293 int hour = defaultTime.hour();
1295 int minute = defaultTime.minute();
1296 int second = defaultTime.second();
1297 int msec = defaultTime.msec();
1298 int dayofweek = calendar.dayOfWeek(defaultDate);
1299 QTimeZone timeZone = defaultValue.timeRepresentation();
1302 Sections isSet = NoSection;
1304 for (
int index = 0; index < sectionNodesCount; ++index) {
1305 Q_ASSERT(state != Invalid);
1306 const QString &separator = separators.at(index);
1307 int step = matchesSeparator(QStringView{m_text}.sliced(pos), separator);
1309 QDTPDEBUG <<
"invalid because" << QStringView{m_text}.sliced(pos)
1310 <<
"does not start with" << separator
1311 << index << pos << currentSectionIndex;
1315 sectionNodes[index].pos = pos;
1316 int *current =
nullptr;
1318 const SectionNode sn = sectionNodes.at(index);
1319 const QDateTime usedDateTime = [&] {
1320 const QDate date = actualDate(isSet, calendar, defaultCenturyStart,
1321 year, year2digits, month, day, dayofweek);
1322 const QTime time = actualTime(isSet, hour, hour12, ampm, minute, second, msec);
1323 return QDateTime(date, time, timeZone);
1325 ParsedSection sect = parseSection(usedDateTime, index, pos);
1327 QDTPDEBUG <<
"sectionValue" << sn.name() << m_text
1328 <<
"pos" << pos <<
"used" << sect.used << stateName(sect.state);
1330 padding += sect.zeroes;
1331 if (fixup && sect.state == Intermediate && sect.used < sn.count) {
1332 const FieldInfo fi = fieldInfo(index);
1333 if ((fi & (Numeric|FixedWidth)) == (Numeric|FixedWidth)) {
1334 const QString newText = QString::asprintf(
"%0*d", sn.count, sect.value);
1335 m_text.replace(pos, sect.used, newText);
1336 sect.used = sn.count;
1340 state = qMin<State>(state, sect.state);
1342 if (state == Invalid || (context == FromString && (state == Intermediate || sect.zeroes)))
1346 case TimeZoneSection:
1347 current = &zoneOffset;
1348 if (sect.used > 0) {
1350 QStringView zoneName = QStringView{m_text}.sliced(pos, sect.used);
1351 Q_ASSERT(!zoneName.isEmpty());
1353 const QStringView offsetStr
1354 = zoneName.startsWith(
"UTC"_L1) ? zoneName.sliced(3) : zoneName;
1355 const bool isUtcOffset = offsetStr.startsWith(u'+') || offsetStr.startsWith(u'-');
1356 const bool isUtc = zoneName ==
"Z"_L1 || zoneName ==
"UTC"_L1;
1358 if (isUtc || isUtcOffset) {
1359 timeZone = QTimeZone::fromSecondsAheadOfUtc(sect.value);
1360#if QT_CONFIG(timezone)
1361 }
else if (startsWithLocalTimeZone(zoneName, usedDateTime, locale()) != sect.used) {
1362 if (QTimeZone namedZone = QTimeZone(zoneName.toLatin1()); namedZone.isValid()) {
1363 timeZone = namedZone;
1365 auto found = findZoneByLongName(zoneName, locale(), usedDateTime);
1366 Q_ASSERT(found.isValid());
1367 Q_ASSERT(found.nameLength == zoneName.length());
1368 timeZone = found.zone;
1372 timeZone = QTimeZone::LocalTime;
1376 case Hour24Section: current = &hour;
break;
1377 case Hour12Section: current = &hour12;
break;
1378 case MinuteSection: current = &minute;
break;
1379 case SecondSection: current = &second;
break;
1380 case MSecSection: current = &msec;
break;
1381 case YearSection: current = &year;
break;
1382 case YearSection2Digits: current = &year2digits;
break;
1383 case MonthSection: current = &month;
break;
1384 case DayOfWeekSectionShort:
1385 case DayOfWeekSectionLong: current = &dayofweek;
break;
1386 case DaySection: current = &day; sect.value = qMax<
int>(1, sect.value);
break;
1387 case AmPmSection: current = &m;
break;
1389 qWarning(
"QDateTimeParser::parse Internal error (%ls)",
1390 qUtf16Printable(sn.name()));
1394 Q_ASSERT(sect.state != Invalid);
1398 QDTPDEBUG << index << sn.name() <<
"is set to"
1399 << pos <<
"state is" << stateName(state);
1401 if (isSet & sn.type && *current != sect.value) {
1402 QDTPDEBUG <<
"CONFLICT " << sn.name() << *current << sect.value;
1404 if (index != currentSectionIndex)
1407 *current = sect.value;
1413 int step = matchesSeparator(QStringView{m_text}.sliced(pos), separators.last());
1414 if (step == -1 || step + pos < m_text.size()) {
1415 QDTPDEBUG <<
"invalid because" << QStringView{m_text}.sliced(pos)
1416 <<
"does not match" << separators.last() << pos;
1420 if (parserType != QMetaType::QTime) {
1421 if (year % 100 != year2digits && (isSet & YearSection2Digits)) {
1422 const QDate date = actualDate(isSet, calendar, defaultCenturyStart,
1423 year, year2digits, month, day, dayofweek);
1424 if (!date.isValid()) {
1426 }
else if (!(isSet & YearSection)) {
1427 year = date.year(calendar);
1430 const SectionNode &sn = sectionNode(currentSectionIndex);
1431 if (sn.type == YearSection2Digits)
1432 year = date.year(calendar);
1436 const auto fieldType = sectionType(currentSectionIndex);
1437 const QDate date(year, month, day, calendar);
1438 if ((!date.isValid() || dayofweek != calendar.dayOfWeek(date))
1439 && state == Acceptable && isSet & DayOfWeekSectionMask) {
1440 if (isSet & DaySection)
1444 if (currentSectionIndex == -1 || fieldType & DayOfWeekSectionMask
1445 || (!conflicts && (fieldType & (YearSectionMask | MonthSection)))) {
1446 day = weekDayWithinMonth(calendar, year, month, day, dayofweek);
1447 QDTPDEBUG << year << month << day << dayofweek
1448 << calendar.dayOfWeek(QDate(year, month, day, calendar));
1452 bool needfixday =
false;
1453 if (fieldType & DaySectionMask) {
1455 }
else if (cachedDay > day && !(isSet & DayOfWeekSectionMask && state == Acceptable)) {
1460 if (!calendar.isDateValid(year, month, day)) {
1461 if (day <= calendar.maximumDaysInMonth())
1463 if (day > calendar.minimumDaysInMonth() && calendar.isDateValid(year, month, 1))
1467 if (context == FromString)
1469 if (state == Acceptable && fixday) {
1470 day = qMin<
int>(day, calendar.daysInMonth(month, year));
1472 const QLocale loc = locale();
1473 for (
int i=0; i<sectionNodesCount; ++i) {
1474 const SectionNode sn = sectionNode(i);
1475 if (sn.type & DaySection) {
1476 m_text.replace(sectionPos(sn), sectionSize(i), loc.toString(day));
1477 }
else if (sn.type & DayOfWeekSectionMask) {
1478 const int dayOfWeek = calendar.dayOfWeek(QDate(year, month, day, calendar));
1479 const QLocale::FormatType dayFormat =
1480 (sn.type == DayOfWeekSectionShort
1481 ? QLocale::ShortFormat : QLocale::LongFormat);
1482 const QString dayName(loc.dayName(dayOfWeek, dayFormat));
1483 m_text.replace(sectionPos(sn), sectionSize(i), dayName);
1486 }
else if (state > Intermediate) {
1487 state = Intermediate;
1492 if (parserType != QMetaType::QDate) {
1493 if (isSet & Hour12Section) {
1494 const bool hasHour = isSet.testAnyFlag(Hour24Section);
1496 ampm = !hasHour || hour < 12 ? 0 : 1;
1497 hour12 = hour12 % 12 + ampm * 12;
1500 else if (hour != hour12)
1502 }
else if (ampm != -1) {
1503 if (!(isSet & (Hour24Section)))
1505 else if ((ampm == 0) != (hour < 12))
1510 QDTPDEBUG << year << month << day << hour << minute << second << msec;
1511 Q_ASSERT(state != Invalid);
1513 const QDate date(year, month, day, calendar);
1514 const QTime time(hour, minute, second, msec);
1515 const QDateTime when = QDateTime(date, time, timeZone);
1517 if (when.time() != time || when.date() != date) {
1523 if (!(isSet & HourSectionMask)) {
1524 switch (parserType) {
1525 case QMetaType::QDateTime: {
1526 qint64 msecs = when.toMSecsSinceEpoch();
1528 const QDateTime replace = QDateTime::fromMSecsSinceEpoch(msecs, timeZone);
1529 const QTime tick = replace.time();
1530 if (replace.date() == date
1531 && (!(isSet & MinuteSection) || tick.minute() == minute)
1532 && (!(isSet & SecondSection) || tick.second() == second)
1533 && (!(isSet & MSecSection) || tick.msec() == msec)) {
1534 return StateNode(replace, state, padding, conflicts);
1537 case QMetaType::QDate:
1539 return StateNode(date.startOfDay(QTimeZone::UTC),
1540 state, padding, conflicts);
1542 case QMetaType::QTime:
1544 return StateNode(QDateTime(date, time, QTimeZone::UTC),
1545 state, padding, conflicts);
1547 Q_UNREACHABLE_RETURN(StateNode());
1549 }
else if (state > Intermediate) {
1550 state = Intermediate;
1554 return StateNode(when, state, padding, conflicts);
1558
1559
1561QDateTimeParser::StateNode
1562QDateTimeParser::parse(
const QString &input,
int position,
1563 const QDateTime &defaultValue,
bool fixup)
const
1565 const QDateTime minimum = getMinimum(defaultValue.timeRepresentation());
1566 const QDateTime maximum = getMaximum(defaultValue.timeRepresentation());
1570 StateNode scan = scanString(defaultValue, fixup);
1571 QDTPDEBUGN(
"'%s' => '%s'(%s)", m_text.toLatin1().constData(),
1572 scan.value.toString(
"yyyy/MM/dd hh:mm:ss.zzz"_L1).toLatin1().constData(),
1573 stateName(scan.state).toLatin1().constData());
1575 if (scan.value.isValid() && scan.state != Invalid) {
1576 if (context != FromString && scan.value < minimum) {
1578 scan.state = Invalid;
1579 const int sectionNodesCount = sectionNodes.size();
1580 for (
int i=0; i<sectionNodesCount && !done; ++i) {
1581 const SectionNode &sn = sectionNodes.at(i);
1582 QString t = sectionText(m_text, i, sn.pos).toLower();
1583 if ((t.size() < sectionMaxSize(i)
1584 && ((fieldInfo(i) & (FixedWidth|Numeric)) != Numeric))
1585 || t.contains(u' ')) {
1587 case TimeZoneSection:
1593 switch (findAmPm(t, i)) {
1596 scan.state = Acceptable;
1600 scan.state = Invalid;
1605 case PossibleBoth: {
1606 const QDateTime copy(scan.value.addSecs(12 * 60 * 60));
1607 if (copy >= minimum && copy <= maximum) {
1608 scan.state = Intermediate;
1615 if (sn.count >= 3) {
1616 const QDate when = scan.value.date();
1617 const int finalMonth = when.month(calendar);
1618 int tmp = finalMonth;
1620 while ((tmp = findMonth(t, tmp + 1, i, when.year(calendar))) != -1) {
1621 const QDateTime copy(scan.value.addMonths(tmp - finalMonth));
1622 if (copy >= minimum && copy <= maximum)
1626 scan.state = Intermediate;
1636 if (sn.type & TimeSectionMask) {
1637 if (scan.value.daysTo(minimum) != 0)
1640 const QTime time = scan.value.time();
1641 toMin = time.msecsTo(minimum.time());
1642 if (scan.value.daysTo(maximum) > 0)
1645 toMax = time.msecsTo(maximum.time());
1647 toMin = scan.value.daysTo(minimum);
1648 toMax = scan.value.daysTo(maximum);
1650 const int maxChange = sn.maxChange();
1651 if (toMin > maxChange) {
1652 QDTPDEBUG <<
"invalid because toMin > maxChange" << toMin
1653 << maxChange << t << scan.value << minimum;
1654 scan.state = Invalid;
1657 }
else if (toMax > maxChange) {
1661 const int min = getDigit(minimum, i);
1663 qWarning(
"QDateTimeParser::parse Internal error 4 (%ls)",
1664 qUtf16Printable(sn.name()));
1665 scan.state = Invalid;
1670 int max = toMax != -1 ? getDigit(maximum, i) : absoluteMax(i, scan.value);
1671 int pos = position + scan.padded - sn.pos;
1672 if (pos < 0 || pos >= t.size())
1674 if (!potentialValue(t.simplified(), min, max, i, scan.value, pos)) {
1675 QDTPDEBUG <<
"invalid because potentialValue(" << t.simplified() << min << max
1676 << sn.name() <<
"returned" << toMax << toMin << pos;
1677 scan.state = Invalid;
1681 scan.state = Intermediate;
1688 if (scan.value > maximum)
1689 scan.state = Invalid;
1691 QDTPDEBUG <<
"not checking intermediate because scanned value is"
1692 << scan.value << minimum << maximum;
1697 Q_ASSERT(scan.value.isValid() || scan.state != Acceptable);
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713static int findTextEntry(QStringView text,
const ShortVector<QString> &entries, QString *usedText,
int *used)
1720 for (
int n = 0; n < entries.size(); ++n)
1722 const QString &name = entries.at(n);
1724 const int limit = qMin(text.size(), name.size());
1726 while (i < limit && text.at(i) == name.at(i).toLower())
1729 if (i > bestCount || (i == bestCount && i == name.size())) {
1732 if (i == name.size() && i == text.size())
1736 if (usedText && bestMatch != -1)
1737 *usedText = entries.at(bestMatch);
1745
1746
1747
1748
1750int QDateTimeParser::findMonth(QStringView str,
int startMonth,
int sectionIndex,
1751 int year, QString *usedMonth,
int *used)
const
1753 const SectionNode &sn = sectionNode(sectionIndex);
1754 if (sn.type != MonthSection) {
1755 qWarning(
"QDateTimeParser::findMonth Internal error");
1759 QLocale::FormatType type = sn.count == 3 ? QLocale::ShortFormat : QLocale::LongFormat;
1760 QLocale l = locale();
1761 ShortVector<QString> monthNames;
1762 monthNames.reserve(13 - startMonth);
1763 for (
int month = startMonth; month <= 12; ++month)
1764 monthNames.append(calendar.monthName(l, month, year, type));
1766 const int index = findTextEntry(str, monthNames, usedMonth, used);
1767 return index < 0 ? index : index + startMonth;
1770int QDateTimeParser::findDay(QStringView str,
int startDay,
int sectionIndex, QString *usedDay,
int *used)
const
1772 const SectionNode &sn = sectionNode(sectionIndex);
1773 if (!(sn.type & DaySectionMask)) {
1774 qWarning(
"QDateTimeParser::findDay Internal error");
1778 QLocale::FormatType type = sn.count == 4 ? QLocale::LongFormat : QLocale::ShortFormat;
1779 QLocale l = locale();
1780 ShortVector<QString> daysOfWeek;
1781 daysOfWeek.reserve(8 - startDay);
1782 for (
int day = startDay; day <= 7; ++day)
1783 daysOfWeek.append(l.dayName(day, type));
1785 const int index = findTextEntry(str, daysOfWeek, usedDay, used);
1786 return index < 0 ? index : index + startDay;
1790
1791
1792
1793
1794
1795
1796
1797QDateTimeParser::ParsedSection QDateTimeParser::findUtcOffset(QStringView str,
int mode)
const
1799 Q_ASSERT(mode > 0 && mode < 4);
1800 const bool startsWithUtc = str.startsWith(
"UTC"_L1);
1802 if (startsWithUtc) {
1804 return ParsedSection();
1805 str = str.sliced(3);
1807 return ParsedSection(Acceptable, 0, 3);
1810 const bool negativeSign = str.startsWith(u'-');
1812 if (!negativeSign && !str.startsWith(u'+'))
1813 return ParsedSection();
1814 str = str.sliced(1);
1816 const int colonPosition = str.indexOf(u':');
1818 bool hasColon = (colonPosition >= 0 && colonPosition < 3);
1821 const int digits = hasColon ? colonPosition + 3 : 4;
1823 for (
const int offsetLength = qMin(qsizetype(digits), str.size()); i < offsetLength; ++i) {
1824 if (i != colonPosition && !str.at(i).isDigit())
1827 const int hoursLength = qMin(i, hasColon ? colonPosition : 2);
1828 if (hoursLength < 1)
1829 return ParsedSection();
1833 if (!startsWithUtc && hoursLength != 2)
1834 return ParsedSection();
1838 if (mode == (hasColon ? 2 : 3))
1839 return ParsedSection();
1843 const int hours = str.first(hoursLength).toInt(&isInt);
1845 return ParsedSection();
1846 const QStringView minutesStr = str.mid(hasColon ? colonPosition + 1 : 2, 2);
1847 const int minutes = minutesStr.isEmpty() ? 0 : minutesStr.toInt(&isInt);
1849 return ParsedSection();
1854 const State status = (hours > 14 || minutes >= 60) ? Invalid
1855 : (hours == 14 && minutes > 0) ? Intermediate : Acceptable;
1857 int offset = 3600 * hours + 60 * minutes;
1862 const int usedSymbols = (startsWithUtc ? 3 : 0) + 1 + hoursLength + (hasColon ? 1 : 0)
1863 + minutesStr.size();
1865 return ParsedSection(status, offset, usedSymbols);
1869
1870
1871
1872
1873
1874
1875QDateTimeParser::ParsedSection
1876QDateTimeParser::findTimeZoneName(QStringView str,
const QDateTime &when)
const
1878 const int systemLength = startsWithLocalTimeZone(str, when, locale());
1879#if QT_CONFIG(timezone)
1882 const auto invalidZoneNameCharacter = [] (
const QChar &c) {
1883 const auto cu = c.unicode();
1884 return cu >= 127u || !(memchr(
"+-./:_",
char(cu), 6) || c.isLetterOrNumber());
1886 int index = std::distance(str.cbegin(),
1887 std::find_if(str.cbegin(), str.cend(), invalidZoneNameCharacter));
1894 Q_ASSERT(index <= str.size());
1895 while (lastSlash < index) {
1896 int slash = str.indexOf(u'/', lastSlash + 1);
1897 if (slash < 0 || slash > index)
1899 else if (++count > 5)
1901 if (slash - lastSlash > 20)
1902 index = lastSlash + 20;
1908 for (QStringView copy = str; index > systemLength; --index) {
1909 copy.truncate(index);
1910 QTimeZone zone(copy.toLatin1());
1912 return ParsedSection(Acceptable, zone.offsetFromUtc(when), index);
1916 if (
auto found = findZoneByLongName(str, locale(), when); found.isValid())
1917 return ParsedSection(Acceptable, found.zone.offsetFromUtc(when), found.nameLength);
1919 if (systemLength > 0)
1920 return ParsedSection(Acceptable, when.toLocalTime().offsetFromUtc(), systemLength);
1921 return ParsedSection();
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936QDateTimeParser::ParsedSection
1937QDateTimeParser::findTimeZone(QStringView str,
const QDateTime &when,
1938 int maxVal,
int minVal,
int mode)
const
1940 Q_ASSERT(mode > 0 && mode <= 4);
1942 if (mode == 1 && str == u'Z')
1943 return ParsedSection(Acceptable, 0, 1);
1945 ParsedSection section;
1947 section = findUtcOffset(str, mode);
1948 if (mode != 2 && mode != 3 && section.used <= 0)
1949 section = findTimeZoneName(str, when);
1951 if (section.state == Acceptable && (section.value < minVal || section.value > maxVal))
1952 section.state = Intermediate;
1953 if (section.used > 0)
1958 if (str.startsWith(
"UTC"_L1))
1959 return ParsedSection(Acceptable, 0, 3);
1960 if (str.startsWith(u'Z'))
1961 return ParsedSection(Acceptable, 0, 1);
1964 return ParsedSection();
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981QDateTimeParser::AmPmFinder QDateTimeParser::findAmPm(QString &str,
int sectionIndex,
int *used)
const
1983 const SectionNode &s = sectionNode(sectionIndex);
1984 if (s.type != AmPmSection) {
1985 qWarning(
"QDateTimeParser::findAmPm Internal error");
1990 if (QStringView(str).trimmed().isEmpty())
1991 return PossibleBoth;
1993 const QLatin1Char space(
' ');
1994 int size = sectionMaxSize(sectionIndex);
2001 ampm[amindex] = getAmPmText(AmText, Case(s.count));
2002 ampm[pmindex] = getAmPmText(PmText, Case(s.count));
2003 for (
int i = 0; i < 2; ++i)
2004 ampm[i].truncate(size);
2006 QDTPDEBUG <<
"findAmPm" << str << ampm[0] << ampm[1];
2008 if (str.startsWith(ampm[amindex], Qt::CaseInsensitive)) {
2009 str = ampm[amindex];
2014 }
else if (str.startsWith(ampm[pmindex], Qt::CaseInsensitive)) {
2015 str = ampm[pmindex];
2020 }
else if (context == FromString || (str.count(space) == 0 && str.size() >= size)) {
2023 size = qMin(size, str.size());
2025 bool broken[2] = {
false,
false};
2026 for (
int i=0; i<size; ++i) {
2027 const QChar ch = str.at(i);
2029 for (
int j=0; j<2; ++j) {
2031 int index = ampm[j].indexOf(ch);
2033 <<
"in" << ampm[j] <<
"and got" << index;
2035 if (ch.category() == QChar::Letter_Uppercase) {
2036 index = ampm[j].indexOf(ch.toLower());
2037 QDTPDEBUG <<
"trying with" << ch.toLower()
2038 <<
"in" << ampm[j] <<
"and got" << index;
2039 }
else if (ch.category() == QChar::Letter_Lowercase) {
2040 index = ampm[j].indexOf(ch.toUpper());
2041 QDTPDEBUG <<
"trying with" << ch.toUpper()
2042 <<
"in" << ampm[j] <<
"and got" << index;
2046 if (broken[amindex] && broken[pmindex]) {
2052 str[i] = ampm[j].at(index);
2055 ampm[j].remove(index, 1);
2060 if (!broken[pmindex] && !broken[amindex])
2061 return PossibleBoth;
2062 return (!broken[amindex] ? PossibleAM : PossiblePM);
2066
2067
2068
2070int QDateTimeParser::SectionNode::maxChange()
const
2072 using namespace QtPrivate::DateTimeConstants;
2075 case MSecSection:
return MSECS_PER_SEC - 1;
2076 case SecondSection:
return (SECS_PER_MIN - 1) * MSECS_PER_SEC;
2077 case MinuteSection:
return (MINS_PER_HOUR - 1) * MSECS_PER_MIN;
2078 case Hour24Section:
return 23 * MSECS_PER_HOUR;
2079 case Hour12Section:
return 11 * MSECS_PER_HOUR;
2080 case TimeZoneSection:
2081 return (QTimeZone::MaxUtcOffsetSecs - QTimeZone::MinUtcOffsetSecs - 1) * MSECS_PER_SEC;
2085 case DayOfWeekSectionShort:
2086 case DayOfWeekSectionLong:
return 6;
2088 case DaySection:
return 30;
2090 case MonthSection:
return 366 - 31;
2092 case YearSection:
return 19997 * 365 + 50 * 97 - 1;
2094
2095
2096
2098 case YearSection2Digits:
return 100 * 365 + 24;
2101 qWarning(
"QDateTimeParser::maxChange() Internal error (%ls)",
2102 qUtf16Printable(name()));
2108QDateTimeParser::FieldInfo QDateTimeParser::fieldInfo(
int index)
const
2111 const SectionNode &sn = sectionNode(index);
2120 case YearSection2Digits:
2121 ret |= AllowPartial;
2135 ret |= (Numeric|AllowPartial);
2139 case DayOfWeekSectionShort:
2140 case DayOfWeekSectionLong:
2146 if (getAmPmText(AmText, Case(sn.count)).size()
2147 == getAmPmText(PmText, Case(sn.count)).size()) {
2152 case TimeZoneSection:
2155 qWarning(
"QDateTimeParser::fieldInfo Internal error 2 (%d %ls %d)",
2156 index, qUtf16Printable(sn.name()), sn.count);
2162QString QDateTimeParser::SectionNode::format()
const
2166 case AmPmSection:
return count == 1 ?
"ap"_L1 : count == 2 ?
"AP"_L1 :
"Ap"_L1;
2167 case MSecSection: fillChar = u'z';
break;
2168 case SecondSection: fillChar = u's';
break;
2169 case MinuteSection: fillChar = u'm';
break;
2170 case Hour24Section: fillChar = u'H';
break;
2171 case Hour12Section: fillChar = u'h';
break;
2172 case DayOfWeekSectionShort:
2173 case DayOfWeekSectionLong:
2174 case DaySection: fillChar = u'd';
break;
2175 case MonthSection: fillChar = u'M';
break;
2176 case YearSection2Digits:
2177 case YearSection: fillChar = u'y';
break;
2179 qWarning(
"QDateTimeParser::sectionFormat Internal error (%ls)",
2180 qUtf16Printable(name(type)));
2183 if (fillChar.isNull()) {
2184 qWarning(
"QDateTimeParser::sectionFormat Internal error 2");
2187 return QString(count, fillChar);
2192
2193
2194
2195
2196
2198bool QDateTimeParser::potentialValue(QStringView str,
int min,
int max,
int index,
2199 const QDateTime ¤tValue,
int insert)
const
2204 const int size = sectionMaxSize(index);
2205 int val = (
int)locale().toUInt(str);
2206 const SectionNode &sn = sectionNode(index);
2207 if (sn.type == YearSection2Digits) {
2208 const int year = currentValue.date().year(calendar);
2209 val += year - (year % 100);
2211 if (val >= min && val <= max && str.size() == size)
2213 if (val > max || (str.size() == size && val < min))
2216 const int len = size - str.size();
2217 for (
int i=0; i<len; ++i) {
2218 for (
int j=0; j<10; ++j) {
2219 if (potentialValue(str + QLatin1Char(
'0' + j), min, max, index, currentValue, insert)) {
2221 }
else if (insert >= 0) {
2222 const QString tmp = str.left(insert) + QLatin1Char(
'0' + j) + str.mid(insert);
2223 if (potentialValue(tmp, min, max, index, currentValue, insert))
2233
2234
2235bool QDateTimeParser::skipToNextSection(
int index,
const QDateTime ¤t, QStringView text)
const
2237 Q_ASSERT(text.size() < sectionMaxSize(index));
2238 const SectionNode &node = sectionNode(index);
2239 int min = absoluteMin(index);
2240 int max = absoluteMax(index, current);
2242 if (node.type != TimeZoneSection || current.timeSpec() == Qt::OffsetFromUTC) {
2243 const QDateTime maximum = getMaximum(current.timeRepresentation());
2244 const QDateTime minimum = getMinimum(current.timeRepresentation());
2250 QDateTime tmp = current;
2251 if (!setDigit(tmp, index, min) || tmp < minimum)
2252 min = getDigit(minimum, index);
2254 if (!setDigit(tmp, index, max) || tmp > maximum)
2255 max = getDigit(maximum, index);
2257 int pos = cursorPosition() - node.pos;
2258 if (pos < 0 || pos >= text.size())
2262
2263
2264
2265
2266
2267 return !potentialValue(text, min, max, index, current, pos);
2271
2272
2273
2275QString QDateTimeParser::SectionNode::name(QDateTimeParser::Section s)
2278 case AmPmSection:
return "AmPmSection"_L1;
2279 case DaySection:
return "DaySection"_L1;
2280 case DayOfWeekSectionShort:
return "DayOfWeekSectionShort"_L1;
2281 case DayOfWeekSectionLong:
return "DayOfWeekSectionLong"_L1;
2282 case Hour24Section:
return "Hour24Section"_L1;
2283 case Hour12Section:
return "Hour12Section"_L1;
2284 case MSecSection:
return "MSecSection"_L1;
2285 case MinuteSection:
return "MinuteSection"_L1;
2286 case MonthSection:
return "MonthSection"_L1;
2287 case SecondSection:
return "SecondSection"_L1;
2288 case TimeZoneSection:
return "TimeZoneSection"_L1;
2289 case YearSection:
return "YearSection"_L1;
2290 case YearSection2Digits:
return "YearSection2Digits"_L1;
2291 case NoSection:
return "NoSection"_L1;
2292 case FirstSection:
return "FirstSection"_L1;
2293 case LastSection:
return "LastSection"_L1;
2294 default:
return "Unknown section "_L1 + QString::number(
int(s));
2299
2300
2301
2303QString QDateTimeParser::stateName(State s)
const
2306 case Invalid:
return "Invalid"_L1;
2307 case Intermediate:
return "Intermediate"_L1;
2308 case Acceptable:
return "Acceptable"_L1;
2309 default:
return "Unknown state "_L1 + QString::number(s);
2315
2316
2317
2318QDateTime QDateTimeParser::baseDate(
const QTimeZone &zone)
const
2320 QDateTime when = QDate(defaultCenturyStart, 1, 1).startOfDay(zone);
2321 if (
const QDateTime start = getMinimum(zone); when < start)
2323 if (
const QDateTime end = getMaximum(zone); when > end)
2329bool QDateTimeParser::fromString(
const QString &t, QDate *date, QTime *time,
int baseYear)
const
2331 defaultCenturyStart = baseYear;
2332 const StateNode tmp = parse(t, -1, baseDate(QTimeZone::UTC),
false);
2333 if (tmp.state != Acceptable || tmp.conflicts)
2338 const QTime t = tmp.value.time();
2346 const QDate d = tmp.value.date();
2355bool QDateTimeParser::fromString(
const QString &t, QDateTime *datetime,
int baseYear)
const
2357 defaultCenturyStart = baseYear;
2358 const StateNode tmp = parse(t, -1, baseDate(QTimeZone::LocalTime),
false);
2360 *datetime = tmp.value;
2361 return tmp.state >= Intermediate && !tmp.conflicts && tmp.value.isValid();
2364QDateTime QDateTimeParser::getMinimum(
const QTimeZone &zone)
const
2371 static const QDateTime localTimeMin(QDATETIMEEDIT_DATE_MIN.startOfDay());
2372 static const QDateTime utcTimeMin = localTimeMin.toUTC();
2373 switch (zone.timeSpec()) {
2375 return localTimeMin;
2378 case Qt::OffsetFromUTC:
2382 return utcTimeMin.toTimeZone(zone);
2385QDateTime QDateTimeParser::getMaximum(
const QTimeZone &zone)
const
2392 static const QDateTime localTimeMax(QDATETIMEEDIT_DATE_MAX.endOfDay());
2393 static const QDateTime utcTimeMax(QDATETIMEEDIT_DATE_MAX.endOfDay(QTimeZone::UTC));
2394 switch (zone.timeSpec()) {
2396 return localTimeMax;
2399 case Qt::OffsetFromUTC:
2403 return utcTimeMax.toTimeZone(zone);
2406QString QDateTimeParser::getAmPmText(AmPm ap, Case cs)
const
2408 const QLocale loc = locale();
2409 QString raw = ap == AmText ? loc.amText() : loc.pmText();
2412 case UpperCase:
return std::move(raw).toUpper();
2413 case LowerCase:
return std::move(raw).toLower();
2414 case NativeCase:
return raw;
2416 Q_UNREACHABLE_RETURN(raw);
2420
2421
2423bool operator==(QDateTimeParser::SectionNode s1, QDateTimeParser::SectionNode s2)
2425 return (s1.type == s2.type) && (s1.pos == s2.pos) && (s1.count == s2.count);
2429
2430
2431
2433void QDateTimeParser::setCalendar(QCalendar cal)
Combined button and popup list for selecting options.
static int dayOfWeekDiff(int sought, int held)
static void appendSeparator(QStringList *list, QStringView string, int from, int size, int lastQuote)
static int countRepeat(QStringView str, int index, int maxCount)
static int weekDayWithinMonth(QCalendar calendar, int year, int month, int day, int weekDay)
static QString unquote(QStringView str)
static int yearInCenturyFrom(int y2d, int baseYear)
static int startsWithLocalTimeZone(QStringView name, const QDateTime &when, const QLocale &locale)
static bool preferDayOfWeek(const QList< QDateTimeParser::SectionNode > &nodes)
bool operator==(QDateTimeParser::SectionNode s1, QDateTimeParser::SectionNode s2)
static int matchesSeparator(QStringView text, QStringView separator)
static qsizetype digitCount(QStringView str)
static int findTextEntry(QStringView text, const ShortVector< QString > &entries, QString *usedText, int *used)
static QTime actualTime(QDateTimeParser::Sections known, int hour, int hour12, int ampm, int minute, int second, int msec)
static QDate actualDate(QDateTimeParser::Sections known, QCalendar calendar, int baseYear, int year, int year2digits, int month, int day, int dayofweek)
QVarLengthArray< T, 13 > ShortVector