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);
1268 const bool niceOffset = pfx.offset
1269 && *pfx.offset >= QTimeZone::MinUtcOffsetSecs
1270 && *pfx.offset <= QTimeZone::MaxUtcOffsetSecs;
1271 result = R{ niceOffset
1272 ? QTimeZone::fromSecondsAheadOfUtc(*pfx.offset)
1273 : QTimeZone(pfx.ianaId),
1275 Q_ASSERT(result.zone.isValid());
1283
1284
1285QDateTimeParser::StateNode
1286QDateTimeParser::scanString(
const QDateTime &defaultValue,
bool fixup)
const
1288 State state = Acceptable;
1289 bool conflicts =
false;
1290 const int sectionNodesCount = sectionNodes.size();
1293 int year, month, day;
1294 const QDate defaultDate = defaultValue.date();
1295 const QTime defaultTime = defaultValue.time();
1296 defaultDate.getDate(&year, &month, &day);
1297 int year2digits = year % 100;
1298 int hour = defaultTime.hour();
1300 int minute = defaultTime.minute();
1301 int second = defaultTime.second();
1302 int msec = defaultTime.msec();
1303 int dayofweek = calendar.dayOfWeek(defaultDate);
1304 QTimeZone timeZone = defaultValue.timeRepresentation();
1307 Sections isSet = NoSection;
1309 for (
int index = 0; index < sectionNodesCount; ++index) {
1310 Q_ASSERT(state != Invalid);
1311 const QString &separator = separators.at(index);
1312 int step = matchesSeparator(QStringView{m_text}.sliced(pos), separator);
1314 QDTPDEBUG <<
"invalid because" << QStringView{m_text}.sliced(pos)
1315 <<
"does not start with" << separator
1316 << index << pos << currentSectionIndex;
1320 sectionNodes[index].pos = pos;
1321 int *current =
nullptr;
1323 const SectionNode sn = sectionNodes.at(index);
1324 const QDateTime usedDateTime = [&] {
1325 const QDate date = actualDate(isSet, calendar, defaultCenturyStart,
1326 year, year2digits, month, day, dayofweek);
1327 const QTime time = actualTime(isSet, hour, hour12, ampm, minute, second, msec);
1328 return QDateTime(date, time, timeZone);
1330 ParsedSection sect = parseSection(usedDateTime, index, pos);
1332 QDTPDEBUG <<
"sectionValue" << sn.name() << m_text
1333 <<
"pos" << pos <<
"used" << sect.used << stateName(sect.state);
1335 padding += sect.zeroes;
1336 if (fixup && sect.state == Intermediate && sect.used < sn.count) {
1337 const FieldInfo fi = fieldInfo(index);
1338 if ((fi & (Numeric|FixedWidth)) == (Numeric|FixedWidth)) {
1339 const QString newText = QString::asprintf(
"%0*d", sn.count, sect.value);
1340 m_text.replace(pos, sect.used, newText);
1341 sect.used = sn.count;
1345 state = qMin<State>(state, sect.state);
1347 if (state == Invalid || (context == FromString && (state == Intermediate || sect.zeroes)))
1351 case TimeZoneSection:
1352 current = &zoneOffset;
1353 if (sect.used > 0) {
1355 QStringView zoneName = QStringView{m_text}.sliced(pos, sect.used);
1356 Q_ASSERT(!zoneName.isEmpty());
1358 const QStringView offsetStr
1359 = zoneName.startsWith(
"UTC"_L1) ? zoneName.sliced(3) : zoneName;
1360 const bool isUtcOffset = offsetStr.startsWith(u'+') || offsetStr.startsWith(u'-');
1361 const bool isUtc = zoneName ==
"Z"_L1 || zoneName ==
"UTC"_L1;
1363 if (isUtc || isUtcOffset) {
1364 timeZone = QTimeZone::fromSecondsAheadOfUtc(sect.value);
1365#if QT_CONFIG(timezone)
1366 }
else if (startsWithLocalTimeZone(zoneName, usedDateTime, locale()) != sect.used) {
1367 if (QTimeZone namedZone = QTimeZone(zoneName.toLatin1()); namedZone.isValid()) {
1368 timeZone = namedZone;
1370 auto found = findZoneByLongName(zoneName, locale(), usedDateTime);
1371 Q_ASSERT(found.isValid());
1372 Q_ASSERT(found.nameLength == zoneName.length());
1373 timeZone = found.zone;
1377 timeZone = QTimeZone::LocalTime;
1381 case Hour24Section: current = &hour;
break;
1382 case Hour12Section: current = &hour12;
break;
1383 case MinuteSection: current = &minute;
break;
1384 case SecondSection: current = &second;
break;
1385 case MSecSection: current = &msec;
break;
1386 case YearSection: current = &year;
break;
1387 case YearSection2Digits: current = &year2digits;
break;
1388 case MonthSection: current = &month;
break;
1389 case DayOfWeekSectionShort:
1390 case DayOfWeekSectionLong: current = &dayofweek;
break;
1391 case DaySection: current = &day; sect.value = qMax<
int>(1, sect.value);
break;
1392 case AmPmSection: current = &m;
break;
1394 qWarning(
"QDateTimeParser::parse Internal error (%ls)",
1395 qUtf16Printable(sn.name()));
1399 Q_ASSERT(sect.state != Invalid);
1403 QDTPDEBUG << index << sn.name() <<
"is set to"
1404 << pos <<
"state is" << stateName(state);
1406 if (isSet & sn.type && *current != sect.value) {
1407 QDTPDEBUG <<
"CONFLICT " << sn.name() << *current << sect.value;
1409 if (index != currentSectionIndex)
1412 *current = sect.value;
1418 int step = matchesSeparator(QStringView{m_text}.sliced(pos), separators.last());
1419 if (step == -1 || step + pos < m_text.size()) {
1420 QDTPDEBUG <<
"invalid because" << QStringView{m_text}.sliced(pos)
1421 <<
"does not match" << separators.last() << pos;
1425 if (parserType != QMetaType::QTime) {
1426 if (year % 100 != year2digits && (isSet & YearSection2Digits)) {
1427 const QDate date = actualDate(isSet, calendar, defaultCenturyStart,
1428 year, year2digits, month, day, dayofweek);
1429 if (!date.isValid()) {
1431 }
else if (!(isSet & YearSection)) {
1432 year = date.year(calendar);
1435 const SectionNode &sn = sectionNode(currentSectionIndex);
1436 if (sn.type == YearSection2Digits)
1437 year = date.year(calendar);
1441 const auto fieldType = sectionType(currentSectionIndex);
1442 const QDate date(year, month, day, calendar);
1443 if ((!date.isValid() || dayofweek != calendar.dayOfWeek(date))
1444 && state == Acceptable && isSet & DayOfWeekSectionMask) {
1445 if (isSet & DaySection)
1449 if (currentSectionIndex == -1 || fieldType & DayOfWeekSectionMask
1450 || (!conflicts && (fieldType & (YearSectionMask | MonthSection)))) {
1451 day = weekDayWithinMonth(calendar, year, month, day, dayofweek);
1452 QDTPDEBUG << year << month << day << dayofweek
1453 << calendar.dayOfWeek(QDate(year, month, day, calendar));
1457 bool needfixday =
false;
1458 if (fieldType & DaySectionMask) {
1460 }
else if (cachedDay > day && !(isSet & DayOfWeekSectionMask && state == Acceptable)) {
1465 if (!calendar.isDateValid(year, month, day)) {
1466 if (day <= calendar.maximumDaysInMonth())
1468 if (day > calendar.minimumDaysInMonth() && calendar.isDateValid(year, month, 1))
1472 if (context == FromString)
1474 if (state == Acceptable && fixday) {
1475 day = qMin<
int>(day, calendar.daysInMonth(month, year));
1477 const QLocale loc = locale();
1478 for (
int i=0; i<sectionNodesCount; ++i) {
1479 const SectionNode sn = sectionNode(i);
1480 if (sn.type & DaySection) {
1481 m_text.replace(sectionPos(sn), sectionSize(i), loc.toString(day));
1482 }
else if (sn.type & DayOfWeekSectionMask) {
1483 const int dayOfWeek = calendar.dayOfWeek(QDate(year, month, day, calendar));
1484 const QLocale::FormatType dayFormat =
1485 (sn.type == DayOfWeekSectionShort
1486 ? QLocale::ShortFormat : QLocale::LongFormat);
1487 const QString dayName(loc.dayName(dayOfWeek, dayFormat));
1488 m_text.replace(sectionPos(sn), sectionSize(i), dayName);
1491 }
else if (state > Intermediate) {
1492 state = Intermediate;
1497 if (parserType != QMetaType::QDate) {
1498 if (isSet & Hour12Section) {
1499 const bool hasHour = isSet.testAnyFlag(Hour24Section);
1501 ampm = !hasHour || hour < 12 ? 0 : 1;
1502 hour12 = hour12 % 12 + ampm * 12;
1505 else if (hour != hour12)
1507 }
else if (ampm != -1) {
1508 if (!(isSet & (Hour24Section)))
1510 else if ((ampm == 0) != (hour < 12))
1515 QDTPDEBUG << year << month << day << hour << minute << second << msec;
1516 Q_ASSERT(state != Invalid);
1518 const QDate date(year, month, day, calendar);
1519 const QTime time(hour, minute, second, msec);
1520 const QDateTime when = QDateTime(date, time, timeZone);
1522 if (when.time() != time || when.date() != date) {
1528 if (!(isSet & HourSectionMask)) {
1529 switch (parserType) {
1530 case QMetaType::QDateTime: {
1531 qint64 msecs = when.toMSecsSinceEpoch();
1533 const QDateTime replace = QDateTime::fromMSecsSinceEpoch(msecs, timeZone);
1534 const QTime tick = replace.time();
1535 if (replace.date() == date
1536 && (!(isSet & MinuteSection) || tick.minute() == minute)
1537 && (!(isSet & SecondSection) || tick.second() == second)
1538 && (!(isSet & MSecSection) || tick.msec() == msec)) {
1539 return StateNode(replace, state, padding, conflicts);
1542 case QMetaType::QDate:
1544 return StateNode(date.startOfDay(QTimeZone::UTC),
1545 state, padding, conflicts);
1547 case QMetaType::QTime:
1549 return StateNode(QDateTime(date, time, QTimeZone::UTC),
1550 state, padding, conflicts);
1552 Q_UNREACHABLE_RETURN(StateNode());
1554 }
else if (state > Intermediate) {
1555 state = Intermediate;
1559 return StateNode(when, state, padding, conflicts);
1563
1564
1566QDateTimeParser::StateNode
1567QDateTimeParser::parse(
const QString &input,
int position,
1568 const QDateTime &defaultValue,
bool fixup)
const
1570 const QDateTime minimum = getMinimum(defaultValue.timeRepresentation());
1571 const QDateTime maximum = getMaximum(defaultValue.timeRepresentation());
1575 StateNode scan = scanString(defaultValue, fixup);
1576 QDTPDEBUGN(
"'%s' => '%s'(%s)", m_text.toLatin1().constData(),
1577 scan.value.toString(
"yyyy/MM/dd hh:mm:ss.zzz"_L1).toLatin1().constData(),
1578 stateName(scan.state).toLatin1().constData());
1580 if (scan.value.isValid() && scan.state != Invalid) {
1581 if (context != FromString && scan.value < minimum) {
1583 scan.state = Invalid;
1584 const int sectionNodesCount = sectionNodes.size();
1585 for (
int i=0; i<sectionNodesCount && !done; ++i) {
1586 const SectionNode &sn = sectionNodes.at(i);
1587 QString t = sectionText(m_text, i, sn.pos).toLower();
1588 if ((t.size() < sectionMaxSize(i)
1589 && ((fieldInfo(i) & (FixedWidth|Numeric)) != Numeric))
1590 || t.contains(u' ')) {
1592 case TimeZoneSection:
1597 case DayOfWeekSectionShort:
1598 case DayOfWeekSectionLong:
1603 scan.state = Invalid;
1607 switch (findAmPm(t, i)) {
1610 scan.state = Acceptable;
1614 scan.state = Invalid;
1619 case PossibleBoth: {
1620 const QDateTime copy(scan.value.addSecs(12 * 60 * 60));
1621 if (copy >= minimum && copy <= maximum) {
1622 scan.state = Intermediate;
1629 if (sn.count >= 3) {
1630 const QDate when = scan.value.date();
1631 const int finalMonth = when.month(calendar);
1632 int tmp = finalMonth;
1634 while ((tmp = findMonth(t, tmp + 1, i, when.year(calendar))) != -1) {
1635 const QDateTime copy(scan.value.addMonths(tmp - finalMonth));
1636 if (copy >= minimum && copy <= maximum)
1640 scan.state = Intermediate;
1652 if (sn.type & TimeSectionMask) {
1653 if (scan.value.daysTo(minimum) != 0)
1656 const QTime time = scan.value.time();
1657 toMin = time.msecsTo(minimum.time());
1658 if (scan.value.daysTo(maximum) > 0)
1661 toMax = time.msecsTo(maximum.time());
1663 toMin = scan.value.daysTo(minimum);
1664 toMax = scan.value.daysTo(maximum);
1666 const int maxChange = sn.maxChange();
1667 if (toMin > maxChange) {
1668 QDTPDEBUG <<
"invalid because toMin > maxChange" << toMin
1669 << maxChange << t << scan.value << minimum;
1670 scan.state = Invalid;
1673 }
else if (toMax > maxChange) {
1677 const int min = getDigit(minimum, i);
1679 qWarning(
"QDateTimeParser::parse Internal error 4 (%ls)",
1680 qUtf16Printable(sn.name()));
1681 scan.state = Invalid;
1686 int max = toMax != -1 ? getDigit(maximum, i) : absoluteMax(i, scan.value);
1687 int pos = position + scan.padded - sn.pos;
1688 if (pos < 0 || pos >= t.size())
1690 if (!potentialValue(t.simplified(), min, max, i, scan.value, pos)) {
1691 QDTPDEBUG <<
"invalid because potentialValue(" << t.simplified() << min << max
1692 << sn.name() <<
"returned" << toMax << toMin << pos;
1693 scan.state = Invalid;
1697 scan.state = Intermediate;
1704 if (scan.value > maximum)
1705 scan.state = Invalid;
1707 QDTPDEBUG <<
"not checking intermediate because scanned value is"
1708 << scan.value << minimum << maximum;
1713 Q_ASSERT(scan.value.isValid() || scan.state != Acceptable);
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729static int findTextEntry(QStringView text,
const ShortVector<QString> &entries, QString *usedText,
int *used)
1736 for (
int n = 0; n < entries.size(); ++n)
1738 const QString &name = entries.at(n);
1740 const int limit = qMin(text.size(), name.size());
1742 while (i < limit && text.at(i) == name.at(i).toLower())
1745 if (i > bestCount || (i == bestCount && i == name.size())) {
1748 if (i == name.size() && i == text.size())
1752 if (usedText && bestMatch != -1)
1753 *usedText = entries.at(bestMatch);
1761
1762
1763
1764
1766int QDateTimeParser::findMonth(QStringView str,
int startMonth,
int sectionIndex,
1767 int year, QString *usedMonth,
int *used)
const
1769 const SectionNode &sn = sectionNode(sectionIndex);
1770 if (sn.type != MonthSection) {
1771 qWarning(
"QDateTimeParser::findMonth Internal error");
1775 QLocale::FormatType type = sn.count == 3 ? QLocale::ShortFormat : QLocale::LongFormat;
1776 QLocale l = locale();
1777 ShortVector<QString> monthNames;
1778 monthNames.reserve(13 - startMonth);
1779 for (
int month = startMonth; month <= 12; ++month)
1780 monthNames.append(calendar.monthName(l, month, year, type));
1782 const int index = findTextEntry(str, monthNames, usedMonth, used);
1783 return index < 0 ? index : index + startMonth;
1786int QDateTimeParser::findDay(QStringView str,
int startDay,
int sectionIndex, QString *usedDay,
int *used)
const
1788 const SectionNode &sn = sectionNode(sectionIndex);
1789 if (!(sn.type & DaySectionMask)) {
1790 qWarning(
"QDateTimeParser::findDay Internal error");
1794 QLocale::FormatType type = sn.count == 4 ? QLocale::LongFormat : QLocale::ShortFormat;
1795 QLocale l = locale();
1796 ShortVector<QString> daysOfWeek;
1797 daysOfWeek.reserve(8 - startDay);
1798 for (
int day = startDay; day <= 7; ++day)
1799 daysOfWeek.append(l.dayName(day, type));
1801 const int index = findTextEntry(str, daysOfWeek, usedDay, used);
1802 return index < 0 ? index : index + startDay;
1806
1807
1808
1809
1810
1811
1812
1813QDateTimeParser::ParsedSection QDateTimeParser::findUtcOffset(QStringView str,
int mode)
const
1815 Q_ASSERT(mode > 0 && mode < 4);
1816 const bool startsWithUtc = str.startsWith(
"UTC"_L1);
1818 if (startsWithUtc) {
1820 return ParsedSection();
1821 str = str.sliced(3);
1823 return ParsedSection(Acceptable, 0, 3);
1826 const bool negativeSign = str.startsWith(u'-');
1828 if (!negativeSign && !str.startsWith(u'+'))
1829 return ParsedSection();
1830 str = str.sliced(1);
1832 const int colonPosition = str.indexOf(u':');
1834 bool hasColon = (colonPosition >= 0 && colonPosition < 3);
1837 const int digits = hasColon ? colonPosition + 3 : 4;
1839 for (
const int offsetLength = qMin(qsizetype(digits), str.size()); i < offsetLength; ++i) {
1840 if (i != colonPosition && !str.at(i).isDigit())
1843 const int hoursLength = qMin(i, hasColon ? colonPosition : 2);
1844 if (hoursLength < 1)
1845 return ParsedSection();
1849 if (!startsWithUtc && hoursLength != 2)
1850 return ParsedSection();
1854 if (mode == (hasColon ? 2 : 3))
1855 return ParsedSection();
1859 const int hours = str.first(hoursLength).toInt(&isInt);
1861 return ParsedSection();
1862 const QStringView minutesStr = str.mid(hasColon ? colonPosition + 1 : 2, 2);
1863 const int minutes = minutesStr.isEmpty() ? 0 : minutesStr.toInt(&isInt);
1865 return ParsedSection();
1870 const State status = (hours > 14 || minutes >= 60) ? Invalid
1871 : (hours == 14 && minutes > 0) ? Intermediate : Acceptable;
1873 int offset = 3600 * hours + 60 * minutes;
1878 const int usedSymbols = (startsWithUtc ? 3 : 0) + 1 + hoursLength + (hasColon ? 1 : 0)
1879 + minutesStr.size();
1881 return ParsedSection(status, offset, usedSymbols);
1885
1886
1887
1888
1889
1890
1891QDateTimeParser::ParsedSection
1892QDateTimeParser::findTimeZoneName(QStringView str,
const QDateTime &when)
const
1894 const int systemLength = startsWithLocalTimeZone(str, when, locale());
1895#if QT_CONFIG(timezone)
1898 const auto invalidZoneNameCharacter = [] (
const QChar &c) {
1899 const auto cu = c.unicode();
1900 return cu >= 127u || !(memchr(
"+-./:_",
char(cu), 6) || c.isLetterOrNumber());
1902 int index = std::distance(str.cbegin(),
1903 std::find_if(str.cbegin(), str.cend(), invalidZoneNameCharacter));
1910 Q_ASSERT(index <= str.size());
1911 while (lastSlash < index) {
1912 int slash = str.indexOf(u'/', lastSlash + 1);
1913 if (slash < 0 || slash > index)
1915 else if (++count > 5)
1917 if (slash - lastSlash > 20)
1918 index = lastSlash + 20;
1924 for (QStringView copy = str; index > systemLength; --index) {
1925 copy.truncate(index);
1926 QTimeZone zone(copy.toLatin1());
1928 return ParsedSection(Acceptable, zone.offsetFromUtc(when), index);
1932 if (
auto found = findZoneByLongName(str, locale(), when); found.isValid())
1933 return ParsedSection(Acceptable, found.zone.offsetFromUtc(when), found.nameLength);
1935 if (systemLength > 0)
1936 return ParsedSection(Acceptable, when.toLocalTime().offsetFromUtc(), systemLength);
1937 return ParsedSection();
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952QDateTimeParser::ParsedSection
1953QDateTimeParser::findTimeZone(QStringView str,
const QDateTime &when,
1954 int maxVal,
int minVal,
int mode)
const
1956 Q_ASSERT(mode > 0 && mode <= 4);
1958 if (mode == 1 && str == u'Z')
1959 return ParsedSection(Acceptable, 0, 1);
1961 ParsedSection section;
1963 section = findUtcOffset(str, mode);
1964 if (mode != 2 && mode != 3 && section.used <= 0)
1965 section = findTimeZoneName(str, when);
1967 if (section.state == Acceptable && (section.value < minVal || section.value > maxVal))
1968 section.state = Intermediate;
1969 if (section.used > 0)
1974 if (str.startsWith(
"UTC"_L1))
1975 return ParsedSection(Acceptable, 0, 3);
1976 if (str.startsWith(u'Z'))
1977 return ParsedSection(Acceptable, 0, 1);
1980 return ParsedSection();
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997QDateTimeParser::AmPmFinder QDateTimeParser::findAmPm(QString &str,
int sectionIndex,
int *used)
const
1999 const SectionNode &s = sectionNode(sectionIndex);
2000 if (s.type != AmPmSection) {
2001 qWarning(
"QDateTimeParser::findAmPm Internal error");
2006 if (QStringView(str).trimmed().isEmpty())
2007 return PossibleBoth;
2009 const QLatin1Char space(
' ');
2010 int size = sectionMaxSize(sectionIndex);
2017 ampm[amindex] = getAmPmText(AmText, Case(s.count));
2018 ampm[pmindex] = getAmPmText(PmText, Case(s.count));
2019 for (
int i = 0; i < 2; ++i)
2020 ampm[i].truncate(size);
2022 QDTPDEBUG <<
"findAmPm" << str << ampm[0] << ampm[1];
2024 if (str.startsWith(ampm[amindex], Qt::CaseInsensitive)) {
2025 str = ampm[amindex];
2030 }
else if (str.startsWith(ampm[pmindex], Qt::CaseInsensitive)) {
2031 str = ampm[pmindex];
2036 }
else if (context == FromString || (str.count(space) == 0 && str.size() >= size)) {
2039 size = qMin(size, str.size());
2041 bool broken[2] = {
false,
false};
2042 for (
int i=0; i<size; ++i) {
2043 const QChar ch = str.at(i);
2045 for (
int j=0; j<2; ++j) {
2047 int index = ampm[j].indexOf(ch);
2049 <<
"in" << ampm[j] <<
"and got" << index;
2051 if (ch.category() == QChar::Letter_Uppercase) {
2052 index = ampm[j].indexOf(ch.toLower());
2053 QDTPDEBUG <<
"trying with" << ch.toLower()
2054 <<
"in" << ampm[j] <<
"and got" << index;
2055 }
else if (ch.category() == QChar::Letter_Lowercase) {
2056 index = ampm[j].indexOf(ch.toUpper());
2057 QDTPDEBUG <<
"trying with" << ch.toUpper()
2058 <<
"in" << ampm[j] <<
"and got" << index;
2062 if (broken[amindex] && broken[pmindex]) {
2068 str[i] = ampm[j].at(index);
2071 ampm[j].remove(index, 1);
2076 if (!broken[pmindex] && !broken[amindex])
2077 return PossibleBoth;
2078 return (!broken[amindex] ? PossibleAM : PossiblePM);
2082
2083
2084
2086int QDateTimeParser::SectionNode::maxChange()
const
2088 using namespace QtPrivate::DateTimeConstants;
2091 case MSecSection:
return MSECS_PER_SEC - 1;
2092 case SecondSection:
return (SECS_PER_MIN - 1) * MSECS_PER_SEC;
2093 case MinuteSection:
return (MINS_PER_HOUR - 1) * MSECS_PER_MIN;
2094 case Hour24Section:
return 23 * MSECS_PER_HOUR;
2095 case Hour12Section:
return 11 * MSECS_PER_HOUR;
2096 case TimeZoneSection:
2097 return (QTimeZone::MaxUtcOffsetSecs - QTimeZone::MinUtcOffsetSecs - 1) * MSECS_PER_SEC;
2101 case DayOfWeekSectionShort:
2102 case DayOfWeekSectionLong:
return 6;
2104 case DaySection:
return 30;
2106 case MonthSection:
return 366 - 31;
2108 case YearSection:
return 19997 * 365 + 50 * 97 - 1;
2110
2111
2112
2114 case YearSection2Digits:
return 100 * 365 + 24;
2117 qWarning(
"QDateTimeParser::maxChange() Internal error (%ls)",
2118 qUtf16Printable(name()));
2124QDateTimeParser::FieldInfo QDateTimeParser::fieldInfo(
int index)
const
2127 const SectionNode &sn = sectionNode(index);
2136 case YearSection2Digits:
2137 ret |= AllowPartial;
2151 ret |= (Numeric|AllowPartial);
2155 case DayOfWeekSectionShort:
2156 case DayOfWeekSectionLong:
2162 if (getAmPmText(AmText, Case(sn.count)).size()
2163 == getAmPmText(PmText, Case(sn.count)).size()) {
2168 case TimeZoneSection:
2171 qWarning(
"QDateTimeParser::fieldInfo Internal error 2 (%d %ls %d)",
2172 index, qUtf16Printable(sn.name()), sn.count);
2178QString QDateTimeParser::SectionNode::format()
const
2182 case AmPmSection:
return count == 1 ?
"ap"_L1 : count == 2 ?
"AP"_L1 :
"Ap"_L1;
2183 case MSecSection: fillChar = u'z';
break;
2184 case SecondSection: fillChar = u's';
break;
2185 case MinuteSection: fillChar = u'm';
break;
2186 case Hour24Section: fillChar = u'H';
break;
2187 case Hour12Section: fillChar = u'h';
break;
2188 case DayOfWeekSectionShort:
2189 case DayOfWeekSectionLong:
2190 case DaySection: fillChar = u'd';
break;
2191 case MonthSection: fillChar = u'M';
break;
2192 case YearSection2Digits:
2193 case YearSection: fillChar = u'y';
break;
2195 qWarning(
"QDateTimeParser::sectionFormat Internal error (%ls)",
2196 qUtf16Printable(name(type)));
2199 if (fillChar.isNull()) {
2200 qWarning(
"QDateTimeParser::sectionFormat Internal error 2");
2203 return QString(count, fillChar);
2208
2209
2210
2211
2212
2214bool QDateTimeParser::potentialValue(QStringView str,
int min,
int max,
int index,
2215 const QDateTime ¤tValue,
int insert)
const
2220 const int size = sectionMaxSize(index);
2221 int val = (
int)locale().toUInt(str);
2222 const SectionNode &sn = sectionNode(index);
2223 if (sn.type == YearSection2Digits) {
2224 const int year = currentValue.date().year(calendar);
2225 val += year - (year % 100);
2227 if (val >= min && val <= max && str.size() == size)
2229 if (val > max || (str.size() == size && val < min))
2232 const int len = size - str.size();
2233 for (
int i=0; i<len; ++i) {
2234 for (
int j=0; j<10; ++j) {
2235 if (potentialValue(str + QLatin1Char(
'0' + j), min, max, index, currentValue, insert)) {
2237 }
else if (insert >= 0) {
2238 const QString tmp = str.left(insert) + QLatin1Char(
'0' + j) + str.mid(insert);
2239 if (potentialValue(tmp, min, max, index, currentValue, insert))
2249
2250
2251bool QDateTimeParser::skipToNextSection(
int index,
const QDateTime ¤t, QStringView text)
const
2253 Q_ASSERT(text.size() < sectionMaxSize(index));
2254 const SectionNode &node = sectionNode(index);
2255 int min = absoluteMin(index);
2256 int max = absoluteMax(index, current);
2258 if (node.type != TimeZoneSection || current.timeSpec() == Qt::OffsetFromUTC) {
2259 const QDateTime maximum = getMaximum(current.timeRepresentation());
2260 const QDateTime minimum = getMinimum(current.timeRepresentation());
2266 QDateTime tmp = current;
2267 if (!setDigit(tmp, index, min) || tmp < minimum)
2268 min = getDigit(minimum, index);
2270 if (!setDigit(tmp, index, max) || tmp > maximum)
2271 max = getDigit(maximum, index);
2273 int pos = cursorPosition() - node.pos;
2274 if (pos < 0 || pos >= text.size())
2278
2279
2280
2281
2282
2283 return !potentialValue(text, min, max, index, current, pos);
2287
2288
2289
2291QString QDateTimeParser::SectionNode::name(QDateTimeParser::Section s)
2294 case AmPmSection:
return "AmPmSection"_L1;
2295 case DaySection:
return "DaySection"_L1;
2296 case DayOfWeekSectionShort:
return "DayOfWeekSectionShort"_L1;
2297 case DayOfWeekSectionLong:
return "DayOfWeekSectionLong"_L1;
2298 case Hour24Section:
return "Hour24Section"_L1;
2299 case Hour12Section:
return "Hour12Section"_L1;
2300 case MSecSection:
return "MSecSection"_L1;
2301 case MinuteSection:
return "MinuteSection"_L1;
2302 case MonthSection:
return "MonthSection"_L1;
2303 case SecondSection:
return "SecondSection"_L1;
2304 case TimeZoneSection:
return "TimeZoneSection"_L1;
2305 case YearSection:
return "YearSection"_L1;
2306 case YearSection2Digits:
return "YearSection2Digits"_L1;
2307 case NoSection:
return "NoSection"_L1;
2308 case FirstSection:
return "FirstSection"_L1;
2309 case LastSection:
return "LastSection"_L1;
2310 default:
return "Unknown section "_L1 + QString::number(
int(s));
2315
2316
2317
2319QString QDateTimeParser::stateName(State s)
const
2322 case Invalid:
return "Invalid"_L1;
2323 case Intermediate:
return "Intermediate"_L1;
2324 case Acceptable:
return "Acceptable"_L1;
2325 default:
return "Unknown state "_L1 + QString::number(s);
2331
2332
2333
2334QDateTime QDateTimeParser::baseDate(
const QTimeZone &zone)
const
2336 QDateTime when = QDate(defaultCenturyStart, 1, 1).startOfDay(zone);
2337 if (
const QDateTime start = getMinimum(zone); when < start)
2339 if (
const QDateTime end = getMaximum(zone); when > end)
2345bool QDateTimeParser::fromString(
const QString &t, QDate *date, QTime *time,
int baseYear)
const
2347 defaultCenturyStart = baseYear;
2348 const StateNode tmp = parse(t, -1, baseDate(QTimeZone::UTC),
false);
2349 if (tmp.state != Acceptable || tmp.conflicts)
2354 const QTime t = tmp.value.time();
2362 const QDate d = tmp.value.date();
2371bool QDateTimeParser::fromString(
const QString &t, QDateTime *datetime,
int baseYear)
const
2373 defaultCenturyStart = baseYear;
2374 const StateNode tmp = parse(t, -1, baseDate(QTimeZone::LocalTime),
false);
2376 *datetime = tmp.value;
2377 return tmp.state >= Intermediate && !tmp.conflicts && tmp.value.isValid();
2380QDateTime QDateTimeParser::getMinimum(
const QTimeZone &zone)
const
2387 static const QDateTime localTimeMin(QDATETIMEEDIT_DATE_MIN.startOfDay());
2388 static const QDateTime utcTimeMin = localTimeMin.toUTC();
2389 switch (zone.timeSpec()) {
2391 return localTimeMin;
2394 case Qt::OffsetFromUTC:
2398 return utcTimeMin.toTimeZone(zone);
2401QDateTime QDateTimeParser::getMaximum(
const QTimeZone &zone)
const
2408 static const QDateTime localTimeMax(QDATETIMEEDIT_DATE_MAX.endOfDay());
2409 static const QDateTime utcTimeMax(QDATETIMEEDIT_DATE_MAX.endOfDay(QTimeZone::UTC));
2410 switch (zone.timeSpec()) {
2412 return localTimeMax;
2415 case Qt::OffsetFromUTC:
2419 return utcTimeMax.toTimeZone(zone);
2422QString QDateTimeParser::getAmPmText(AmPm ap, Case cs)
const
2424 const QLocale loc = locale();
2425 QString raw = ap == AmText ? loc.amText() : loc.pmText();
2428 case UpperCase:
return std::move(raw).toUpper();
2429 case LowerCase:
return std::move(raw).toLower();
2430 case NativeCase:
return raw;
2432 Q_UNREACHABLE_RETURN(raw);
2436
2437
2439bool operator==(QDateTimeParser::SectionNode s1, QDateTimeParser::SectionNode s2)
2441 return (s1.type == s2.type) && (s1.pos == s2.pos) && (s1.count == s2.count);
2445
2446
2447
2449void 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