Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qdatetimeparser.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qplatformdefs.h"
5#include "private/qdatetimeparser_p.h"
6
7#include "qdatastream.h"
8#include "qdatetime.h"
9#include "qdebug.h"
10#include "qlocale.h"
11#include "qset.h"
12#include "qtimezone.h"
13#include "qvarlengtharray.h"
14#include "private/qlocale_p.h"
15
16#include "private/qstringiterator_p.h"
17#include "private/qtenvironmentvariables_p.h"
18
19//#define QDATETIMEPARSER_DEBUG
20#if defined (QDATETIMEPARSER_DEBUG) && !defined(QT_NO_DEBUG_STREAM)
21# define QDTPDEBUG qDebug()
22# define QDTPDEBUGN qDebug
23#else
24# define QDTPDEBUG if (false) qDebug()
25# define QDTPDEBUGN if (false) qDebug
26#endif
27
29
30using namespace Qt::StringLiterals;
31
32template <typename T>
33using ShortVector = QVarLengthArray<T, 13>; // enough for month (incl. leap) and day-of-week names
34
36{
37}
38
49{
50 if (index < 0 || index >= sectionNodes.size()) {
51 qWarning("QDateTimeParser::getDigit() Internal error (%ls %d)",
52 qUtf16Printable(t.toString()), index);
53 return -1;
54 }
55 const SectionNode &node = sectionNodes.at(index);
56 switch (node.type) {
57 case TimeZoneSection: return t.offsetFromUtc();
58 case Hour24Section: case Hour12Section: return t.time().hour();
59 case MinuteSection: return t.time().minute();
60 case SecondSection: return t.time().second();
61 case MSecSection: return t.time().msec();
63 case YearSection: return t.date().year(calendar);
64 case MonthSection: return t.date().month(calendar);
65 case DaySection: return t.date().day(calendar);
67 case DayOfWeekSectionLong: return calendar.dayOfWeek(t.date());
68 case AmPmSection: return t.time().hour() > 11 ? 1 : 0;
69
70 default: break;
71 }
72
73 qWarning("QDateTimeParser::getDigit() Internal error 2 (%ls %d)",
74 qUtf16Printable(t.toString()), index);
75 return -1;
76}
77
87static int dayOfWeekDiff(int sought, int held)
88{
89 const int diff = sought - held;
90 return diff < -3 ? diff + 7 : diff > 3 ? diff - 7 : diff;
91}
92
94{
95 // True precisely if there is a day-of-week field but no day-of-month field.
96 bool result = false;
97 for (const auto &node : nodes) {
98 if (node.type & QDateTimeParser::DaySection)
99 return false;
101 result = true;
102 }
103 return result;
104}
105
118bool QDateTimeParser::setDigit(QDateTime &v, int index, int newVal) const
119{
120 if (index < 0 || index >= sectionNodes.size()) {
121 qWarning("QDateTimeParser::setDigit() Internal error (%ls %d %d)",
122 qUtf16Printable(v.toString()), index, newVal);
123 return false;
124 }
125
126 const QDate oldDate = v.date();
128 if (!date.isValid())
129 return false;
130 int weekDay = calendar.dayOfWeek(oldDate);
131 enum { NoFix, MonthDay, WeekDay } fixDay = NoFix;
132
133 const QTime time = v.time();
134 int hour = time.hour();
135 int minute = time.minute();
136 int second = time.second();
137 int msec = time.msec();
138 QTimeZone timeZone = v.timeRepresentation();
139
140 const SectionNode &node = sectionNodes.at(index);
141 switch (node.type) {
142 case Hour24Section: case Hour12Section: hour = newVal; break;
143 case MinuteSection: minute = newVal; break;
144 case SecondSection: second = newVal; break;
145 case MSecSection: msec = newVal; break;
147 case YearSection: date.year = newVal; break;
148 case MonthSection: date.month = newVal; break;
149 case DaySection:
150 if (newVal > 31) {
151 // have to keep legacy behavior. setting the
152 // date to 32 should return false. Setting it
153 // to 31 for february should return true
154 return false;
155 }
156 date.day = newVal;
157 fixDay = MonthDay;
158 break;
161 if (newVal > 7 || newVal <= 0)
162 return false;
163 date.day += dayOfWeekDiff(newVal, weekDay);
164 weekDay = newVal;
165 fixDay = WeekDay;
166 break;
167 case TimeZoneSection:
168 if (newVal < absoluteMin(index) || newVal > absoluteMax(index))
169 return false;
170 // Only offset from UTC is amenable to setting an int value:
171 timeZone = QTimeZone::fromSecondsAheadOfUtc(newVal);
172 break;
173 case AmPmSection: hour = (newVal == 0 ? hour % 12 : (hour % 12) + 12); break;
174 default:
175 qWarning("QDateTimeParser::setDigit() Internal error (%ls)",
176 qUtf16Printable(node.name()));
177 break;
178 }
179
180 if (!(node.type & DaySectionMask)) {
181 if (date.day < cachedDay)
183 fixDay = MonthDay;
184 if (weekDay > 0 && weekDay <= 7 && preferDayOfWeek(sectionNodes)) {
185 const int max = calendar.daysInMonth(date.month, date.year);
186 if (max > 0 && date.day > max)
187 date.day = max;
188 const int newDoW = calendar.dayOfWeek(calendar.dateFromParts(date));
189 if (newDoW > 0 && newDoW <= 7)
190 date.day += dayOfWeekDiff(weekDay, newDoW);
191 fixDay = WeekDay;
192 }
193 }
194
195 if (fixDay != NoFix) {
196 const int max = calendar.daysInMonth(date.month, date.year);
197 // max > 0 precisely if the year does have such a month
198 if (max > 0 && date.day > max)
199 date.day = fixDay == WeekDay ? date.day - 7 : max;
200 else if (date.day < 1)
201 date.day = fixDay == WeekDay ? date.day + 7 : 1;
202 Q_ASSERT(fixDay != WeekDay
204 }
205
206 const QDate newDate = calendar.dateFromParts(date);
207 const QTime newTime(hour, minute, second, msec);
208 if (!newDate.isValid() || !newTime.isValid())
209 return false;
210
211 v = QDateTime(newDate, newTime, timeZone);
212 return true;
213}
214
215
216
223int QDateTimeParser::absoluteMax(int s, const QDateTime &cur) const
224{
225 const SectionNode &sn = sectionNode(s);
226 switch (sn.type) {
227 case TimeZoneSection:
229 case Hour24Section:
230 case Hour12Section:
231 // This is special-cased in parseSection.
232 // We want it to be 23 for the stepBy case.
233 return 23;
234 case MinuteSection:
235 case SecondSection:
236 return 59;
237 case MSecSection:
238 return 999;
240 case YearSection:
241 // sectionMaxSize will prevent people from typing in a larger number in
242 // count == 2 sections; stepBy() will work on real years anyway.
243 return 9999;
244 case MonthSection:
246 case DaySection:
250 return 7;
251 case AmPmSection:
252 return int(UpperCase);
253 default:
254 break;
255 }
256 qWarning("QDateTimeParser::absoluteMax() Internal error (%ls)",
257 qUtf16Printable(sn.name()));
258 return -1;
259}
260
268{
269 const SectionNode &sn = sectionNode(s);
270 switch (sn.type) {
271 case TimeZoneSection:
273 case Hour24Section:
274 case Hour12Section:
275 case MinuteSection:
276 case SecondSection:
277 case MSecSection:
279 return 0;
280 case YearSection:
281 return -9999;
282 case MonthSection:
283 case DaySection:
285 case DayOfWeekSectionLong: return 1;
286 case AmPmSection: return int(NativeCase);
287 default: break;
288 }
289 qWarning("QDateTimeParser::absoluteMin() Internal error (%ls, %0x)",
290 qUtf16Printable(sn.name()), sn.type);
291 return -1;
292}
293
301{
302 if (sectionIndex < 0) {
303 switch (sectionIndex) {
305 return first;
306 case LastSectionIndex:
307 return last;
308 case NoSectionIndex:
309 return none;
310 }
311 } else if (sectionIndex < sectionNodes.size()) {
312 return sectionNodes.at(sectionIndex);
313 }
314
315 qWarning("QDateTimeParser::sectionNode() Internal error (%d)",
316 sectionIndex);
317 return none;
318}
319
321{
322 return sectionNode(sectionIndex).type;
323}
324
325
332int QDateTimeParser::sectionPos(int sectionIndex) const
333{
334 return sectionPos(sectionNode(sectionIndex));
335}
336
338{
339 switch (sn.type) {
340 case FirstSection: return 0;
341 case LastSection: return displayText().size() - 1;
342 default: break;
343 }
344 if (sn.pos == -1) {
345 qWarning("QDateTimeParser::sectionPos Internal error (%ls)", qUtf16Printable(sn.name()));
346 return -1;
347 }
348 return sn.pos;
349}
350
358{
359 qsizetype digits = 0;
360 for (QStringIterator it(str); it.hasNext();) {
361 if (!QChar::isDigit(it.next()))
362 break;
363 digits++;
364 }
365 return digits;
366}
367
376{
377 // ### Align unquoting format strings for both from/toString(), QTBUG-110669
378 const QLatin1Char quote('\'');
379 const QLatin1Char slash('\\');
380 const QLatin1Char zero('0');
381 QString ret;
382 QChar status(zero);
383 const int max = str.size();
384 for (int i=0; i<max; ++i) {
385 if (str.at(i) == quote) {
386 if (status != quote)
387 status = quote;
388 else if (!ret.isEmpty() && str.at(i - 1) == slash)
389 ret[ret.size() - 1] = quote;
390 else
391 status = zero;
392 } else {
393 ret += str.at(i);
394 }
395 }
396 return ret;
397}
398
399static inline int countRepeat(QStringView str, int index, int maxCount)
400{
401 str = str.sliced(index);
402 if (maxCount < str.size())
404
405 return qt_repeatCount(str);
406}
407
408static inline void appendSeparator(QStringList *list, QStringView string,
409 int from, int size, int lastQuote)
410{
411 Q_ASSERT(size >= 0 && from + size <= string.size());
412 const QStringView separator = string.sliced(from, size);
413 list->append(lastQuote >= from ? unquote(separator) : separator.toString());
414}
415
423{
424 const QLatin1Char quote('\'');
425 const QLatin1Char slash('\\');
426 const QLatin1Char zero('0');
427 if (newFormat == displayFormat && !newFormat.isEmpty())
428 return true;
429
430 QDTPDEBUGN("parseFormat: %s", newFormat.toLatin1().constData());
431
432 QList<SectionNode> newSectionNodes;
433 Sections newDisplay;
434 QStringList newSeparators;
435 int i, index = 0;
436 int add = 0;
437 QLatin1Char status(zero);
438 const int max = newFormat.size();
439 int lastQuote = -1;
440 for (i = 0; i<max; ++i) {
441 if (newFormat.at(i) == quote) {
442 lastQuote = i;
443 ++add;
444 if (status != quote)
445 status = quote;
446 else if (i > 0 && newFormat.at(i - 1) != slash)
447 status = zero;
448 } else if (status != quote) {
449 const char sect = newFormat.at(i).toLatin1();
450 switch (sect) {
451 case 'H':
452 case 'h':
453 if (parserType != QMetaType::QDate) {
454 const Section hour = (sect == 'h') ? Hour12Section : Hour24Section;
455 const SectionNode sn = { hour, i - add, countRepeat(newFormat, i, 2), 0 };
456 newSectionNodes.append(sn);
457 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
458 i += sn.count - 1;
459 index = i + 1;
460 newDisplay |= hour;
461 }
462 break;
463 case 'm':
464 if (parserType != QMetaType::QDate) {
465 const SectionNode sn = { MinuteSection, i - add, countRepeat(newFormat, i, 2), 0 };
466 newSectionNodes.append(sn);
467 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
468 i += sn.count - 1;
469 index = i + 1;
470 newDisplay |= MinuteSection;
471 }
472 break;
473 case 's':
474 if (parserType != QMetaType::QDate) {
475 const SectionNode sn = { SecondSection, i - add, countRepeat(newFormat, i, 2), 0 };
476 newSectionNodes.append(sn);
477 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
478 i += sn.count - 1;
479 index = i + 1;
480 newDisplay |= SecondSection;
481 }
482 break;
483
484 case 'z':
485 if (parserType != QMetaType::QDate) {
486 const int repeat = countRepeat(newFormat, i, 3);
487 const SectionNode sn = { MSecSection, i - add, repeat < 3 ? 1 : 3, 0 };
488 newSectionNodes.append(sn);
489 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
490 i += repeat - 1;
491 index = i + 1;
492 newDisplay |= MSecSection;
493 }
494 break;
495 case 'A':
496 case 'a':
497 if (parserType != QMetaType::QDate) {
498 const int pos = i - add;
499 Case caseOpt = sect == 'A' ? UpperCase : LowerCase;
500 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
501 newDisplay |= AmPmSection;
502 if (i + 1 < newFormat.size()
503 && newFormat.sliced(i + 1).startsWith(u'p', Qt::CaseInsensitive)) {
504 ++i;
505 if (newFormat.at(i) != QLatin1Char(caseOpt == UpperCase ? 'P' : 'p'))
506 caseOpt = NativeCase;
507 }
508 const SectionNode sn = { AmPmSection, pos, int(caseOpt), 0 };
509 newSectionNodes.append(sn);
510 index = i + 1;
511 }
512 break;
513 case 'y':
514 if (parserType != QMetaType::QTime) {
515 const int repeat = countRepeat(newFormat, i, 4);
516 if (repeat >= 2) {
517 const SectionNode sn = { repeat == 4 ? YearSection : YearSection2Digits,
518 i - add, repeat == 4 ? 4 : 2, 0 };
519 newSectionNodes.append(sn);
520 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
521 i += sn.count - 1;
522 index = i + 1;
523 newDisplay |= sn.type;
524 }
525 }
526 break;
527 case 'M':
528 if (parserType != QMetaType::QTime) {
529 const SectionNode sn = { MonthSection, i - add, countRepeat(newFormat, i, 4), 0 };
530 newSectionNodes.append(sn);
531 newSeparators.append(unquote(newFormat.first(i).sliced(index)));
532 i += sn.count - 1;
533 index = i + 1;
534 newDisplay |= MonthSection;
535 }
536 break;
537 case 'd':
538 if (parserType != QMetaType::QTime) {
539 const int repeat = countRepeat(newFormat, i, 4);
540 const Section sectionType = (repeat == 4 ? DayOfWeekSectionLong
541 : (repeat == 3 ? DayOfWeekSectionShort : DaySection));
542 const SectionNode sn = { sectionType, i - add, repeat, 0 };
543 newSectionNodes.append(sn);
544 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
545 i += sn.count - 1;
546 index = i + 1;
547 newDisplay |= sn.type;
548 }
549 break;
550 case 't':
551 if (parserType == QMetaType::QDateTime) {
552 const SectionNode sn
553 = { TimeZoneSection, i - add, countRepeat(newFormat, i, 4), 0 };
554 newSectionNodes.append(sn);
555 appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
556 i += sn.count - 1;
557 index = i + 1;
558 newDisplay |= TimeZoneSection;
559 }
560 break;
561 default:
562 break;
563 }
564 }
565 }
566 if (newSectionNodes.isEmpty() && context == DateTimeEdit)
567 return false;
568
569 if ((newDisplay & (AmPmSection|Hour12Section)) == Hour12Section) {
570 const int count = newSectionNodes.size();
571 for (int i = 0; i < count; ++i) {
572 SectionNode &node = newSectionNodes[i];
573 if (node.type == Hour12Section)
574 node.type = Hour24Section;
575 }
576 }
577
578 if (index < max)
579 appendSeparator(&newSeparators, newFormat, index, max - index, lastQuote);
580 else
581 newSeparators.append(QString());
582
583 displayFormat = newFormat.toString();
584 separators = newSeparators;
585 sectionNodes = newSectionNodes;
586 display = newDisplay;
587 last.pos = -1;
588
589// for (int i=0; i<sectionNodes.size(); ++i) {
590// QDTPDEBUG << sectionNodes.at(i).name() << sectionNodes.at(i).count;
591// }
592
593 QDTPDEBUG << newFormat << displayFormat;
594 QDTPDEBUGN("separators:\n'%s'", separators.join("\n"_L1).toLatin1().constData());
595
596 return true;
597}
598
605int QDateTimeParser::sectionSize(int sectionIndex) const
606{
607 if (sectionIndex < 0)
608 return 0;
609
610 if (sectionIndex >= sectionNodes.size()) {
611 qWarning("QDateTimeParser::sectionSize Internal error (%d)", sectionIndex);
612 return -1;
613 }
614
615 if (sectionIndex == sectionNodes.size() - 1) {
616 // In some cases there is a difference between displayText() and text.
617 // e.g. when text is 2000/01/31 and displayText() is "2000/2/31" - text
618 // is the previous value and displayText() is the new value.
619 // The size difference is always due to leading zeroes.
620 int sizeAdjustment = 0;
621 const int displayTextSize = displayText().size();
622 if (displayTextSize != m_text.size()) {
623 // Any zeroes added before this section will affect our size.
624 int preceedingZeroesAdded = 0;
625 if (sectionNodes.size() > 1 && context == DateTimeEdit) {
626 const auto begin = sectionNodes.cbegin();
627 const auto end = begin + sectionIndex;
628 for (auto sectionIt = begin; sectionIt != end; ++sectionIt)
629 preceedingZeroesAdded += sectionIt->zeroesAdded;
630 }
631 sizeAdjustment = preceedingZeroesAdded;
632 }
633
634 return displayTextSize + sizeAdjustment - sectionPos(sectionIndex) - separators.last().size();
635 } else {
636 return sectionPos(sectionIndex + 1) - sectionPos(sectionIndex)
637 - separators.at(sectionIndex + 1).size();
638 }
639}
640
641
642int QDateTimeParser::sectionMaxSize(Section s, int count) const
643{
644#if QT_CONFIG(textdate)
645 int mcount = calendar.maximumMonthsInYear();
646#endif
647
648 switch (s) {
649 case FirstSection:
650 case NoSection:
651 case LastSection:
652 return 0;
653
654 case AmPmSection:
655 // Special: "count" here is a case flag, not field width !
656 return qMax(getAmPmText(AmText, Case(count)).size(),
657 getAmPmText(PmText, Case(count)).size());
658
659 case Hour24Section:
660 case Hour12Section:
661 case MinuteSection:
662 case SecondSection:
663 case DaySection:
664 return 2;
665
668#if QT_CONFIG(textdate)
669 mcount = 7;
671#endif
672 case MonthSection:
673#if QT_CONFIG(textdate)
674 if (count <= 2)
675 return 2;
676
677 {
678 int ret = 0;
679 const QLocale l = locale();
681 for (int i=1; i<=mcount; ++i) {
682 const QString str = (s == MonthSection
684 : l.dayName(i, format));
685 ret = qMax(str.size(), ret);
686 }
687 return ret;
688 }
689#else
690 return 2;
691#endif // textdate
692 case MSecSection:
693 return 3;
694 case YearSection:
695 return 4;
697 return 2;
698 case TimeZoneSection:
699 // Arbitrarily many tokens (each up to 14 bytes) joined with / separators:
700 return std::numeric_limits<int>::max();
701
703 case Internal:
704 case TimeSectionMask:
705 case DateSectionMask:
706 case HourSectionMask:
707 case YearSectionMask:
709 case DaySectionMask:
710 qWarning("QDateTimeParser::sectionMaxSize: Invalid section %s",
711 SectionNode::name(s).toLatin1().constData());
712
713 case NoSectionIndex:
715 case LastSectionIndex:
717 // these cases can't happen
718 break;
719 }
720 return -1;
721}
722
723
724int QDateTimeParser::sectionMaxSize(int index) const
725{
726 const SectionNode &sn = sectionNode(index);
727 return sectionMaxSize(sn.type, sn.count);
728}
729
738QString QDateTimeParser::sectionText(const QString &text, int sectionIndex, int index) const
739{
740 const SectionNode &sn = sectionNode(sectionIndex);
741 switch (sn.type) {
742 case NoSectionIndex:
744 case LastSectionIndex:
745 return QString();
746 default: break;
747 }
748
749 return text.mid(index, sectionSize(sectionIndex));
750}
751
752QString QDateTimeParser::sectionText(int sectionIndex) const
753{
754 const SectionNode &sn = sectionNode(sectionIndex);
755 return sectionText(displayText(), sectionIndex, sn.pos);
756}
757
758QDateTimeParser::ParsedSection
759QDateTimeParser::parseSection(const QDateTime &currentValue, int sectionIndex, int offset) const
760{
761 ParsedSection result; // initially Invalid
762 const SectionNode &sn = sectionNode(sectionIndex);
763 Q_ASSERT_X(!(sn.type & Internal),
764 "QDateTimeParser::parseSection", "Internal error");
765
766 const int sectionmaxsize = sectionMaxSize(sectionIndex);
767 const bool negate = (sn.type == YearSection && m_text.size() > offset
768 && m_text.at(offset) == u'-');
769 const int negativeYearOffset = negate ? 1 : 0;
770
771 // If the fields we've read thus far imply a time in a spring-forward,
772 // coerce to a nearby valid time:
773 const QDateTime defaultValue = currentValue.isValid() ? currentValue
774 : QDateTime::fromMSecsSinceEpoch(currentValue.toMSecsSinceEpoch());
775
776 QStringView sectionTextRef =
777 QStringView { m_text }.mid(offset + negativeYearOffset, sectionmaxsize);
778
779 QDTPDEBUG << "sectionValue for" << sn.name()
780 << "with text" << m_text << "and (at" << offset
781 << ") st:" << sectionTextRef;
782
783 switch (sn.type) {
784 case AmPmSection: {
785 QString sectiontext = sectionTextRef.toString();
786 int used;
787 const int ampm = findAmPm(sectiontext, sectionIndex, &used);
788 switch (ampm) {
789 case AM: // sectiontext == AM
790 case PM: // sectiontext == PM
791 result = ParsedSection(Acceptable, ampm, used);
792 break;
793 case PossibleAM: // sectiontext => AM
794 case PossiblePM: // sectiontext => PM
795 result = ParsedSection(Intermediate, ampm - 2, used);
796 break;
797 case PossibleBoth: // sectiontext => AM|PM
798 result = ParsedSection(Intermediate, 0, used);
799 break;
800 case Neither:
801 QDTPDEBUG << "invalid because findAmPm(" << sectiontext << ") returned -1";
802 break;
803 default:
804 QDTPDEBUGN("This should never happen (findAmPm returned %d)", ampm);
805 break;
806 }
807 if (result.state != Invalid)
808 m_text.replace(offset, used, sectiontext.constData(), used);
809 break; }
810 case TimeZoneSection:
811 result = findTimeZone(sectionTextRef, defaultValue,
812 absoluteMax(sectionIndex),
813 absoluteMin(sectionIndex), sn.count);
814 break;
815 case MonthSection:
818 if (sn.count >= 3) {
819 QString sectiontext = sectionTextRef.toString();
820 int num = 0, used = 0;
821 if (sn.type == MonthSection) {
822 const QDate minDate = getMinimum().date();
823 const int year = defaultValue.date().year(calendar);
824 const int min = (year == minDate.year(calendar)) ? minDate.month(calendar) : 1;
825 num = findMonth(sectiontext.toLower(), min, sectionIndex, year, &sectiontext, &used);
826 } else {
827 num = findDay(sectiontext.toLower(), 1, sectionIndex, &sectiontext, &used);
828 }
829
830 result = ParsedSection(Intermediate, num, used);
831 if (num != -1) {
832 m_text.replace(offset, used, sectiontext.constData(), used);
833 if (used == sectiontext.size())
834 result = ParsedSection(Acceptable, num, used);
835 }
836 break;
837 }
839 // All numeric:
840 case DaySection:
841 case YearSection:
843 case Hour12Section:
844 case Hour24Section:
845 case MinuteSection:
846 case SecondSection:
847 case MSecSection: {
848 int used = negativeYearOffset;
849 // We already sliced off the - sign if it was legitimately present.
850 if (sectionTextRef.startsWith(u'-')
851 || sectionTextRef.startsWith(u'+')) {
852 if (separators.at(sectionIndex + 1).startsWith(sectionTextRef[0]))
853 result = ParsedSection(Intermediate, 0, used);
854 break;
855 }
856 QStringView digitsStr = sectionTextRef.left(digitCount(sectionTextRef));
857
858 if (digitsStr.isEmpty()) {
859 result = ParsedSection(Intermediate, 0, used);
860 } else {
861 const QLocale loc = locale();
862 const int absMax = absoluteMax(sectionIndex);
863 const int absMin = absoluteMin(sectionIndex);
864
865 int lastVal = -1;
866
867 for (; digitsStr.size(); digitsStr.chop(1)) {
868 bool ok = false;
869 int value = int(loc.toUInt(digitsStr, &ok));
870 if (!ok || (negate ? -value < absMin : value > absMax))
871 continue;
872
873 if (sn.type == Hour12Section) {
874 if (value > 12)
875 continue;
876 if (value == 12)
877 value = 0;
878 }
879
880 QDTPDEBUG << digitsStr << value << digitsStr.size();
881 lastVal = value;
882 used += digitsStr.size();
883 break;
884 }
885
886 if (lastVal == -1) {
887 const auto &sep = separators.at(sectionIndex + 1);
888 if (sep.startsWith(sectionTextRef[0])
889 || (negate && sep.startsWith(m_text.at(offset))))
890 result = ParsedSection(Intermediate, 0, 0);
891 else
892 QDTPDEBUG << "invalid because" << sectionTextRef << "can't become a uint"
893 << lastVal;
894 } else {
895 if (negate)
896 lastVal = -lastVal;
897 const FieldInfo fi = fieldInfo(sectionIndex);
898 const bool unfilled = used - negativeYearOffset < sectionmaxsize;
899 if (unfilled && fi & Fraction) { // typing 2 in a zzz field should be .200, not .002
900 for (int i = used; i < sectionmaxsize; ++i)
901 lastVal *= 10;
902 }
903 // Even those *= 10s can't take last above absMax:
904 Q_ASSERT(negate ? lastVal >= absMin : lastVal <= absMax);
905 if (negate ? lastVal > absMax : lastVal < absMin) {
906 if (unfilled) {
907 result = ParsedSection(Intermediate, lastVal, used);
908 } else if (negate) {
909 QDTPDEBUG << "invalid because" << lastVal << "is greater than absoluteMax"
910 << absMax;
911 } else {
912 QDTPDEBUG << "invalid because" << lastVal << "is less than absoluteMin"
913 << absMin;
914 }
915
916 } else if (unfilled && (fi & (FixedWidth | Numeric)) == (FixedWidth | Numeric)) {
917 if (skipToNextSection(sectionIndex, defaultValue, digitsStr)) {
918 const int missingZeroes = sectionmaxsize - digitsStr.size();
919 result = ParsedSection(Acceptable, lastVal, sectionmaxsize, missingZeroes);
920 m_text.insert(offset, QString(missingZeroes, u'0'));
921 ++(const_cast<QDateTimeParser*>(this)->sectionNodes[sectionIndex].zeroesAdded);
922 } else {
923 result = ParsedSection(Intermediate, lastVal, used);;
924 }
925 } else {
926 result = ParsedSection(Acceptable, lastVal, used);
927 }
928 }
929 }
930 break; }
931 default:
932 qWarning("QDateTimeParser::parseSection Internal error (%ls %d)",
933 qUtf16Printable(sn.name()), sectionIndex);
934 return result;
935 }
936 Q_ASSERT(result.state != Invalid || result.value == -1);
937
938 return result;
939}
940
949static int weekDayWithinMonth(QCalendar calendar, int year, int month, int day, int weekDay)
950{
951 // TODO: can we adapt this to cope gracefully with intercallary days (day of
952 // week > 7) without making it slower for more widely-used calendars ?
953 const int maxDay = calendar.daysInMonth(month, year); // 0 if no such month
954 day = maxDay > 1 ? qBound(1, day, maxDay) : qMax(1, day);
955 day += dayOfWeekDiff(weekDay, calendar.dayOfWeek(QDate(year, month, day, calendar)));
956 return day <= 0 ? day + 7 : maxDay > 0 && day > maxDay ? day - 7 : day;
957}
958
967static QDate actualDate(QDateTimeParser::Sections known, const QCalendar &calendar,
968 int year, int year2digits, int month, int day, int dayofweek)
969{
970 QDate actual(year, month, day, calendar);
971 if (actual.isValid() && year % 100 == year2digits && calendar.dayOfWeek(actual) == dayofweek)
972 return actual; // The obvious candidate is fine :-)
973
974 if (dayofweek < 1 || dayofweek > 7) // Intercallary (or invalid): ignore
975 known &= ~QDateTimeParser::DayOfWeekSectionMask;
976
977 // Assuming year > 0 ...
978 if (year % 100 != year2digits) {
980 // Over-ride year, even if specified:
981 year += year2digits - year % 100;
982 known &= ~QDateTimeParser::YearSection;
983 } else {
984 year2digits = year % 100;
985 }
986 }
987 Q_ASSERT(year % 100 == year2digits);
988
989 if (month < 1) { // If invalid, clip to nearest valid and ignore in known.
990 month = 1;
991 known &= ~QDateTimeParser::MonthSection;
992 } else if (month > 12) {
993 month = 12;
994 known &= ~QDateTimeParser::MonthSection;
995 }
996
997 QDate first(year, month, 1, calendar);
998 int last = known & QDateTimeParser::MonthSection
1000 ? calendar.daysInMonth(month, year) : calendar.daysInMonth(month))
1001 : 0;
1002 // We can only fix DOW if we know year as well as month (hence last):
1003 const bool fixDayOfWeek = last && known & QDateTimeParser::YearSection
1005 // If we also know day-of-week, tweak last to the last in the month that matches it:
1006 if (fixDayOfWeek) {
1007 const int diff = (dayofweek - calendar.dayOfWeek(first) - last) % 7;
1008 Q_ASSERT(diff <= 0); // C++11 specifies (-ve) % (+ve) to be <= 0.
1009 last += diff;
1010 }
1011 if (day < 1) {
1012 if (fixDayOfWeek) {
1013 day = 1 + dayofweek - calendar.dayOfWeek(first);
1014 if (day < 1)
1015 day += 7;
1016 } else {
1017 day = 1;
1018 }
1019 known &= ~QDateTimeParser::DaySection;
1020 } else if (day > calendar.maximumDaysInMonth()) {
1021 day = last;
1022 known &= ~QDateTimeParser::DaySection;
1023 } else if (last && day > last && (known & QDateTimeParser::DaySection) == 0) {
1024 day = last;
1025 }
1026
1027 actual = QDate(year, month, day, calendar);
1028 if (!actual.isValid() // We can't do better than we have, in this case
1029 || (known & QDateTimeParser::DaySection
1031 && known & QDateTimeParser::YearSection) // ditto
1032 || calendar.dayOfWeek(actual) == dayofweek // Good enough, use it.
1033 || (known & QDateTimeParser::DayOfWeekSectionMask) == 0) { // No contradiction, use it.
1034 return actual;
1035 }
1036
1037 /*
1038 Now it gets trickier.
1039
1040 We have some inconsistency in our data; we've been told day of week, but
1041 it doesn't fit with our year, month and day. At least one of these is
1042 unknown, though: so we can fix day of week by tweaking it.
1043 */
1044
1045 if ((known & QDateTimeParser::DaySection) == 0) {
1046 // Relatively easy to fix.
1047 day = weekDayWithinMonth(calendar, year, month, day, dayofweek);
1048 actual = QDate(year, month, day, calendar);
1049 return actual;
1050 }
1051
1052 if ((known & QDateTimeParser::MonthSection) == 0) {
1053 /*
1054 Try possible month-offsets, m, preferring small; at least one (present
1055 month doesn't work) and at most 11 (max month, 12, minus min, 1); try
1056 in both directions, ignoring any offset that takes us out of range.
1057 */
1058 for (int m = 1; m < 12; m++) {
1059 if (m < month) {
1060 actual = QDate(year, month - m, day, calendar);
1061 if (calendar.dayOfWeek(actual) == dayofweek)
1062 return actual;
1063 }
1064 if (m + month <= 12) {
1065 actual = QDate(year, month + m, day, calendar);
1066 if (calendar.dayOfWeek(actual) == dayofweek)
1067 return actual;
1068 }
1069 }
1070 // Should only get here in corner cases; e.g. day == 31
1071 actual = QDate(year, month, day, calendar); // Restore from trial values.
1072 }
1073
1074 if ((known & QDateTimeParser::YearSection) == 0) {
1076 /*
1077 Two-digit year and month are specified; choice of century can only
1078 fix this if diff is in one of {1, 2, 5} or {2, 4, 6}; but not if
1079 diff is in the other. It's also only reasonable to consider
1080 adjacent century, e.g. if year thinks it's 2012 and two-digit year
1081 is '97, it makes sense to consider 1997. If either adjacent
1082 century does work, the other won't.
1083 */
1084 actual = QDate(year + 100, month, day, calendar);
1085 if (calendar.dayOfWeek(actual) == dayofweek)
1086 return actual;
1087 actual = QDate(year - 100, month, day, calendar);
1088 if (calendar.dayOfWeek(actual) == dayofweek)
1089 return actual;
1090 } else {
1091 // Offset by 7 is usually enough, but rare cases may need more:
1092 for (int y = 1; y < 12; y++) {
1093 actual = QDate(year - y, month, day, calendar);
1094 if (calendar.dayOfWeek(actual) == dayofweek)
1095 return actual;
1096 actual = QDate(year + y, month, day, calendar);
1097 if (calendar.dayOfWeek(actual) == dayofweek)
1098 return actual;
1099 }
1100 }
1101 actual = QDate(year, month, day, calendar); // Restore from trial values.
1102 }
1103
1104 return actual; // It'll just have to do :-(
1105}
1106
1111static QTime actualTime(QDateTimeParser::Sections known,
1112 int hour, int hour12, int ampm,
1113 int minute, int second, int msec)
1114{
1115 // If we have no conflict, or don't know enough to diagonose one, use this:
1116 QTime actual(hour, minute, second, msec);
1117 if (hour12 < 0 || hour12 > 12) { // ignore bogus value
1118 known &= ~QDateTimeParser::Hour12Section;
1119 hour12 = hour % 12;
1120 }
1121
1122 if (ampm == -1 || (known & QDateTimeParser::AmPmSection) == 0) {
1123 if ((known & QDateTimeParser::Hour12Section) == 0 || hour % 12 == hour12)
1124 return actual;
1125
1126 if ((known & QDateTimeParser::Hour24Section) == 0)
1127 hour = hour12 + (hour > 12 ? 12 : 0);
1128 } else {
1129 Q_ASSERT(ampm == 0 || ampm == 1);
1130 if (hour - hour12 == ampm * 12)
1131 return actual;
1132
1133 if ((known & QDateTimeParser::Hour24Section) == 0
1134 && known & QDateTimeParser::Hour12Section) {
1135 hour = hour12 + ampm * 12;
1136 }
1137 }
1138 actual = QTime(hour, minute, second, msec);
1139 return actual;
1140}
1141
1142/*
1143 \internal
1144*/
1146{
1147 for (int i = 0; i < 2; ++i) {
1148 const QString zone(qTzName(i));
1149 if (name.startsWith(zone))
1150 return zone.size();
1151 }
1152 return 0;
1153}
1154
1159QDateTimeParser::scanString(const QDateTime &defaultValue, bool fixup) const
1160{
1162 bool conflicts = false;
1163 const int sectionNodesCount = sectionNodes.size();
1164 int padding = 0;
1165 int pos = 0;
1166 int year, month, day;
1167 const QDate defaultDate = defaultValue.date();
1168 const QTime defaultTime = defaultValue.time();
1169 defaultDate.getDate(&year, &month, &day);
1170 int year2digits = year % 100;
1171 int hour = defaultTime.hour();
1172 int hour12 = -1;
1173 int minute = defaultTime.minute();
1174 int second = defaultTime.second();
1175 int msec = defaultTime.msec();
1176 int dayofweek = calendar.dayOfWeek(defaultDate);
1177 QTimeZone timeZone = defaultValue.timeRepresentation();
1178
1179 int ampm = -1;
1180 Sections isSet = NoSection;
1181
1182 for (int index = 0; index < sectionNodesCount; ++index) {
1184 const QString &separator = separators.at(index);
1185 if (QStringView{m_text}.mid(pos, separator.size()) != separator) {
1186 QDTPDEBUG << "invalid because" << QStringView{m_text}.mid(pos, separator.size())
1187 << "!=" << separator
1188 << index << pos << currentSectionIndex;
1189 return StateNode();
1190 }
1191 pos += separator.size();
1192 sectionNodes[index].pos = pos;
1193 int *current = nullptr;
1194 int zoneOffset; // Needed to serve as *current when setting zone
1195 const SectionNode sn = sectionNodes.at(index);
1196 ParsedSection sect;
1197
1198 {
1199 const QDate date = actualDate(isSet, calendar, year, year2digits,
1200 month, day, dayofweek);
1201 const QTime time = actualTime(isSet, hour, hour12, ampm, minute, second, msec);
1202 sect = parseSection(QDateTime(date, time, timeZone), index, pos);
1203 }
1204
1205 QDTPDEBUG << "sectionValue" << sn.name() << m_text
1206 << "pos" << pos << "used" << sect.used << stateName(sect.state);
1207
1208 padding += sect.zeroes;
1209 if (fixup && sect.state == Intermediate && sect.used < sn.count) {
1210 const FieldInfo fi = fieldInfo(index);
1211 if ((fi & (Numeric|FixedWidth)) == (Numeric|FixedWidth)) {
1212 const QString newText = QString("%1"_L1).arg(sect.value, sn.count, 10, '0'_L1);
1213 m_text.replace(pos, sect.used, newText);
1214 sect.used = sn.count;
1215 }
1216 }
1217
1218 state = qMin<State>(state, sect.state);
1219 // QDateTimeEdit can fix Intermediate and zeroes, but input needing that didn't match format:
1220 if (state == Invalid || (context == FromString && (state == Intermediate || sect.zeroes)))
1221 return StateNode();
1222
1223 switch (sn.type) {
1224 case TimeZoneSection:
1225 current = &zoneOffset;
1226 if (sect.used > 0) {
1227 // Synchronize with what findTimeZone() found:
1228 QStringView zoneName = QStringView{m_text}.sliced(pos, sect.used);
1229 Q_ASSERT(!zoneName.isEmpty()); // sect.used > 0
1230
1231 const QStringView offsetStr
1232 = zoneName.startsWith("UTC"_L1) ? zoneName.sliced(3) : zoneName;
1233 const bool isUtcOffset = offsetStr.startsWith(u'+') || offsetStr.startsWith(u'-');
1234 const bool isUtc = zoneName == "Z"_L1 || zoneName == "UTC"_L1;
1235
1236 if (isUtc || isUtcOffset) {
1237 timeZone = QTimeZone::fromSecondsAheadOfUtc(sect.value);
1238 } else {
1239#if QT_CONFIG(timezone)
1240 QTimeZone namedZone = QTimeZone(zoneName.toLatin1());
1241 if (namedZone.isValid()) {
1242 timeZone = namedZone;
1243 } else {
1245 timeZone = QTimeZone::LocalTime;
1246 }
1247#else
1248 timeZone = QTimeZone::LocalTime;
1249#endif
1250 }
1251 }
1252 break;
1253 case Hour24Section: current = &hour; break;
1254 case Hour12Section: current = &hour12; break;
1255 case MinuteSection: current = &minute; break;
1256 case SecondSection: current = &second; break;
1257 case MSecSection: current = &msec; break;
1258 case YearSection: current = &year; break;
1259 case YearSection2Digits: current = &year2digits; break;
1260 case MonthSection: current = &month; break;
1262 case DayOfWeekSectionLong: current = &dayofweek; break;
1263 case DaySection: current = &day; sect.value = qMax<int>(1, sect.value); break;
1264 case AmPmSection: current = &ampm; break;
1265 default:
1266 qWarning("QDateTimeParser::parse Internal error (%ls)",
1267 qUtf16Printable(sn.name()));
1268 return StateNode();
1269 }
1270 Q_ASSERT(current);
1271 Q_ASSERT(sect.state != Invalid);
1272
1273 if (sect.used > 0)
1274 pos += sect.used;
1275 QDTPDEBUG << index << sn.name() << "is set to"
1276 << pos << "state is" << stateName(state);
1277
1278 if (isSet & sn.type && *current != sect.value) {
1279 QDTPDEBUG << "CONFLICT " << sn.name() << *current << sect.value;
1280 conflicts = true;
1282 continue;
1283 }
1284 *current = sect.value;
1285
1286 // Record the present section:
1287 isSet |= sn.type;
1288 }
1289
1290 if (QStringView{m_text}.sliced(pos) != separators.last()) {
1291 QDTPDEBUG << "invalid because" << QStringView{m_text}.sliced(pos)
1292 << "!=" << separators.last() << pos;
1293 return StateNode();
1294 }
1295
1296 if (parserType != QMetaType::QTime) {
1297 if (year % 100 != year2digits && (isSet & YearSection2Digits)) {
1298 if (!(isSet & YearSection)) {
1299 year = (year / 100) * 100;
1300 year += year2digits;
1301 } else {
1302 conflicts = true;
1303 const SectionNode &sn = sectionNode(currentSectionIndex);
1304 if (sn.type == YearSection2Digits) {
1305 year = (year / 100) * 100;
1306 year += year2digits;
1307 }
1308 }
1309 }
1310
1311 const auto fieldType = sectionType(currentSectionIndex);
1312 const QDate date(year, month, day, calendar);
1313 if ((!date.isValid() || dayofweek != calendar.dayOfWeek(date))
1314 && state == Acceptable && isSet & DayOfWeekSectionMask) {
1315 if (isSet & DaySection)
1316 conflicts = true;
1317 // Change to day of week should adjust day of month;
1318 // when day of month isn't set, so should change to year or month.
1319 if (currentSectionIndex == -1 || fieldType & DayOfWeekSectionMask
1320 || (!conflicts && (fieldType & (YearSectionMask | MonthSection)))) {
1321 day = weekDayWithinMonth(calendar, year, month, day, dayofweek);
1322 QDTPDEBUG << year << month << day << dayofweek
1323 << calendar.dayOfWeek(QDate(year, month, day, calendar));
1324 }
1325 }
1326
1327 bool needfixday = false;
1328 if (fieldType & DaySectionMask) {
1329 cachedDay = day;
1330 } else if (cachedDay > day && !(isSet & DayOfWeekSectionMask && state == Acceptable)) {
1331 day = cachedDay;
1332 needfixday = true;
1333 }
1334
1335 if (!calendar.isDateValid(year, month, day)) {
1336 if (day <= calendar.maximumDaysInMonth())
1337 cachedDay = day;
1338 if (day > calendar.minimumDaysInMonth() && calendar.isDateValid(year, month, 1))
1339 needfixday = true;
1340 }
1341 if (needfixday) {
1342 if (context == FromString)
1343 return StateNode();
1344 if (state == Acceptable && fixday) {
1345 day = qMin<int>(day, calendar.daysInMonth(month, year));
1346
1347 const QLocale loc = locale();
1348 for (int i=0; i<sectionNodesCount; ++i) {
1349 const SectionNode sn = sectionNode(i);
1350 if (sn.type & DaySection) {
1351 m_text.replace(sectionPos(sn), sectionSize(i), loc.toString(day));
1352 } else if (sn.type & DayOfWeekSectionMask) {
1353 const int dayOfWeek = calendar.dayOfWeek(QDate(year, month, day, calendar));
1354 const QLocale::FormatType dayFormat =
1355 (sn.type == DayOfWeekSectionShort
1357 const QString dayName(loc.dayName(dayOfWeek, dayFormat));
1358 m_text.replace(sectionPos(sn), sectionSize(i), dayName);
1359 }
1360 }
1361 } else if (state > Intermediate) {
1363 }
1364 }
1365 }
1366
1367 if (parserType != QMetaType::QDate) {
1368 if (isSet & Hour12Section) {
1369 const bool hasHour = isSet.testAnyFlag(Hour24Section);
1370 if (ampm == -1) // If we don't know from hour, assume am:
1371 ampm = !hasHour || hour < 12 ? 0 : 1;
1372 hour12 = hour12 % 12 + ampm * 12;
1373 if (!hasHour)
1374 hour = hour12;
1375 else if (hour != hour12)
1376 conflicts = true;
1377 } else if (ampm != -1) {
1378 if (!(isSet & (Hour24Section)))
1379 hour = 12 * ampm; // Special case: only ap section
1380 else if ((ampm == 0) != (hour < 12))
1381 conflicts = true;
1382 }
1383 }
1384
1385 QDTPDEBUG << year << month << day << hour << minute << second << msec;
1387
1388 const QDate date(year, month, day, calendar);
1389 const QTime time(hour, minute, second, msec);
1390 const QDateTime when = QDateTime(date, time, timeZone);
1391
1392 // If hour wasn't specified, check the default we're using exists on the
1393 // given date (which might be a spring-forward, skipping an hour).
1394 if (!(isSet & HourSectionMask) && !when.isValid()) {
1395 switch (parserType) {
1396 case QMetaType::QDateTime: {
1397 qint64 msecs = when.toMSecsSinceEpoch();
1398 // Fortunately, that gets a useful answer, even though when is invalid ...
1399 const QDateTime replace = QDateTime::fromMSecsSinceEpoch(msecs, timeZone);
1400 const QTime tick = replace.time();
1401 if (replace.date() == date
1402 && (!(isSet & MinuteSection) || tick.minute() == minute)
1403 && (!(isSet & SecondSection) || tick.second() == second)
1404 && (!(isSet & MSecSection) || tick.msec() == msec)) {
1405 return StateNode(replace, state, padding, conflicts);
1406 }
1407 } break;
1408 case QMetaType::QDate:
1409 // Don't care about time, so just use start of day (and ignore spec):
1410 return StateNode(date.startOfDay(QTimeZone::UTC), state, padding, conflicts);
1411 break;
1412 case QMetaType::QTime:
1413 // Don't care about date or representation, so pick a safe representation:
1414 return StateNode(QDateTime(date, time, QTimeZone::UTC), state, padding, conflicts);
1415 default:
1416 Q_UNREACHABLE_RETURN(StateNode());
1417 }
1418 }
1419
1420 return StateNode(when, state, padding, conflicts);
1421}
1422
1429 const QDateTime &defaultValue, bool fixup) const
1430{
1431 const QDateTime minimum = getMinimum();
1432 const QDateTime maximum = getMaximum();
1433 m_text = input;
1434
1435 QDTPDEBUG << "parse" << input;
1436 StateNode scan = scanString(defaultValue, fixup);
1437 QDTPDEBUGN("'%s' => '%s'(%s)", m_text.toLatin1().constData(),
1438 scan.value.toString("yyyy/MM/dd hh:mm:ss.zzz"_L1).toLatin1().constData(),
1439 stateName(scan.state).toLatin1().constData());
1440
1441 if (scan.value.isValid() && scan.state != Invalid) {
1442 if (context != FromString && scan.value < minimum) {
1443 const QLatin1Char space(' ');
1444 if (scan.value >= minimum)
1445 qWarning("QDateTimeParser::parse Internal error 3 (%ls %ls)",
1446 qUtf16Printable(scan.value.toString()), qUtf16Printable(minimum.toString()));
1447
1448 bool done = false;
1449 scan.state = Invalid;
1450 const int sectionNodesCount = sectionNodes.size();
1451 for (int i=0; i<sectionNodesCount && !done; ++i) {
1452 const SectionNode &sn = sectionNodes.at(i);
1453 QString t = sectionText(m_text, i, sn.pos).toLower();
1454 if ((t.size() < sectionMaxSize(i)
1455 && ((fieldInfo(i) & (FixedWidth|Numeric)) != Numeric))
1456 || t.contains(space)) {
1457 switch (sn.type) {
1458 case AmPmSection:
1459 switch (findAmPm(t, i)) {
1460 case AM:
1461 case PM:
1462 scan.state = Acceptable;
1463 done = true;
1464 break;
1465 case Neither:
1466 scan.state = Invalid;
1467 done = true;
1468 break;
1469 case PossibleAM:
1470 case PossiblePM:
1471 case PossibleBoth: {
1472 const QDateTime copy(scan.value.addSecs(12 * 60 * 60));
1473 if (copy >= minimum && copy <= maximum) {
1474 scan.state = Intermediate;
1475 done = true;
1476 }
1477 break; }
1478 }
1479 Q_FALLTHROUGH();
1480 case MonthSection:
1481 if (sn.count >= 3) {
1482 const QDate when = scan.value.date();
1483 const int finalMonth = when.month(calendar);
1484 int tmp = finalMonth;
1485 // I know the first possible month makes the date too early
1486 while ((tmp = findMonth(t, tmp + 1, i, when.year(calendar))) != -1) {
1487 const QDateTime copy(scan.value.addMonths(tmp - finalMonth));
1488 if (copy >= minimum && copy <= maximum)
1489 break; // break out of while
1490 }
1491 if (tmp != -1) {
1492 scan.state = Intermediate;
1493 done = true;
1494 }
1495 break;
1496 }
1497 Q_FALLTHROUGH();
1498 default: {
1499 int toMin;
1500 int toMax;
1501
1502 if (sn.type & TimeSectionMask) {
1503 if (scan.value.daysTo(minimum) != 0)
1504 break;
1505
1506 const QTime time = scan.value.time();
1507 toMin = time.msecsTo(minimum.time());
1508 if (scan.value.daysTo(maximum) > 0)
1509 toMax = -1; // can't get to max
1510 else
1511 toMax = time.msecsTo(maximum.time());
1512 } else {
1513 toMin = scan.value.daysTo(minimum);
1514 toMax = scan.value.daysTo(maximum);
1515 }
1516 const int maxChange = sn.maxChange();
1517 if (toMin > maxChange) {
1518 QDTPDEBUG << "invalid because toMin > maxChange" << toMin
1519 << maxChange << t << scan.value << minimum;
1520 scan.state = Invalid;
1521 done = true;
1522 break;
1523 } else if (toMax > maxChange) {
1524 toMax = -1; // can't get to max
1525 }
1526
1527 const int min = getDigit(minimum, i);
1528 if (min == -1) {
1529 qWarning("QDateTimeParser::parse Internal error 4 (%ls)",
1530 qUtf16Printable(sn.name()));
1531 scan.state = Invalid;
1532 done = true;
1533 break;
1534 }
1535
1536 int max = toMax != -1 ? getDigit(maximum, i) : absoluteMax(i, scan.value);
1537 int pos = position + scan.padded - sn.pos;
1538 if (pos < 0 || pos >= t.size())
1539 pos = -1;
1540 if (!potentialValue(t.simplified(), min, max, i, scan.value, pos)) {
1541 QDTPDEBUG << "invalid because potentialValue(" << t.simplified() << min << max
1542 << sn.name() << "returned" << toMax << toMin << pos;
1543 scan.state = Invalid;
1544 done = true;
1545 break;
1546 }
1547 scan.state = Intermediate;
1548 done = true;
1549 break; }
1550 }
1551 }
1552 }
1553 } else {
1554 if (context == FromString) {
1555 // optimization
1556 Q_ASSERT(maximum.date().toJulianDay() == 5373484);
1557 if (scan.value.date().toJulianDay() > 5373484)
1558 scan.state = Invalid;
1559 } else if (scan.value > maximum) {
1560 scan.state = Invalid;
1561 }
1562
1563 QDTPDEBUG << "not checking intermediate because scanned value is" << scan.value << minimum << maximum;
1564 }
1565 }
1566
1567 /*
1568 We might have ended up with an invalid datetime: the non-existent hour
1569 during dst changes, for instance.
1570 */
1571 if (!scan.value.isValid() && scan.state == Acceptable)
1572 scan.state = Intermediate;
1573
1574 return scan;
1575}
1576
1577/*
1578 \internal
1579 \brief Returns the index in \a entries with the best prefix match to \a text
1580
1581 Scans \a entries looking for an entry overlapping \a text as much as possible
1582 (an exact match beats any prefix match; a match of the full entry as prefix of
1583 text beats any entry but one matching a longer prefix; otherwise, the match of
1584 longest prefix wins, earlier entries beating later on a draw). Records the
1585 length of overlap in *used (if \a used is non-NULL) and the first entry that
1586 overlapped this much in *usedText (if \a usedText is non-NULL).
1587 */
1588static int findTextEntry(const QString &text, const ShortVector<QString> &entries, QString *usedText, int *used)
1589{
1590 if (text.isEmpty())
1591 return -1;
1592
1593 int bestMatch = -1;
1594 int bestCount = 0;
1595 for (int n = 0; n < entries.size(); ++n)
1596 {
1597 const QString &name = entries.at(n);
1598
1599 const int limit = qMin(text.size(), name.size());
1600 int i = 0;
1601 while (i < limit && text.at(i) == name.at(i).toLower())
1602 ++i;
1603 // Full match beats an equal prefix match:
1604 if (i > bestCount || (i == bestCount && i == name.size())) {
1605 bestCount = i;
1606 bestMatch = n;
1607 if (i == name.size() && i == text.size())
1608 break; // Exact match, name == text, wins.
1609 }
1610 }
1611 if (usedText && bestMatch != -1)
1612 *usedText = entries.at(bestMatch);
1613 if (used)
1614 *used = bestCount;
1615
1616 return bestMatch;
1617}
1618
1625int QDateTimeParser::findMonth(const QString &str1, int startMonth, int sectionIndex,
1626 int year, QString *usedMonth, int *used) const
1627{
1628 const SectionNode &sn = sectionNode(sectionIndex);
1629 if (sn.type != MonthSection) {
1630 qWarning("QDateTimeParser::findMonth Internal error");
1631 return -1;
1632 }
1633
1635 QLocale l = locale();
1636 ShortVector<QString> monthNames;
1637 monthNames.reserve(13 - startMonth);
1638 for (int month = startMonth; month <= 12; ++month)
1639 monthNames.append(calendar.monthName(l, month, year, type));
1640
1641 const int index = findTextEntry(str1, monthNames, usedMonth, used);
1642 return index < 0 ? index : index + startMonth;
1643}
1644
1645int QDateTimeParser::findDay(const QString &str1, int startDay, int sectionIndex, QString *usedDay, int *used) const
1646{
1647 const SectionNode &sn = sectionNode(sectionIndex);
1648 if (!(sn.type & DaySectionMask)) {
1649 qWarning("QDateTimeParser::findDay Internal error");
1650 return -1;
1651 }
1652
1654 QLocale l = locale();
1655 ShortVector<QString> daysOfWeek;
1656 daysOfWeek.reserve(8 - startDay);
1657 for (int day = startDay; day <= 7; ++day)
1658 daysOfWeek.append(l.dayName(day, type));
1659
1660 const int index = findTextEntry(str1, daysOfWeek, usedDay, used);
1661 return index < 0 ? index : index + startDay;
1662}
1663
1672QDateTimeParser::ParsedSection QDateTimeParser::findUtcOffset(QStringView str, int mode) const
1673{
1674 Q_ASSERT(mode > 0 && mode < 4);
1675 const bool startsWithUtc = str.startsWith("UTC"_L1);
1676 // Deal with UTC prefix if present:
1677 if (startsWithUtc) {
1678 if (mode != 1)
1679 return ParsedSection();
1680 str = str.sliced(3);
1681 if (str.isEmpty())
1682 return ParsedSection(Acceptable, 0, 3);
1683 }
1684
1685 const bool negativeSign = str.startsWith(u'-');
1686 // Must start with a sign:
1687 if (!negativeSign && !str.startsWith(u'+'))
1688 return ParsedSection();
1689 str = str.sliced(1); // drop sign
1690
1691 const int colonPosition = str.indexOf(u':');
1692 // Colon that belongs to offset is at most at position 2 (hh:mm)
1693 bool hasColon = (colonPosition >= 0 && colonPosition < 3);
1694
1695 // We deal only with digits at this point (except ':'), so collect them
1696 const int digits = hasColon ? colonPosition + 3 : 4;
1697 int i = 0;
1698 for (const int offsetLength = qMin(qsizetype(digits), str.size()); i < offsetLength; ++i) {
1699 if (i != colonPosition && !str.at(i).isDigit())
1700 break;
1701 }
1702 const int hoursLength = qMin(i, hasColon ? colonPosition : 2);
1703 if (hoursLength < 1)
1704 return ParsedSection();
1705 // Field either ends with hours or also has two digits of minutes
1706 if (i < digits) {
1707 // Only allow single-digit hours with UTC prefix or :mm suffix
1708 if (!startsWithUtc && hoursLength != 2)
1709 return ParsedSection();
1710 i = hoursLength;
1711 hasColon = false;
1712 }
1713 if (mode == (hasColon ? 2 : 3))
1714 return ParsedSection();
1715 str.truncate(i); // The rest of the string is not part of the UTC offset
1716
1717 bool isInt = false;
1718 const int hours = str.first(hoursLength).toInt(&isInt);
1719 if (!isInt)
1720 return ParsedSection();
1721 const QStringView minutesStr = str.mid(hasColon ? colonPosition + 1 : 2, 2);
1722 const int minutes = minutesStr.isEmpty() ? 0 : minutesStr.toInt(&isInt);
1723 if (!isInt)
1724 return ParsedSection();
1725
1726 // Keep in sync with QTimeZone::maxUtcOffset hours (14 at most). Also, user
1727 // could be in the middle of updating the offset (e.g. UTC+14:23) which is
1728 // an intermediate state
1729 const State status = (hours > 14 || minutes >= 60) ? Invalid
1730 : (hours == 14 && minutes > 0) ? Intermediate : Acceptable;
1731
1732 int offset = 3600 * hours + 60 * minutes;
1733 if (negativeSign)
1734 offset = -offset;
1735
1736 // Used: UTC, sign, hours, colon, minutes
1737 const int usedSymbols = (startsWithUtc ? 3 : 0) + 1 + hoursLength + (hasColon ? 1 : 0)
1738 + minutesStr.size();
1739
1740 return ParsedSection(status, offset, usedSymbols);
1741}
1742
1750QDateTimeParser::ParsedSection
1751QDateTimeParser::findTimeZoneName(QStringView str, const QDateTime &when) const
1752{
1753 const int systemLength = startsWithLocalTimeZone(str);
1754#if QT_CONFIG(timezone)
1755 // Collect up plausibly-valid characters; let QTimeZone work out what's
1756 // truly valid.
1757 const auto invalidZoneNameCharacter = [] (const QChar &c) {
1758 const auto cu = c.unicode();
1759 return cu >= 127u || !(memchr("+-./:_", char(cu), 6) || c.isLetterOrNumber());
1760 };
1761 int index = std::distance(str.cbegin(),
1762 std::find_if(str.cbegin(), str.cend(), invalidZoneNameCharacter));
1763
1764 // Limit name fragments (between slashes) to 20 characters.
1765 // (Valid time-zone IDs are allowed up to 14 and Android has quirks up to 17.)
1766 // Limit number of fragments to six; no known zone name has more than four.
1767 int lastSlash = -1;
1768 int count = 0;
1769 Q_ASSERT(index <= str.size());
1770 while (lastSlash < index) {
1771 int slash = str.indexOf(u'/', lastSlash + 1);
1772 if (slash < 0 || slash > index)
1773 slash = index; // i.e. the end of the candidate text
1774 else if (++count > 5)
1775 index = slash; // Truncate
1776 if (slash - lastSlash > 20)
1777 index = lastSlash + 20; // Truncate
1778 // If any of those conditions was met, index <= slash, so this exits the loop:
1779 lastSlash = slash;
1780 }
1781
1782 for (; index > systemLength; --index) { // Find longest match
1784 QTimeZone zone(str.toLatin1());
1785 if (zone.isValid())
1786 return ParsedSection(Acceptable, zone.offsetFromUtc(when), index);
1787 }
1788#endif
1789 if (systemLength > 0) // won't actually use the offset, but need it to be valid
1790 return ParsedSection(Acceptable, when.toLocalTime().offsetFromUtc(), systemLength);
1791 return ParsedSection();
1792}
1793
1806QDateTimeParser::ParsedSection
1807QDateTimeParser::findTimeZone(QStringView str, const QDateTime &when,
1808 int maxVal, int minVal, int mode) const
1809{
1810 Q_ASSERT(mode > 0 && mode <= 4);
1811 // Short-cut Zulu suffix when it's all there is (rather than a prefix match):
1812 if (mode == 1 && str == u'Z')
1813 return ParsedSection(Acceptable, 0, 1);
1814
1815 ParsedSection section;
1816 if (mode != 4)
1817 section = findUtcOffset(str, mode);
1818 if (mode != 2 && mode != 3 && section.used <= 0) // if nothing used, try time zone parsing
1819 section = findTimeZoneName(str, when);
1820 // It can be a well formed time zone specifier, but with value out of range
1821 if (section.state == Acceptable && (section.value < minVal || section.value > maxVal))
1822 section.state = Intermediate;
1823 if (section.used > 0)
1824 return section;
1825
1826 if (mode == 1) {
1827 // Check if string is UTC or alias to UTC, after all other options
1828 if (str.startsWith("UTC"_L1))
1829 return ParsedSection(Acceptable, 0, 3);
1830 if (str.startsWith(u'Z'))
1831 return ParsedSection(Acceptable, 0, 1);
1832 }
1833
1834 return ParsedSection();
1835}
1836
1851QDateTimeParser::AmPmFinder QDateTimeParser::findAmPm(QString &str, int sectionIndex, int *used) const
1852{
1853 const SectionNode &s = sectionNode(sectionIndex);
1854 if (s.type != AmPmSection) {
1855 qWarning("QDateTimeParser::findAmPm Internal error");
1856 return Neither;
1857 }
1858 if (used)
1859 *used = str.size();
1860 if (QStringView(str).trimmed().isEmpty())
1861 return PossibleBoth;
1862
1863 const QLatin1Char space(' ');
1864 int size = sectionMaxSize(sectionIndex);
1865
1866 enum {
1867 amindex = 0,
1868 pmindex = 1
1869 };
1870 QString ampm[2];
1871 ampm[amindex] = getAmPmText(AmText, Case(s.count));
1872 ampm[pmindex] = getAmPmText(PmText, Case(s.count));
1873 for (int i = 0; i < 2; ++i)
1874 ampm[i].truncate(size);
1875
1876 QDTPDEBUG << "findAmPm" << str << ampm[0] << ampm[1];
1877
1878 if (str.startsWith(ampm[amindex], Qt::CaseInsensitive)) {
1879 str = ampm[amindex];
1880 return AM;
1881 } else if (str.startsWith(ampm[pmindex], Qt::CaseInsensitive)) {
1882 str = ampm[pmindex];
1883 return PM;
1884 } else if (context == FromString || (str.count(space) == 0 && str.size() >= size)) {
1885 return Neither;
1886 }
1887 size = qMin(size, str.size());
1888
1889 bool broken[2] = {false, false};
1890 for (int i=0; i<size; ++i) {
1891 const QChar ch = str.at(i);
1892 if (ch != space) {
1893 for (int j=0; j<2; ++j) {
1894 if (!broken[j]) {
1895 int index = ampm[j].indexOf(ch);
1896 QDTPDEBUG << "looking for" << ch
1897 << "in" << ampm[j] << "and got" << index;
1898 if (index == -1) {
1899 if (ch.category() == QChar::Letter_Uppercase) {
1900 index = ampm[j].indexOf(ch.toLower());
1901 QDTPDEBUG << "trying with" << ch.toLower()
1902 << "in" << ampm[j] << "and got" << index;
1903 } else if (ch.category() == QChar::Letter_Lowercase) {
1904 index = ampm[j].indexOf(ch.toUpper());
1905 QDTPDEBUG << "trying with" << ch.toUpper()
1906 << "in" << ampm[j] << "and got" << index;
1907 }
1908 if (index == -1) {
1909 broken[j] = true;
1910 if (broken[amindex] && broken[pmindex]) {
1911 QDTPDEBUG << str << "didn't make it";
1912 return Neither;
1913 }
1914 continue;
1915 } else {
1916 str[i] = ampm[j].at(index); // fix case
1917 }
1918 }
1919 ampm[j].remove(index, 1);
1920 }
1921 }
1922 }
1923 }
1924 if (!broken[pmindex] && !broken[amindex])
1925 return PossibleBoth;
1926 return (!broken[amindex] ? PossibleAM : PossiblePM);
1927}
1928
1935{
1936 switch (type) {
1937 // Time. unit is msec
1938 case MSecSection: return 999;
1939 case SecondSection: return 59 * 1000;
1940 case MinuteSection: return 59 * 60 * 1000;
1941 case Hour24Section: case Hour12Section: return 59 * 60 * 60 * 1000;
1942
1943 // Date. unit is day
1945 case DayOfWeekSectionLong: return 7;
1946 case DaySection: return 30;
1947 case MonthSection: return 365 - 31;
1948 case YearSection: return 9999 * 365;
1949 case YearSection2Digits: return 100 * 365;
1950 default:
1951 qWarning("QDateTimeParser::maxChange() Internal error (%ls)",
1953 }
1954
1955 return -1;
1956}
1957
1958QDateTimeParser::FieldInfo QDateTimeParser::fieldInfo(int index) const
1959{
1960 FieldInfo ret;
1961 const SectionNode &sn = sectionNode(index);
1962 switch (sn.type) {
1963 case MSecSection:
1964 ret |= Fraction;
1965 Q_FALLTHROUGH();
1966 case SecondSection:
1967 case MinuteSection:
1968 case Hour24Section:
1969 case Hour12Section:
1970 case YearSection2Digits:
1971 ret |= AllowPartial;
1972 Q_FALLTHROUGH();
1973 case YearSection:
1974 ret |= Numeric;
1975 if (sn.count != 1)
1976 ret |= FixedWidth;
1977 break;
1978 case MonthSection:
1979 case DaySection:
1980 switch (sn.count) {
1981 case 2:
1982 ret |= FixedWidth;
1983 Q_FALLTHROUGH();
1984 case 1:
1986 break;
1987 }
1988 break;
1991 if (sn.count == 3)
1992 ret |= FixedWidth;
1993 break;
1994 case AmPmSection:
1995 // Some locales have different length AM and PM texts.
1996 if (getAmPmText(AmText, Case(sn.count)).size()
1997 == getAmPmText(PmText, Case(sn.count)).size()) {
1998 // Only relevant to DateTimeEdit's fixups in parse().
1999 ret |= FixedWidth;
2000 }
2001 break;
2002 case TimeZoneSection:
2003 break;
2004 default:
2005 qWarning("QDateTimeParser::fieldInfo Internal error 2 (%d %ls %d)",
2006 index, qUtf16Printable(sn.name()), sn.count);
2007 break;
2008 }
2009 return ret;
2010}
2011
2013{
2014 QChar fillChar;
2015 switch (type) {
2016 case AmPmSection: return count == 1 ? "ap"_L1 : count == 2 ? "AP"_L1 : "Ap"_L1;
2017 case MSecSection: fillChar = u'z'; break;
2018 case SecondSection: fillChar = u's'; break;
2019 case MinuteSection: fillChar = u'm'; break;
2020 case Hour24Section: fillChar = u'H'; break;
2021 case Hour12Section: fillChar = u'h'; break;
2024 case DaySection: fillChar = u'd'; break;
2025 case MonthSection: fillChar = u'M'; break;
2026 case YearSection2Digits:
2027 case YearSection: fillChar = u'y'; break;
2028 default:
2029 qWarning("QDateTimeParser::sectionFormat Internal error (%ls)",
2031 return QString();
2032 }
2033 if (fillChar.isNull()) {
2034 qWarning("QDateTimeParser::sectionFormat Internal error 2");
2035 return QString();
2036 }
2037 return QString(count, fillChar);
2038}
2039
2040
2048bool QDateTimeParser::potentialValue(QStringView str, int min, int max, int index,
2049 const QDateTime &currentValue, int insert) const
2050{
2051 if (str.isEmpty())
2052 return true;
2053
2054 const int size = sectionMaxSize(index);
2055 int val = (int)locale().toUInt(str);
2056 const SectionNode &sn = sectionNode(index);
2057 if (sn.type == YearSection2Digits) {
2058 const int year = currentValue.date().year(calendar);
2059 val += year - (year % 100);
2060 }
2061 if (val >= min && val <= max && str.size() == size)
2062 return true;
2063 if (val > max || (str.size() == size && val < min))
2064 return false;
2065
2066 const int len = size - str.size();
2067 for (int i=0; i<len; ++i) {
2068 for (int j=0; j<10; ++j) {
2069 if (potentialValue(str + QLatin1Char('0' + j), min, max, index, currentValue, insert)) {
2070 return true;
2071 } else if (insert >= 0) {
2072 const QString tmp = str.left(insert) + QLatin1Char('0' + j) + str.mid(insert);
2073 if (potentialValue(tmp, min, max, index, currentValue, insert))
2074 return true;
2075 }
2076 }
2077 }
2078
2079 return false;
2080}
2081
2086{
2087 Q_ASSERT(text.size() < sectionMaxSize(index));
2088 const SectionNode &node = sectionNode(index);
2089 int min = absoluteMin(index);
2090 int max = absoluteMax(index, current);
2091 // Time-zone field is only numeric if given as offset from UTC:
2092 if (node.type != TimeZoneSection || current.timeSpec() == Qt::OffsetFromUTC) {
2093 const QDateTime maximum = getMaximum();
2094 const QDateTime minimum = getMinimum();
2095 Q_ASSERT(current >= minimum && current <= maximum);
2096
2097 QDateTime tmp = current;
2098 if (!setDigit(tmp, index, min) || tmp < minimum)
2099 min = getDigit(minimum, index);
2100
2101 if (!setDigit(tmp, index, max) || tmp > maximum)
2102 max = getDigit(maximum, index);
2103 }
2104 int pos = cursorPosition() - node.pos;
2105 if (pos < 0 || pos >= text.size())
2106 pos = -1;
2107
2108 /*
2109 If the value potentially can become another valid entry we don't want to
2110 skip to the next. E.g. In a M field (month without leading 0) if you type
2111 1 we don't want to autoskip (there might be [012] following) but if you
2112 type 3 we do.
2113 */
2114 return !potentialValue(text, min, max, index, current, pos);
2115}
2116
2123{
2124 switch (s) {
2125 case QDateTimeParser::AmPmSection: return "AmPmSection"_L1;
2126 case QDateTimeParser::DaySection: return "DaySection"_L1;
2127 case QDateTimeParser::DayOfWeekSectionShort: return "DayOfWeekSectionShort"_L1;
2128 case QDateTimeParser::DayOfWeekSectionLong: return "DayOfWeekSectionLong"_L1;
2129 case QDateTimeParser::Hour24Section: return "Hour24Section"_L1;
2130 case QDateTimeParser::Hour12Section: return "Hour12Section"_L1;
2131 case QDateTimeParser::MSecSection: return "MSecSection"_L1;
2132 case QDateTimeParser::MinuteSection: return "MinuteSection"_L1;
2133 case QDateTimeParser::MonthSection: return "MonthSection"_L1;
2134 case QDateTimeParser::SecondSection: return "SecondSection"_L1;
2135 case QDateTimeParser::TimeZoneSection: return "TimeZoneSection"_L1;
2136 case QDateTimeParser::YearSection: return "YearSection"_L1;
2137 case QDateTimeParser::YearSection2Digits: return "YearSection2Digits"_L1;
2138 case QDateTimeParser::NoSection: return "NoSection"_L1;
2139 case QDateTimeParser::FirstSection: return "FirstSection"_L1;
2140 case QDateTimeParser::LastSection: return "LastSection"_L1;
2141 default: return "Unknown section "_L1 + QString::number(int(s));
2142 }
2143}
2144
2151{
2152 switch (s) {
2153 case Invalid: return "Invalid"_L1;
2154 case Intermediate: return "Intermediate"_L1;
2155 case Acceptable: return "Acceptable"_L1;
2156 default: return "Unknown state "_L1 + QString::number(s);
2157 }
2158}
2159
2160// Only called when we want only one of date or time; use UTC to avoid bogus DST issues.
2162{
2163 QDateTime val(QDate(1900, 1, 1).startOfDay(QTimeZone::UTC));
2164 const StateNode tmp = parse(t, -1, val, false);
2165 if (tmp.state != Acceptable || tmp.conflicts)
2166 return false;
2167
2168 if (time) {
2169 Q_ASSERT(!date);
2170 const QTime t = tmp.value.time();
2171 if (!t.isValid())
2172 return false;
2173 *time = t;
2174 }
2175
2176 if (date) {
2177 Q_ASSERT(!time);
2178 const QDate d = tmp.value.date();
2179 if (!d.isValid())
2180 return false;
2181 *date = d;
2182 }
2183 return true;
2184}
2185
2186// Only called when we want both date and time; default to local time.
2187bool QDateTimeParser::fromString(const QString &t, QDateTime *datetime) const
2188{
2189 static const QDateTime defaultLocalTime = QDate(1900, 1, 1).startOfDay();
2190 const StateNode tmp = parse(t, -1, defaultLocalTime, false);
2191 if (datetime)
2192 *datetime = tmp.value;
2193 return tmp.state == Acceptable && !tmp.conflicts && tmp.value.isValid();
2194}
2195
2197{
2198 // NB: QDateTimeParser always uses Qt::LocalTime time spec by default. If
2199 // any subclass needs a changing time spec, it must override this
2200 // method. At the time of writing, this is done by QDateTimeEditPrivate.
2201
2202 // Cache the only case
2203 static const QDateTime localTimeMin(QDATETIMEEDIT_DATE_MIN.startOfDay());
2204 return localTimeMin;
2205}
2206
2208{
2209 // NB: QDateTimeParser always uses Qt::LocalTime time spec by default. If
2210 // any subclass needs a changing time spec, it must override this
2211 // method. At the time of writing, this is done by QDateTimeEditPrivate.
2212
2213 // Cache the only case
2214 static const QDateTime localTimeMax(QDATETIMEEDIT_DATE_MAX.endOfDay());
2215 return localTimeMax;
2216}
2217
2218QString QDateTimeParser::getAmPmText(AmPm ap, Case cs) const
2219{
2220 const QLocale loc = locale();
2221 QString raw = ap == AmText ? loc.amText() : loc.pmText();
2222 switch (cs)
2223 {
2224 case UpperCase: return raw.toUpper();
2225 case LowerCase: return raw.toLower();
2226 case NativeCase: return raw;
2227 }
2228 Q_UNREACHABLE_RETURN(raw);
2229}
2230
2231/*
2232 \internal
2233
2234 I give arg2 preference because arg1 is always a QDateTime.
2235*/
2236
2238{
2239 return (s1.type == s2.type) && (s1.pos == s2.pos) && (s1.count == s2.count);
2240}
2241
2247{
2248 calendar = cal;
2249}
2250
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:122
The QCalendar class describes calendar systems.
Definition qcalendar.h:53
QDate dateFromParts(int year, int month, int day) const
int minimumDaysInMonth() const
Returns the number of days in the shortest month in the calendar, in any year.
bool isDateValid(int year, int month, int day) const
Returns true precisely if the given year, month, and day specify a valid date in this calendar.
QString monthName(const QLocale &locale, int month, int year=Unspecified, QLocale::FormatType format=QLocale::LongFormat) const
Returns a suitably localised name for a month.
@ Unspecified
Definition qcalendar.h:57
YearMonthDay partsFromDate(QDate date) const
Converts a QDate to a year, month, and day of the month.
int maximumMonthsInYear() const
Returns the largest number of months that any year may contain.
int maximumDaysInMonth() const
Returns the number of days in the longest month in the calendar, in any year.
int dayOfWeek(QDate date) const
Returns the day of the week number for the given date.
int daysInMonth(int month, int year=Unspecified) const
Returns the number of days in the given month of the given year.
\inmodule QtCore
Definition qchar.h:48
constexpr bool isDigit() const noexcept
Returns true if the character is a decimal digit (Number_DecimalDigit); otherwise returns false.
Definition qchar.h:473
QChar toLower() const noexcept
Returns the lowercase equivalent if the character is uppercase or titlecase; otherwise returns the ch...
Definition qchar.h:448
constexpr char toLatin1() const noexcept
Returns the Latin-1 character equivalent to the QChar, or 0.
Definition qchar.h:457
@ Letter_Lowercase
Definition qchar.h:124
@ Letter_Uppercase
Definition qchar.h:123
constexpr bool isNull() const noexcept
Returns true if the character is the Unicode character 0x0000 ('\0'); otherwise returns false.
Definition qchar.h:463
virtual QLocale locale() const
int absoluteMin(int index) const
virtual QString displayText() const
int sectionSize(int index) const
int absoluteMax(int index, const QDateTime &value=QDateTime()) const
QString stateName(State s) const
virtual ~QDateTimeParser()
FieldInfo fieldInfo(int index) const
virtual QDateTime getMaximum() const
bool fromString(const QString &text, QDate *date, QTime *time) const
int getDigit(const QDateTime &dt, int index) const
StateNode parse(const QString &input, int position, const QDateTime &defaultValue, bool fixup) const
void setCalendar(const QCalendar &calendar)
Sets cal as the calendar to use.
virtual int cursorPosition() const
virtual QDateTime getMinimum() const
bool skipToNextSection(int section, const QDateTime &current, QStringView sectionText) const
QList< SectionNode > sectionNodes
Section sectionType(int index) const
const SectionNode & sectionNode(int index) const
bool setDigit(QDateTime &t, int index, int newval) const
QMetaType::Type parserType
int sectionPos(int index) const
bool parseFormat(QStringView format)
\inmodule QtCore\reentrant
Definition qdatetime.h:257
int offsetFromUtc() const
qint64 toMSecsSinceEpoch() const
static QDateTime fromMSecsSinceEpoch(qint64 msecs, const QTimeZone &timeZone)
QDateTime addMonths(int months) const
Returns a QDateTime object containing a datetime nmonths months later than the datetime of this objec...
QTime time() const
Returns the time part of the datetime.
QDateTime toLocalTime() const
Returns a copy of this datetime converted to local time.
QDateTime addSecs(qint64 secs) const
Returns a QDateTime object containing a datetime s seconds later than the datetime of this object (or...
Qt::TimeSpec timeSpec() const
Returns the time specification of the datetime.
bool isValid() const
Returns true if this datetime represents a definite moment, otherwise false.
qint64 daysTo(const QDateTime &) const
Returns the number of days from this datetime to the other datetime.
QTimeZone timeRepresentation() const
QDate date() const
Returns the date part of the datetime.
\inmodule QtCore \reentrant
Definition qdatetime.h:27
constexpr bool isValid() const
Returns true if this date is valid; otherwise returns false.
Definition qdatetime.h:86
constexpr qint64 toJulianDay() const
Converts the date to a Julian day.
Definition qdatetime.h:161
int month() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
int day() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
int year() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
void getDate(int *year, int *month, int *day) const
int daysInMonth() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
QDateTime startOfDay(const QTimeZone &zone) const
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
bool isEmpty() const noexcept
Definition qlist.h:390
void append(parameter_type t)
Definition qlist.h:441
uint toUInt(const QString &s, bool *ok=nullptr) const
Returns the unsigned int represented by the localized string s.
Definition qlocale.h:938
QString dayName(int, FormatType format=LongFormat) const
Definition qlocale.cpp:2867
@ LongFormat
Definition qlocale.h:865
@ ShortFormat
Definition qlocale.h:865
QString amText() const
Definition qlocale.cpp:3299
QString pmText() const
Definition qlocale.cpp:3319
QString toString(qlonglong i) const
Returns a localized string representation of i.
Definition qlocale.cpp:1962
\inmodule QtCore
\inmodule QtCore
Definition qstringview.h:76
bool startsWith(QStringView s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const noexcept
constexpr void chop(qsizetype n) noexcept
Truncates this string view by length characters.
constexpr bool isEmpty() const noexcept
Returns whether this string view is empty - that is, whether {size() == 0}.
constexpr qsizetype size() const noexcept
Returns the size of this string view, in UTF-16 code units (that is, surrogate pairs count as two for...
constexpr QStringView first(qsizetype n) const noexcept
constexpr QStringView left(qsizetype n) const noexcept
QByteArray toLatin1() const
Returns a Latin-1 representation of the string as a QByteArray.
QString toString() const
Returns a deep copy of this string view's data as a QString.
Definition qstring.h:1014
int toInt(bool *ok=nullptr, int base=10) const
Returns the string view converted to an int using base base, which is 10 by default and must be betwe...
Definition qstring.h:1025
constexpr QChar at(qsizetype n) const noexcept
Returns the character at position n in this string view.
constexpr QStringView sliced(qsizetype pos) const noexcept
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
QByteArray toLatin1() const &
Definition qstring.h:559
int toInt(bool *ok=nullptr, int base=10) const
Returns the string converted to an int using base base, which is 10 by default and must be between 2 ...
Definition qstring.h:660
bool startsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string starts with s; otherwise returns false.
Definition qstring.cpp:5299
QString & replace(qsizetype i, qsizetype len, QChar after)
Definition qstring.cpp:3794
void truncate(qsizetype pos)
Truncates the string at the given position index.
Definition qstring.cpp:6159
const QChar * constData() const
Returns a pointer to the data stored in the QString.
Definition qstring.h:1101
const_iterator cbegin() const
Definition qstring.h:1201
qsizetype size() const
Returns the number of characters in this string.
Definition qstring.h:182
const_iterator cend() const
Definition qstring.h:1209
QString arg(qlonglong a, int fieldwidth=0, int base=10, QChar fillChar=u' ') const
Definition qstring.cpp:8606
QString mid(qsizetype position, qsizetype n=-1) const
Returns a string that contains n characters of this string, starting at the specified position index.
Definition qstring.cpp:5204
QString first(qsizetype n) const
Definition qstring.h:337
const QChar at(qsizetype i) const
Returns the character at the given index position in the string.
Definition qstring.h:1079
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
QString & insert(qsizetype i, QChar c)
Definition qstring.cpp:3110
QString toLower() const &
Definition qstring.h:368
qsizetype count(QChar c, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4732
static QString number(int, int base=10)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:7822
QString sliced(qsizetype pos) const
Definition qstring.h:341
QString left(qsizetype n) const
Returns a substring that contains the n leftmost characters of the string.
Definition qstring.cpp:5161
static QString static QString qsizetype indexOf(QChar c, qsizetype from=0, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4420
QString toUpper() const &
Definition qstring.h:372
\inmodule QtCore
Definition qtimezone.h:25
static constexpr int MaxUtcOffsetSecs
Definition qtimezone.h:89
bool isValid() const
Returns true if this time zone is valid.
static QTimeZone fromSecondsAheadOfUtc(int offset)
Definition qtimezone.h:129
static constexpr int MinUtcOffsetSecs
Definition qtimezone.h:86
\inmodule QtCore \reentrant
Definition qdatetime.h:189
int hour() const
Returns the hour part (0 to 23) of the time.
int minute() const
Returns the minute part (0 to 59) of the time.
bool isValid() const
Returns true if the time is valid; otherwise returns false.
int msecsTo(QTime t) const
Returns the number of milliseconds from this time to t.
int msec() const
Returns the millisecond part (0 to 999) of the time.
int second() const
Returns the second part (0 to 59) of the time.
constexpr size_type size() const noexcept
const T & at(qsizetype idx) const
void append(const T &t)
void reserve(qsizetype sz)
QString str
[2]
QString text
QDate date
[1]
cache insert(employee->id(), employee)
QSet< QString >::iterator it
else opt state
[0]
struct wl_display * display
Definition linuxdmabuf.h:41
Combined button and popup list for selecting options.
constexpr const T & min(const T &a, const T &b)
Definition qnumeric.h:366
Q_CORE_EXPORT Q_DECL_PURE_FUNCTION QByteArrayView trimmed(QByteArrayView s) noexcept
@ OffsetFromUTC
@ CaseInsensitive
static jboolean copy(JNIEnv *, jobject)
#define Q_FALLTHROUGH()
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 bool preferDayOfWeek(const QList< QDateTimeParser::SectionNode > &nodes)
static int startsWithLocalTimeZone(QStringView name)
#define QDTPDEBUGN
static QDate actualDate(QDateTimeParser::Sections known, const QCalendar &calendar, int year, int year2digits, int month, int day, int dayofweek)
static qsizetype digitCount(QStringView str)
#define QDTPDEBUG
static QTime actualTime(QDateTimeParser::Sections known, int hour, int hour12, int ampm, int minute, int second, int msec)
static int findTextEntry(const QString &text, const ShortVector< QString > &entries, QString *usedText, int *used)
#define QDATETIMEEDIT_DATE_MIN
#define QDATETIMEEDIT_DATE_MAX
Q_CORE_EXPORT bool operator==(const QDateTimeParser::SectionNode &s1, const QDateTimeParser::SectionNode &s2)
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
qsizetype qt_repeatCount(QStringView s)
Definition qlocale.cpp:673
static constexpr int digits(int number)
#define qWarning
Definition qlogging.h:162
return ret
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLsizei const GLfloat * v
[13]
GLenum mode
const GLfloat * m
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint index
[2]
GLuint GLuint end
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat s1
GLenum GLenum GLsizei count
GLenum type
GLenum GLuint GLintptr offset
GLuint name
GLint first
GLfloat n
GLint GLsizei GLsizei GLenum format
GLint y
const GLubyte * c
GLuint GLfloat * val
GLenum GLsizei len
GLint limit
GLsizei maxCount
Definition qopenglext.h:677
GLdouble GLdouble t
Definition qopenglext.h:243
GLuint64EXT * result
[6]
GLdouble s
[6]
Definition qopenglext.h:235
GLuint num
GLenum GLenum GLenum input
static void add(QPainterPath &path, const QWingedEdge &list, int edge, QPathEdge::Traversal traversal)
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_ASSERT_X(cond, x, msg)
Definition qrandom.cpp:48
QtPrivate::QRegularExpressionMatchIteratorRangeBasedForIterator begin(const QRegularExpressionMatchIterator &iterator)
static constexpr QChar sep
#define qUtf16Printable(string)
Definition qstring.h:1403
#define zero
#define s2
QString qTzName(int dstIndex)
ptrdiff_t qsizetype
Definition qtypes.h:70
long long qint64
Definition qtypes.h:55
static double WeekDay(double t)
static QString quote(const QString &str)
QList< int > list
[14]
QFileInfo fi("c:/temp/foo")
[newstuff]
QCalendarWidget * calendar
[0]
static QString name(Section s)
\inmodule QtCore \reentrant
Definition qchar.h:17