Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
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
93static bool preferDayOfWeek(const QList<QDateTimeParser::SectionNode> &nodes)
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 // sectionMaxSize will prevent people from typing in a larger number in
241 // count == 2 sections; stepBy() will work on real years anyway.
242 case YearSection:
243 return 9999;
244 case MonthSection:
246 case DaySection:
247 return cur.isValid() ? cur.date().daysInMonth(calendar) : calendar.maximumDaysInMonth();
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 break;
713
714 case NoSectionIndex:
716 case LastSectionIndex:
718 // these cases can't happen
719 break;
720 }
721 return -1;
722}
723
724
725int QDateTimeParser::sectionMaxSize(int index) const
726{
727 const SectionNode &sn = sectionNode(index);
728 return sectionMaxSize(sn.type, sn.count);
729}
730
731// Separator matching
732//
733// QTBUG-114909: user may be oblivious to difference between visibly
734// indistinguishable spacing characters. For now we only treat horizontal
735// spacing characters, excluding tab, as equivalent.
736
738{
739 const auto isSimpleSpace = [](char32_t ch) {
740 // Distinguish tab, CR and the vertical spaces from the rest:
741 return ch == u' ' || (ch > 127 && QChar::isSpace(ch));
742 };
743 // -1 if not a match, else length of prefix of text that does match.
744 // First check for exact match
745 if (!text.startsWith(separator)) {
746 // Failing that, check for space-identifying match:
747 QStringIterator given(text), sep(separator);
748 while (sep.hasNext()) {
749 if (!given.hasNext())
750 return -1;
751 char32_t s = sep.next(), g = given.next();
752 if (s != g && !(isSimpleSpace(s) && isSimpleSpace(g)))
753 return -1;
754 }
755 // One side may have used a surrogate pair space where the other didn't:
756 return given.index();
757 }
758 return separator.size();
759}
760
769QString QDateTimeParser::sectionText(const QString &text, int sectionIndex, int index) const
770{
771 const SectionNode &sn = sectionNode(sectionIndex);
772 switch (sn.type) {
773 case NoSectionIndex:
775 case LastSectionIndex:
776 return QString();
777 default: break;
778 }
779
780 return text.mid(index, sectionSize(sectionIndex));
781}
782
783QString QDateTimeParser::sectionText(int sectionIndex) const
784{
785 const SectionNode &sn = sectionNode(sectionIndex);
786 return sectionText(displayText(), sectionIndex, sn.pos);
787}
788
789QDateTimeParser::ParsedSection
790QDateTimeParser::parseSection(const QDateTime &currentValue, int sectionIndex, int offset) const
791{
792 ParsedSection result; // initially Invalid
793 const SectionNode &sn = sectionNode(sectionIndex);
794 Q_ASSERT_X(!(sn.type & Internal),
795 "QDateTimeParser::parseSection", "Internal error");
796
797 const int sectionmaxsize = sectionMaxSize(sectionIndex);
798 const bool negate = (sn.type == YearSection && m_text.size() > offset
799 && m_text.at(offset) == u'-');
800 const int negativeYearOffset = negate ? 1 : 0;
801
802 QStringView sectionTextRef =
803 QStringView { m_text }.mid(offset + negativeYearOffset, sectionmaxsize);
804
805 QDTPDEBUG << "sectionValue for" << sn.name()
806 << "with text" << m_text << "and (at" << offset
807 << ") st:" << sectionTextRef;
808
809 switch (sn.type) {
810 case AmPmSection: {
811 QString sectiontext = sectionTextRef.toString();
812 int used;
813 const int ampm = findAmPm(sectiontext, sectionIndex, &used);
814 switch (ampm) {
815 case AM: // sectiontext == AM
816 case PM: // sectiontext == PM
817 result = ParsedSection(Acceptable, ampm, used);
818 break;
819 case PossibleAM: // sectiontext => AM
820 case PossiblePM: // sectiontext => PM
821 result = ParsedSection(Intermediate, ampm - 2, used);
822 break;
823 case PossibleBoth: // sectiontext => AM|PM
824 result = ParsedSection(Intermediate, 0, used);
825 break;
826 case Neither:
827 QDTPDEBUG << "invalid because findAmPm(" << sectiontext << ") returned -1";
828 break;
829 default:
830 QDTPDEBUGN("This should never happen (findAmPm returned %d)", ampm);
831 break;
832 }
833 if (result.state != Invalid)
834 m_text.replace(offset, used, sectiontext.constData(), used);
835 break; }
836 case TimeZoneSection:
837 result = findTimeZone(sectionTextRef, currentValue,
838 absoluteMax(sectionIndex),
839 absoluteMin(sectionIndex), sn.count);
840 break;
841 case MonthSection:
844 if (sn.count >= 3) {
845 QString sectiontext = sectionTextRef.toString();
846 int num = 0, used = 0;
847 if (sn.type == MonthSection) {
848 const QDate minDate = getMinimum().date();
849 const int year = currentValue.date().year(calendar);
850 const int min = (year == minDate.year(calendar)) ? minDate.month(calendar) : 1;
851 num = findMonth(sectiontext.toLower(), min, sectionIndex, year, &sectiontext, &used);
852 } else {
853 num = findDay(sectiontext.toLower(), 1, sectionIndex, &sectiontext, &used);
854 }
855
856 result = ParsedSection(Intermediate, num, used);
857 if (num != -1) {
858 m_text.replace(offset, used, sectiontext.constData(), used);
859 if (used == sectiontext.size())
860 result = ParsedSection(Acceptable, num, used);
861 }
862 break;
863 }
865 // All numeric:
866 case DaySection:
867 case YearSection:
869 case Hour12Section:
870 case Hour24Section:
871 case MinuteSection:
872 case SecondSection:
873 case MSecSection: {
874 const auto checkSeparator = [&result, field=QStringView{m_text}.sliced(offset),
875 negativeYearOffset, sectionIndex, this]() {
876 // No-digit field if next separator is here, otherwise invalid.
877 const auto &sep = separators.at(sectionIndex + 1);
878 if (matchesSeparator(field.sliced(negativeYearOffset), sep) != -1)
879 result = ParsedSection(Intermediate, 0, negativeYearOffset);
880 else if (negativeYearOffset && matchesSeparator(field, sep) != -1)
881 result = ParsedSection(Intermediate, 0, 0);
882 else
883 return false;
884 return true;
885 };
886 int used = negativeYearOffset;
887 // We already sliced off the - sign if it was acceptable.
888 // QLocale::toUInt() would accept a sign, so we must reject it overtly:
889 if (sectionTextRef.startsWith(u'-')
890 || sectionTextRef.startsWith(u'+')) {
891 // However, a sign here may indicate a field with no digits, if it
892 // starts the next separator:
893 checkSeparator();
894 break;
895 }
896 QStringView digitsStr = sectionTextRef.left(digitCount(sectionTextRef));
897
898 if (digitsStr.isEmpty()) {
899 result = ParsedSection(Intermediate, 0, used);
900 } else {
901 const QLocale loc = locale();
902 const int absMax = absoluteMax(sectionIndex);
903 const int absMin = absoluteMin(sectionIndex);
904
905 int lastVal = -1;
906
907 for (; digitsStr.size(); digitsStr.chop(1)) {
908 bool ok = false;
909 int value = int(loc.toUInt(digitsStr, &ok));
910 if (!ok || (negate ? -value < absMin : value > absMax))
911 continue;
912
913 if (sn.type == Hour12Section) {
914 if (value > 12)
915 continue;
916 if (value == 12)
917 value = 0;
918 }
919
920 QDTPDEBUG << digitsStr << value << digitsStr.size();
921 lastVal = value;
922 used += digitsStr.size();
923 break;
924 }
925
926 if (lastVal == -1) {
927 if (!checkSeparator()) {
928 QDTPDEBUG << "invalid because" << sectionTextRef << "can't become a uint"
929 << lastVal;
930 }
931 } else {
932 if (negate)
933 lastVal = -lastVal;
934 const FieldInfo fi = fieldInfo(sectionIndex);
935 const bool unfilled = used - negativeYearOffset < sectionmaxsize;
936 if (unfilled && fi & Fraction) { // typing 2 in a zzz field should be .200, not .002
937 for (int i = used; i < sectionmaxsize; ++i)
938 lastVal *= 10;
939 }
940 // Even those *= 10s can't take last above absMax:
941 Q_ASSERT(negate ? lastVal >= absMin : lastVal <= absMax);
942 if (negate ? lastVal > absMax : lastVal < absMin) {
943 if (unfilled) {
944 result = ParsedSection(Intermediate, lastVal, used);
945 } else if (negate) {
946 QDTPDEBUG << "invalid because" << lastVal << "is greater than absoluteMax"
947 << absMax;
948 } else {
949 QDTPDEBUG << "invalid because" << lastVal << "is less than absoluteMin"
950 << absMin;
951 }
952
953 } else if (unfilled && (fi & (FixedWidth | Numeric)) == (FixedWidth | Numeric)) {
954 if (skipToNextSection(sectionIndex, currentValue, digitsStr)) {
955 const int missingZeroes = sectionmaxsize - digitsStr.size();
956 result = ParsedSection(Acceptable, lastVal, sectionmaxsize, missingZeroes);
957 m_text.insert(offset, QString(missingZeroes, u'0'));
958 ++(const_cast<QDateTimeParser*>(this)->sectionNodes[sectionIndex].zeroesAdded);
959 } else {
960 result = ParsedSection(Intermediate, lastVal, used);
961 }
962 } else {
963 result = ParsedSection(Acceptable, lastVal, used);
964 }
965 }
966 }
967 break; }
968 default:
969 qWarning("QDateTimeParser::parseSection Internal error (%ls %d)",
970 qUtf16Printable(sn.name()), sectionIndex);
971 return result;
972 }
973 Q_ASSERT(result.state != Invalid || result.value == -1);
974
975 return result;
976}
977
986static int weekDayWithinMonth(QCalendar calendar, int year, int month, int day, int weekDay)
987{
988 // TODO: can we adapt this to cope gracefully with intercallary days (day of
989 // week > 7) without making it slower for more widely-used calendars ?
990 const int maxDay = calendar.daysInMonth(month, year); // 0 if no such month
991 day = maxDay > 1 ? qBound(1, day, maxDay) : qMax(1, day);
992 day += dayOfWeekDiff(weekDay, calendar.dayOfWeek(QDate(year, month, day, calendar)));
993 return day <= 0 ? day + 7 : maxDay > 0 && day > maxDay ? day - 7 : day;
994}
995
1000static int yearInCenturyFrom(int y2d, int baseYear)
1001{
1002 Q_ASSERT(0 <= y2d && y2d < 100);
1003 const int year = baseYear - baseYear % 100 + y2d;
1004 return year < baseYear ? year + 100 : year;
1005}
1006
1015static QDate actualDate(QDateTimeParser::Sections known, QCalendar calendar, int baseYear,
1016 int year, int year2digits, int month, int day, int dayofweek)
1017{
1018 QDate actual(year, month, day, calendar);
1019 if (actual.isValid() && year % 100 == year2digits && calendar.dayOfWeek(actual) == dayofweek)
1020 return actual; // The obvious candidate is fine :-)
1021
1022 if (dayofweek < 1 || dayofweek > 7) // Intercallary (or invalid): ignore
1023 known &= ~QDateTimeParser::DayOfWeekSectionMask;
1024
1025 // Assuming year > 0 ...
1026 if (year % 100 != year2digits) {
1028 // Over-ride year, even if specified:
1029 year = yearInCenturyFrom(year2digits, baseYear);
1030 known &= ~QDateTimeParser::YearSection;
1031 } else {
1032 year2digits = year % 100;
1033 }
1034 }
1035 Q_ASSERT(year % 100 == year2digits);
1036
1037 if (month < 1) { // If invalid, clip to nearest valid and ignore in known.
1038 month = 1;
1039 known &= ~QDateTimeParser::MonthSection;
1040 } else if (month > 12) {
1041 month = 12;
1042 known &= ~QDateTimeParser::MonthSection;
1043 }
1044
1045 QDate first(year, month, 1, calendar);
1046 int last = known & QDateTimeParser::MonthSection
1047 ? (known.testAnyFlag(QDateTimeParser::YearSectionMask)
1048 ? calendar.daysInMonth(month, year) : calendar.daysInMonth(month))
1049 : 0;
1050 // We can only fix DOW if we know year as well as month (hence last):
1051 const bool fixDayOfWeek = last && known & QDateTimeParser::YearSection
1053 // If we also know day-of-week, tweak last to the last in the month that matches it:
1054 if (fixDayOfWeek) {
1055 const int diff = (dayofweek - calendar.dayOfWeek(first) - last) % 7;
1056 Q_ASSERT(diff <= 0); // C++11 specifies (-ve) % (+ve) to be <= 0.
1057 last += diff;
1058 }
1059 if (day < 1) {
1060 if (fixDayOfWeek) {
1061 day = 1 + dayofweek - calendar.dayOfWeek(first);
1062 if (day < 1)
1063 day += 7;
1064 } else {
1065 day = 1;
1066 }
1067 known &= ~QDateTimeParser::DaySection;
1068 } else if (day > calendar.maximumDaysInMonth()) {
1069 day = last;
1070 known &= ~QDateTimeParser::DaySection;
1071 } else if (last && day > last && (known & QDateTimeParser::DaySection) == 0) {
1072 day = last;
1073 }
1074
1075 actual = QDate(year, month, day, calendar);
1076 if (!actual.isValid() // We can't do better than we have, in this case
1077 || (known & QDateTimeParser::DaySection
1079 && known & QDateTimeParser::YearSection) // ditto
1080 || calendar.dayOfWeek(actual) == dayofweek // Good enough, use it.
1081 || (known & QDateTimeParser::DayOfWeekSectionMask) == 0) { // No contradiction, use it.
1082 return actual;
1083 }
1084
1085 /*
1086 Now it gets trickier.
1087
1088 We have some inconsistency in our data; we've been told day of week, but
1089 it doesn't fit with our year, month and day. At least one of these is
1090 unknown, though: so we can fix day of week by tweaking it.
1091 */
1092
1093 if ((known & QDateTimeParser::DaySection) == 0) {
1094 // Relatively easy to fix.
1095 day = weekDayWithinMonth(calendar, year, month, day, dayofweek);
1096 actual = QDate(year, month, day, calendar);
1097 return actual;
1098 }
1099
1100 if ((known & QDateTimeParser::MonthSection) == 0) {
1101 /*
1102 Try possible month-offsets, m, preferring small; at least one (present
1103 month doesn't work) and at most 11 (max month, 12, minus min, 1); try
1104 in both directions, ignoring any offset that takes us out of range.
1105 */
1106 for (int m = 1; m < 12; m++) {
1107 if (m < month) {
1108 actual = QDate(year, month - m, day, calendar);
1109 if (calendar.dayOfWeek(actual) == dayofweek)
1110 return actual;
1111 }
1112 if (m + month <= 12) {
1113 actual = QDate(year, month + m, day, calendar);
1114 if (calendar.dayOfWeek(actual) == dayofweek)
1115 return actual;
1116 }
1117 }
1118 // Should only get here in corner cases; e.g. day == 31
1119 actual = QDate(year, month, day, calendar); // Restore from trial values.
1120 }
1121
1122 if ((known & QDateTimeParser::YearSection) == 0) {
1124 actual = calendar.matchCenturyToWeekday({year, month, day}, dayofweek);
1125 if (actual.isValid()) {
1126 Q_ASSERT(calendar.dayOfWeek(actual) == dayofweek);
1127 return actual;
1128 }
1129 } else {
1130 // Offset by 7 is usually enough, but rare cases may need more:
1131 for (int y = 1; y < 12; y++) {
1132 actual = QDate(year - y, month, day, calendar);
1133 if (calendar.dayOfWeek(actual) == dayofweek)
1134 return actual;
1135 actual = QDate(year + y, month, day, calendar);
1136 if (calendar.dayOfWeek(actual) == dayofweek)
1137 return actual;
1138 }
1139 }
1140 actual = QDate(year, month, day, calendar); // Restore from trial values.
1141 }
1142
1143 return actual; // It'll just have to do :-(
1144}
1145
1150static QTime actualTime(QDateTimeParser::Sections known,
1151 int hour, int hour12, int ampm,
1152 int minute, int second, int msec)
1153{
1154 // If we have no conflict, or don't know enough to diagonose one, use this:
1155 QTime actual(hour, minute, second, msec);
1156 if (hour12 < 0 || hour12 > 12) { // ignore bogus value
1157 known &= ~QDateTimeParser::Hour12Section;
1158 hour12 = hour % 12;
1159 }
1160
1161 if (ampm == -1 || (known & QDateTimeParser::AmPmSection) == 0) {
1162 if ((known & QDateTimeParser::Hour12Section) == 0 || hour % 12 == hour12)
1163 return actual;
1164
1165 if ((known & QDateTimeParser::Hour24Section) == 0)
1166 hour = hour12 + (hour > 12 ? 12 : 0);
1167 } else {
1168 Q_ASSERT(ampm == 0 || ampm == 1);
1169 if (hour - hour12 == ampm * 12)
1170 return actual;
1171
1172 if ((known & QDateTimeParser::Hour24Section) == 0
1173 && known & QDateTimeParser::Hour12Section) {
1174 hour = hour12 + ampm * 12;
1175 }
1176 }
1177 actual = QTime(hour, minute, second, msec);
1178 return actual;
1179}
1180
1181/*
1182 \internal
1183*/
1184static int startsWithLocalTimeZone(QStringView name, const QDateTime &when, const QLocale &locale)
1185{
1186 // Pick longest match that we might get.
1187 qsizetype longest = 0;
1188 // On MS-Win, at least when system zone is UTC, the tzname[]s may be empty.
1189 for (int i = 0; i < 2; ++i) {
1190 const QString zone(qTzName(i));
1191 if (zone.size() > longest && name.startsWith(zone))
1192 longest = zone.size();
1193 }
1194 // Mimic each candidate QLocale::toString() could have used, to ensure round-trips work:
1195 const auto consider = [name, &longest](QStringView zone) {
1196 if (name.startsWith(zone)) {
1197 // UTC-based zone's displayName() only includes seconds if non-zero:
1198 if (9 > longest && zone.size() == 6 && zone.startsWith("UTC"_L1)
1199 && name.sliced(6, 3) == ":00"_L1) {
1200 longest = 9;
1201 } else if (zone.size() > longest) {
1202 longest = zone.size();
1203 }
1204 }
1205 };
1206#if QT_CONFIG(timezone)
1207 /* QLocale::toString would skip this if locale == QLocale::system(), but we
1208 might not be using the same system locale as whoever generated the text
1209 we're parsing. So consider it anyway. */
1210 {
1211 const auto localWhen = QDateTime(when.date(), when.time());
1212 consider(localWhen.timeRepresentation().displayName(
1213 localWhen, QTimeZone::ShortName, locale));
1214 }
1215#else
1216 Q_UNUSED(locale);
1217#endif
1218 consider(QDateTime(when.date(), when.time()).timeZoneAbbreviation());
1219 Q_ASSERT(longest <= INT_MAX); // Timezone names are not that long.
1220 return int(longest);
1221}
1222
1227QDateTimeParser::scanString(const QDateTime &defaultValue, bool fixup) const
1228{
1230 bool conflicts = false;
1231 const int sectionNodesCount = sectionNodes.size();
1232 int padding = 0;
1233 int pos = 0;
1234 int year, month, day;
1235 const QDate defaultDate = defaultValue.date();
1236 const QTime defaultTime = defaultValue.time();
1237 defaultDate.getDate(&year, &month, &day);
1238 int year2digits = year % 100;
1239 int hour = defaultTime.hour();
1240 int hour12 = -1;
1241 int minute = defaultTime.minute();
1242 int second = defaultTime.second();
1243 int msec = defaultTime.msec();
1244 int dayofweek = calendar.dayOfWeek(defaultDate);
1245 QTimeZone timeZone = defaultValue.timeRepresentation();
1246
1247 int ampm = -1;
1248 Sections isSet = NoSection;
1249
1250 for (int index = 0; index < sectionNodesCount; ++index) {
1252 const QString &separator = separators.at(index);
1253 int step = matchesSeparator(QStringView{m_text}.sliced(pos), separator);
1254 if (step == -1) {
1255 QDTPDEBUG << "invalid because" << QStringView{m_text}.sliced(pos)
1256 << "does not start with" << separator
1257 << index << pos << currentSectionIndex;
1258 return StateNode();
1259 }
1260 pos += step;
1261 sectionNodes[index].pos = pos;
1262 int *current = nullptr;
1263 int zoneOffset; // Needed to serve as *current when setting zone
1264 const SectionNode sn = sectionNodes.at(index);
1265 const QDateTime usedDateTime = [&] {
1267 year, year2digits, month, day, dayofweek);
1268 const QTime time = actualTime(isSet, hour, hour12, ampm, minute, second, msec);
1269 return QDateTime(date, time, timeZone);
1270 }();
1271 ParsedSection sect = parseSection(usedDateTime, index, pos);
1272
1273 QDTPDEBUG << "sectionValue" << sn.name() << m_text
1274 << "pos" << pos << "used" << sect.used << stateName(sect.state);
1275
1276 padding += sect.zeroes;
1277 if (fixup && sect.state == Intermediate && sect.used < sn.count) {
1278 const FieldInfo fi = fieldInfo(index);
1279 if ((fi & (Numeric|FixedWidth)) == (Numeric|FixedWidth)) {
1280 const QString newText = QString::asprintf("%0*d", sn.count, sect.value);
1281 m_text.replace(pos, sect.used, newText);
1282 sect.used = sn.count;
1283 }
1284 }
1285
1286 state = qMin<State>(state, sect.state);
1287 // QDateTimeEdit can fix Intermediate and zeroes, but input needing that didn't match format:
1288 if (state == Invalid || (context == FromString && (state == Intermediate || sect.zeroes)))
1289 return StateNode();
1290
1291 switch (sn.type) {
1292 case TimeZoneSection:
1293 current = &zoneOffset;
1294 if (sect.used > 0) {
1295 // Synchronize with what findTimeZone() found:
1296 QStringView zoneName = QStringView{m_text}.sliced(pos, sect.used);
1297 Q_ASSERT(!zoneName.isEmpty()); // sect.used > 0
1298
1299 const QStringView offsetStr
1300 = zoneName.startsWith("UTC"_L1) ? zoneName.sliced(3) : zoneName;
1301 const bool isUtcOffset = offsetStr.startsWith(u'+') || offsetStr.startsWith(u'-');
1302 const bool isUtc = zoneName == "Z"_L1 || zoneName == "UTC"_L1;
1303
1304 if (isUtc || isUtcOffset) {
1305 timeZone = QTimeZone::fromSecondsAheadOfUtc(sect.value);
1306#if QT_CONFIG(timezone)
1307 } else if (startsWithLocalTimeZone(zoneName, usedDateTime, locale()) != sect.used) {
1308 QTimeZone namedZone = QTimeZone(zoneName.toLatin1());
1309 Q_ASSERT(namedZone.isValid());
1310 timeZone = namedZone;
1311#endif
1312 } else {
1313 timeZone = QTimeZone::LocalTime;
1314 }
1315 }
1316 break;
1317 case Hour24Section: current = &hour; break;
1318 case Hour12Section: current = &hour12; break;
1319 case MinuteSection: current = &minute; break;
1320 case SecondSection: current = &second; break;
1321 case MSecSection: current = &msec; break;
1322 case YearSection: current = &year; break;
1323 case YearSection2Digits: current = &year2digits; break;
1324 case MonthSection: current = &month; break;
1326 case DayOfWeekSectionLong: current = &dayofweek; break;
1327 case DaySection: current = &day; sect.value = qMax<int>(1, sect.value); break;
1328 case AmPmSection: current = &ampm; break;
1329 default:
1330 qWarning("QDateTimeParser::parse Internal error (%ls)",
1331 qUtf16Printable(sn.name()));
1332 return StateNode();
1333 }
1334 Q_ASSERT(current);
1335 Q_ASSERT(sect.state != Invalid);
1336
1337 if (sect.used > 0)
1338 pos += sect.used;
1339 QDTPDEBUG << index << sn.name() << "is set to"
1340 << pos << "state is" << stateName(state);
1341
1342 if (isSet & sn.type && *current != sect.value) {
1343 QDTPDEBUG << "CONFLICT " << sn.name() << *current << sect.value;
1344 conflicts = true;
1346 continue;
1347 }
1348 *current = sect.value;
1349
1350 // Record the present section:
1351 isSet |= sn.type;
1352 }
1353
1355 if (step == -1 || step + pos < m_text.size()) {
1356 QDTPDEBUG << "invalid because" << QStringView{m_text}.sliced(pos)
1357 << "does not match" << separators.last() << pos;
1358 return StateNode();
1359 }
1360
1361 if (parserType != QMetaType::QTime) {
1362 if (year % 100 != year2digits && (isSet & YearSection2Digits)) {
1364 year, year2digits, month, day, dayofweek);
1365 if (!(isSet & YearSection)) {
1366 year = date.year();
1367 } else {
1368 conflicts = true;
1369 const SectionNode &sn = sectionNode(currentSectionIndex);
1370 if (sn.type == YearSection2Digits)
1371 year = date.year();
1372 }
1373 }
1374
1375 const auto fieldType = sectionType(currentSectionIndex);
1376 const QDate date(year, month, day, calendar);
1377 if ((!date.isValid() || dayofweek != calendar.dayOfWeek(date))
1378 && state == Acceptable && isSet & DayOfWeekSectionMask) {
1379 if (isSet & DaySection)
1380 conflicts = true;
1381 // Change to day of week should adjust day of month;
1382 // when day of month isn't set, so should change to year or month.
1383 if (currentSectionIndex == -1 || fieldType & DayOfWeekSectionMask
1384 || (!conflicts && (fieldType & (YearSectionMask | MonthSection)))) {
1385 day = weekDayWithinMonth(calendar, year, month, day, dayofweek);
1386 QDTPDEBUG << year << month << day << dayofweek
1387 << calendar.dayOfWeek(QDate(year, month, day, calendar));
1388 }
1389 }
1390
1391 bool needfixday = false;
1392 if (fieldType & DaySectionMask) {
1393 cachedDay = day;
1394 } else if (cachedDay > day && !(isSet & DayOfWeekSectionMask && state == Acceptable)) {
1395 day = cachedDay;
1396 needfixday = true;
1397 }
1398
1399 if (!calendar.isDateValid(year, month, day)) {
1400 if (day <= calendar.maximumDaysInMonth())
1401 cachedDay = day;
1402 if (day > calendar.minimumDaysInMonth() && calendar.isDateValid(year, month, 1))
1403 needfixday = true;
1404 }
1405 if (needfixday) {
1406 if (context == FromString)
1407 return StateNode();
1408 if (state == Acceptable && fixday) {
1409 day = qMin<int>(day, calendar.daysInMonth(month, year));
1410
1411 const QLocale loc = locale();
1412 for (int i=0; i<sectionNodesCount; ++i) {
1413 const SectionNode sn = sectionNode(i);
1414 if (sn.type & DaySection) {
1415 m_text.replace(sectionPos(sn), sectionSize(i), loc.toString(day));
1416 } else if (sn.type & DayOfWeekSectionMask) {
1417 const int dayOfWeek = calendar.dayOfWeek(QDate(year, month, day, calendar));
1418 const QLocale::FormatType dayFormat =
1419 (sn.type == DayOfWeekSectionShort
1421 const QString dayName(loc.dayName(dayOfWeek, dayFormat));
1422 m_text.replace(sectionPos(sn), sectionSize(i), dayName);
1423 }
1424 }
1425 } else if (state > Intermediate) {
1427 }
1428 }
1429 }
1430
1431 if (parserType != QMetaType::QDate) {
1432 if (isSet & Hour12Section) {
1433 const bool hasHour = isSet.testAnyFlag(Hour24Section);
1434 if (ampm == -1) // If we don't know from hour, assume am:
1435 ampm = !hasHour || hour < 12 ? 0 : 1;
1436 hour12 = hour12 % 12 + ampm * 12;
1437 if (!hasHour)
1438 hour = hour12;
1439 else if (hour != hour12)
1440 conflicts = true;
1441 } else if (ampm != -1) {
1442 if (!(isSet & (Hour24Section)))
1443 hour = 12 * ampm; // Special case: only ap section
1444 else if ((ampm == 0) != (hour < 12))
1445 conflicts = true;
1446 }
1447 }
1448
1449 QDTPDEBUG << year << month << day << hour << minute << second << msec;
1451
1452 const QDate date(year, month, day, calendar);
1453 const QTime time(hour, minute, second, msec);
1454 const QDateTime when = QDateTime(date, time, timeZone);
1455
1456 if (when.time() != time || when.date() != date) {
1457 // In a spring-forward, if we hit the skipped hour, we may have been
1458 // shunted out of it.
1459
1460 // If hour wasn't specified, so we're using our default, changing it may
1461 // fix that.
1462 if (!(isSet & HourSectionMask)) {
1463 switch (parserType) {
1464 case QMetaType::QDateTime: {
1465 qint64 msecs = when.toMSecsSinceEpoch();
1466 // Fortunately, that gets a useful answer, even though when is invalid ...
1467 const QDateTime replace = QDateTime::fromMSecsSinceEpoch(msecs, timeZone);
1468 const QTime tick = replace.time();
1469 if (replace.date() == date
1470 && (!(isSet & MinuteSection) || tick.minute() == minute)
1471 && (!(isSet & SecondSection) || tick.second() == second)
1472 && (!(isSet & MSecSection) || tick.msec() == msec)) {
1473 return StateNode(replace, state, padding, conflicts);
1474 }
1475 } break;
1476 case QMetaType::QDate:
1477 // Don't care about time, so just use start of day (and ignore spec):
1478 return StateNode(date.startOfDay(QTimeZone::UTC),
1479 state, padding, conflicts);
1480 break;
1481 case QMetaType::QTime:
1482 // Don't care about date or representation, so pick a safe representation:
1483 return StateNode(QDateTime(date, time, QTimeZone::UTC),
1484 state, padding, conflicts);
1485 default:
1486 Q_UNREACHABLE_RETURN(StateNode());
1487 }
1488 } else if (state > Intermediate) {
1490 }
1491 }
1492
1493 return StateNode(when, state, padding, conflicts);
1494}
1495
1502 const QDateTime &defaultValue, bool fixup) const
1503{
1504 const QDateTime minimum = getMinimum();
1505 const QDateTime maximum = getMaximum();
1506 m_text = input;
1507
1508 QDTPDEBUG << "parse" << input;
1509 StateNode scan = scanString(defaultValue, fixup);
1510 QDTPDEBUGN("'%s' => '%s'(%s)", m_text.toLatin1().constData(),
1511 scan.value.toString("yyyy/MM/dd hh:mm:ss.zzz"_L1).toLatin1().constData(),
1512 stateName(scan.state).toLatin1().constData());
1513
1514 if (scan.value.isValid() && scan.state != Invalid) {
1515 if (context != FromString && scan.value < minimum) {
1516 const QLatin1Char space(' ');
1517 if (scan.value >= minimum)
1518 qWarning("QDateTimeParser::parse Internal error 3 (%ls %ls)",
1519 qUtf16Printable(scan.value.toString()), qUtf16Printable(minimum.toString()));
1520
1521 bool done = false;
1522 scan.state = Invalid;
1523 const int sectionNodesCount = sectionNodes.size();
1524 for (int i=0; i<sectionNodesCount && !done; ++i) {
1525 const SectionNode &sn = sectionNodes.at(i);
1526 QString t = sectionText(m_text, i, sn.pos).toLower();
1527 if ((t.size() < sectionMaxSize(i)
1528 && ((fieldInfo(i) & (FixedWidth|Numeric)) != Numeric))
1529 || t.contains(space)) {
1530 switch (sn.type) {
1531 case AmPmSection:
1532 switch (findAmPm(t, i)) {
1533 case AM:
1534 case PM:
1535 scan.state = Acceptable;
1536 done = true;
1537 break;
1538 case Neither:
1539 scan.state = Invalid;
1540 done = true;
1541 break;
1542 case PossibleAM:
1543 case PossiblePM:
1544 case PossibleBoth: {
1545 const QDateTime copy(scan.value.addSecs(12 * 60 * 60));
1546 if (copy >= minimum && copy <= maximum) {
1547 scan.state = Intermediate;
1548 done = true;
1549 }
1550 break; }
1551 }
1552 Q_FALLTHROUGH();
1553 case MonthSection:
1554 if (sn.count >= 3) {
1555 const QDate when = scan.value.date();
1556 const int finalMonth = when.month(calendar);
1557 int tmp = finalMonth;
1558 // I know the first possible month makes the date too early
1559 while ((tmp = findMonth(t, tmp + 1, i, when.year(calendar))) != -1) {
1560 const QDateTime copy(scan.value.addMonths(tmp - finalMonth));
1561 if (copy >= minimum && copy <= maximum)
1562 break; // break out of while
1563 }
1564 if (tmp != -1) {
1565 scan.state = Intermediate;
1566 done = true;
1567 }
1568 break;
1569 }
1570 Q_FALLTHROUGH();
1571 default: {
1572 int toMin;
1573 int toMax;
1574
1575 if (sn.type & TimeSectionMask) {
1576 if (scan.value.daysTo(minimum) != 0)
1577 break;
1578
1579 const QTime time = scan.value.time();
1580 toMin = time.msecsTo(minimum.time());
1581 if (scan.value.daysTo(maximum) > 0)
1582 toMax = -1; // can't get to max
1583 else
1584 toMax = time.msecsTo(maximum.time());
1585 } else {
1586 toMin = scan.value.daysTo(minimum);
1587 toMax = scan.value.daysTo(maximum);
1588 }
1589 const int maxChange = sn.maxChange();
1590 if (toMin > maxChange) {
1591 QDTPDEBUG << "invalid because toMin > maxChange" << toMin
1592 << maxChange << t << scan.value << minimum;
1593 scan.state = Invalid;
1594 done = true;
1595 break;
1596 } else if (toMax > maxChange) {
1597 toMax = -1; // can't get to max
1598 }
1599
1600 const int min = getDigit(minimum, i);
1601 if (min == -1) {
1602 qWarning("QDateTimeParser::parse Internal error 4 (%ls)",
1603 qUtf16Printable(sn.name()));
1604 scan.state = Invalid;
1605 done = true;
1606 break;
1607 }
1608
1609 int max = toMax != -1 ? getDigit(maximum, i) : absoluteMax(i, scan.value);
1610 int pos = position + scan.padded - sn.pos;
1611 if (pos < 0 || pos >= t.size())
1612 pos = -1;
1613 if (!potentialValue(t.simplified(), min, max, i, scan.value, pos)) {
1614 QDTPDEBUG << "invalid because potentialValue(" << t.simplified() << min << max
1615 << sn.name() << "returned" << toMax << toMin << pos;
1616 scan.state = Invalid;
1617 done = true;
1618 break;
1619 }
1620 scan.state = Intermediate;
1621 done = true;
1622 break; }
1623 }
1624 }
1625 }
1626 } else {
1627 if (context == FromString) {
1628 // optimization
1629 Q_ASSERT(maximum.date().toJulianDay() == 5373484);
1630 if (scan.value.date().toJulianDay() > 5373484)
1631 scan.state = Invalid;
1632 } else if (scan.value > maximum) {
1633 scan.state = Invalid;
1634 }
1635
1636 QDTPDEBUG << "not checking intermediate because scanned value is" << scan.value << minimum << maximum;
1637 }
1638 }
1639
1640 // An invalid time should only arise if we set the state to less than acceptable:
1641 Q_ASSERT(scan.value.isValid() || scan.state != Acceptable);
1642
1643 return scan;
1644}
1645
1646/*
1647 \internal
1648 \brief Returns the index in \a entries with the best prefix match to \a text
1649
1650 Scans \a entries looking for an entry overlapping \a text as much as possible
1651 (an exact match beats any prefix match; a match of the full entry as prefix of
1652 text beats any entry but one matching a longer prefix; otherwise, the match of
1653 longest prefix wins, earlier entries beating later on a draw). Records the
1654 length of overlap in *used (if \a used is non-NULL) and the first entry that
1655 overlapped this much in *usedText (if \a usedText is non-NULL).
1656 */
1657static int findTextEntry(QStringView text, const ShortVector<QString> &entries, QString *usedText, int *used)
1658{
1659 if (text.isEmpty())
1660 return -1;
1661
1662 int bestMatch = -1;
1663 int bestCount = 0;
1664 for (int n = 0; n < entries.size(); ++n)
1665 {
1666 const QString &name = entries.at(n);
1667
1668 const int limit = qMin(text.size(), name.size());
1669 int i = 0;
1670 while (i < limit && text.at(i) == name.at(i).toLower())
1671 ++i;
1672 // Full match beats an equal prefix match:
1673 if (i > bestCount || (i == bestCount && i == name.size())) {
1674 bestCount = i;
1675 bestMatch = n;
1676 if (i == name.size() && i == text.size())
1677 break; // Exact match, name == text, wins.
1678 }
1679 }
1680 if (usedText && bestMatch != -1)
1681 *usedText = entries.at(bestMatch);
1682 if (used)
1683 *used = bestCount;
1684
1685 return bestMatch;
1686}
1687
1694int QDateTimeParser::findMonth(QStringView str, int startMonth, int sectionIndex,
1695 int year, QString *usedMonth, int *used) const
1696{
1697 const SectionNode &sn = sectionNode(sectionIndex);
1698 if (sn.type != MonthSection) {
1699 qWarning("QDateTimeParser::findMonth Internal error");
1700 return -1;
1701 }
1702
1704 QLocale l = locale();
1705 ShortVector<QString> monthNames;
1706 monthNames.reserve(13 - startMonth);
1707 for (int month = startMonth; month <= 12; ++month)
1708 monthNames.append(calendar.monthName(l, month, year, type));
1709
1710 const int index = findTextEntry(str, monthNames, usedMonth, used);
1711 return index < 0 ? index : index + startMonth;
1712}
1713
1714int QDateTimeParser::findDay(QStringView str, int startDay, int sectionIndex, QString *usedDay, int *used) const
1715{
1716 const SectionNode &sn = sectionNode(sectionIndex);
1717 if (!(sn.type & DaySectionMask)) {
1718 qWarning("QDateTimeParser::findDay Internal error");
1719 return -1;
1720 }
1721
1723 QLocale l = locale();
1724 ShortVector<QString> daysOfWeek;
1725 daysOfWeek.reserve(8 - startDay);
1726 for (int day = startDay; day <= 7; ++day)
1727 daysOfWeek.append(l.dayName(day, type));
1728
1729 const int index = findTextEntry(str, daysOfWeek, usedDay, used);
1730 return index < 0 ? index : index + startDay;
1731}
1732
1741QDateTimeParser::ParsedSection QDateTimeParser::findUtcOffset(QStringView str, int mode) const
1742{
1743 Q_ASSERT(mode > 0 && mode < 4);
1744 const bool startsWithUtc = str.startsWith("UTC"_L1);
1745 // Deal with UTC prefix if present:
1746 if (startsWithUtc) {
1747 if (mode != 1)
1748 return ParsedSection();
1749 str = str.sliced(3);
1750 if (str.isEmpty())
1751 return ParsedSection(Acceptable, 0, 3);
1752 }
1753
1754 const bool negativeSign = str.startsWith(u'-');
1755 // Must start with a sign:
1756 if (!negativeSign && !str.startsWith(u'+'))
1757 return ParsedSection();
1758 str = str.sliced(1); // drop sign
1759
1760 const int colonPosition = str.indexOf(u':');
1761 // Colon that belongs to offset is at most at position 2 (hh:mm)
1762 bool hasColon = (colonPosition >= 0 && colonPosition < 3);
1763
1764 // We deal only with digits at this point (except ':'), so collect them
1765 const int digits = hasColon ? colonPosition + 3 : 4;
1766 int i = 0;
1767 for (const int offsetLength = qMin(qsizetype(digits), str.size()); i < offsetLength; ++i) {
1768 if (i != colonPosition && !str.at(i).isDigit())
1769 break;
1770 }
1771 const int hoursLength = qMin(i, hasColon ? colonPosition : 2);
1772 if (hoursLength < 1)
1773 return ParsedSection();
1774 // Field either ends with hours or also has two digits of minutes
1775 if (i < digits) {
1776 // Only allow single-digit hours with UTC prefix or :mm suffix
1777 if (!startsWithUtc && hoursLength != 2)
1778 return ParsedSection();
1779 i = hoursLength;
1780 hasColon = false;
1781 }
1782 if (mode == (hasColon ? 2 : 3))
1783 return ParsedSection();
1784 str.truncate(i); // The rest of the string is not part of the UTC offset
1785
1786 bool isInt = false;
1787 const int hours = str.first(hoursLength).toInt(&isInt);
1788 if (!isInt)
1789 return ParsedSection();
1790 const QStringView minutesStr = str.mid(hasColon ? colonPosition + 1 : 2, 2);
1791 const int minutes = minutesStr.isEmpty() ? 0 : minutesStr.toInt(&isInt);
1792 if (!isInt)
1793 return ParsedSection();
1794
1795 // Keep in sync with QTimeZone::maxUtcOffset hours (14 at most). Also, user
1796 // could be in the middle of updating the offset (e.g. UTC+14:23) which is
1797 // an intermediate state
1798 const State status = (hours > 14 || minutes >= 60) ? Invalid
1799 : (hours == 14 && minutes > 0) ? Intermediate : Acceptable;
1800
1801 int offset = 3600 * hours + 60 * minutes;
1802 if (negativeSign)
1803 offset = -offset;
1804
1805 // Used: UTC, sign, hours, colon, minutes
1806 const int usedSymbols = (startsWithUtc ? 3 : 0) + 1 + hoursLength + (hasColon ? 1 : 0)
1807 + minutesStr.size();
1808
1809 return ParsedSection(status, offset, usedSymbols);
1810}
1811
1819QDateTimeParser::ParsedSection
1820QDateTimeParser::findTimeZoneName(QStringView str, const QDateTime &when) const
1821{
1822 const int systemLength = startsWithLocalTimeZone(str, when, locale());
1823#if QT_CONFIG(timezone)
1824 // Collect up plausibly-valid characters; let QTimeZone work out what's
1825 // truly valid.
1826 const auto invalidZoneNameCharacter = [] (const QChar &c) {
1827 const auto cu = c.unicode();
1828 return cu >= 127u || !(memchr("+-./:_", char(cu), 6) || c.isLetterOrNumber());
1829 };
1830 int index = std::distance(str.cbegin(),
1831 std::find_if(str.cbegin(), str.cend(), invalidZoneNameCharacter));
1832
1833 // Limit name fragments (between slashes) to 20 characters.
1834 // (Valid time-zone IDs are allowed up to 14 and Android has quirks up to 17.)
1835 // Limit number of fragments to six; no known zone name has more than four.
1836 int lastSlash = -1;
1837 int count = 0;
1838 Q_ASSERT(index <= str.size());
1839 while (lastSlash < index) {
1840 int slash = str.indexOf(u'/', lastSlash + 1);
1841 if (slash < 0 || slash > index)
1842 slash = index; // i.e. the end of the candidate text
1843 else if (++count > 5)
1844 index = slash; // Truncate
1845 if (slash - lastSlash > 20)
1846 index = lastSlash + 20; // Truncate
1847 // If any of those conditions was met, index <= slash, so this exits the loop:
1848 lastSlash = slash;
1849 }
1850
1851 for (; index > systemLength; --index) { // Find longest match
1853 QTimeZone zone(str.toLatin1());
1854 if (zone.isValid())
1855 return ParsedSection(Acceptable, zone.offsetFromUtc(when), index);
1856 }
1857#endif
1858 if (systemLength > 0) // won't actually use the offset, but need it to be valid
1859 return ParsedSection(Acceptable, when.toLocalTime().offsetFromUtc(), systemLength);
1860 return ParsedSection();
1861}
1862
1875QDateTimeParser::ParsedSection
1876QDateTimeParser::findTimeZone(QStringView str, const QDateTime &when,
1877 int maxVal, int minVal, int mode) const
1878{
1879 Q_ASSERT(mode > 0 && mode <= 4);
1880 // Short-cut Zulu suffix when it's all there is (rather than a prefix match):
1881 if (mode == 1 && str == u'Z')
1882 return ParsedSection(Acceptable, 0, 1);
1883
1884 ParsedSection section;
1885 if (mode != 4)
1886 section = findUtcOffset(str, mode);
1887 if (mode != 2 && mode != 3 && section.used <= 0) // if nothing used, try time zone parsing
1888 section = findTimeZoneName(str, when);
1889 // It can be a well formed time zone specifier, but with value out of range
1890 if (section.state == Acceptable && (section.value < minVal || section.value > maxVal))
1891 section.state = Intermediate;
1892 if (section.used > 0)
1893 return section;
1894
1895 if (mode == 1) {
1896 // Check if string is UTC or alias to UTC, after all other options
1897 if (str.startsWith("UTC"_L1))
1898 return ParsedSection(Acceptable, 0, 3);
1899 if (str.startsWith(u'Z'))
1900 return ParsedSection(Acceptable, 0, 1);
1901 }
1902
1903 return ParsedSection();
1904}
1905
1920QDateTimeParser::AmPmFinder QDateTimeParser::findAmPm(QString &str, int sectionIndex, int *used) const
1921{
1922 const SectionNode &s = sectionNode(sectionIndex);
1923 if (s.type != AmPmSection) {
1924 qWarning("QDateTimeParser::findAmPm Internal error");
1925 return Neither;
1926 }
1927 if (used)
1928 *used = str.size();
1929 if (QStringView(str).trimmed().isEmpty())
1930 return PossibleBoth;
1931
1932 const QLatin1Char space(' ');
1933 int size = sectionMaxSize(sectionIndex);
1934
1935 enum {
1936 amindex = 0,
1937 pmindex = 1
1938 };
1939 QString ampm[2];
1940 ampm[amindex] = getAmPmText(AmText, Case(s.count));
1941 ampm[pmindex] = getAmPmText(PmText, Case(s.count));
1942 for (int i = 0; i < 2; ++i)
1943 ampm[i].truncate(size);
1944
1945 QDTPDEBUG << "findAmPm" << str << ampm[0] << ampm[1];
1946
1947 if (str.startsWith(ampm[amindex], Qt::CaseInsensitive)) {
1948 str = ampm[amindex];
1949 return AM;
1950 } else if (str.startsWith(ampm[pmindex], Qt::CaseInsensitive)) {
1951 str = ampm[pmindex];
1952 return PM;
1953 } else if (context == FromString || (str.count(space) == 0 && str.size() >= size)) {
1954 return Neither;
1955 }
1956 size = qMin(size, str.size());
1957
1958 bool broken[2] = {false, false};
1959 for (int i=0; i<size; ++i) {
1960 const QChar ch = str.at(i);
1961 if (ch != space) {
1962 for (int j=0; j<2; ++j) {
1963 if (!broken[j]) {
1964 int index = ampm[j].indexOf(ch);
1965 QDTPDEBUG << "looking for" << ch
1966 << "in" << ampm[j] << "and got" << index;
1967 if (index == -1) {
1968 if (ch.category() == QChar::Letter_Uppercase) {
1969 index = ampm[j].indexOf(ch.toLower());
1970 QDTPDEBUG << "trying with" << ch.toLower()
1971 << "in" << ampm[j] << "and got" << index;
1972 } else if (ch.category() == QChar::Letter_Lowercase) {
1973 index = ampm[j].indexOf(ch.toUpper());
1974 QDTPDEBUG << "trying with" << ch.toUpper()
1975 << "in" << ampm[j] << "and got" << index;
1976 }
1977 if (index == -1) {
1978 broken[j] = true;
1979 if (broken[amindex] && broken[pmindex]) {
1980 QDTPDEBUG << str << "didn't make it";
1981 return Neither;
1982 }
1983 continue;
1984 } else {
1985 str[i] = ampm[j].at(index); // fix case
1986 }
1987 }
1988 ampm[j].remove(index, 1);
1989 }
1990 }
1991 }
1992 }
1993 if (!broken[pmindex] && !broken[amindex])
1994 return PossibleBoth;
1995 return (!broken[amindex] ? PossibleAM : PossiblePM);
1996}
1997
2004{
2005 switch (type) {
2006 // Time. unit is msec
2007 case MSecSection: return 999;
2008 case SecondSection: return 59 * 1000;
2009 case MinuteSection: return 59 * 60 * 1000;
2010 case Hour24Section: case Hour12Section: return 59 * 60 * 60 * 1000;
2011
2012 // Date. unit is day
2014 case DayOfWeekSectionLong: return 7;
2015 case DaySection: return 30;
2016 case MonthSection: return 365 - 31;
2017 case YearSection: return 9999 * 365;
2018 case YearSection2Digits: return 100 * 365;
2019 default:
2020 qWarning("QDateTimeParser::maxChange() Internal error (%ls)",
2022 }
2023
2024 return -1;
2025}
2026
2027QDateTimeParser::FieldInfo QDateTimeParser::fieldInfo(int index) const
2028{
2029 FieldInfo ret;
2030 const SectionNode &sn = sectionNode(index);
2031 switch (sn.type) {
2032 case MSecSection:
2033 ret |= Fraction;
2034 Q_FALLTHROUGH();
2035 case SecondSection:
2036 case MinuteSection:
2037 case Hour24Section:
2038 case Hour12Section:
2039 case YearSection2Digits:
2040 ret |= AllowPartial;
2041 Q_FALLTHROUGH();
2042 case YearSection:
2043 ret |= Numeric;
2044 if (sn.count != 1)
2045 ret |= FixedWidth;
2046 break;
2047 case MonthSection:
2048 case DaySection:
2049 switch (sn.count) {
2050 case 2:
2051 ret |= FixedWidth;
2052 Q_FALLTHROUGH();
2053 case 1:
2055 break;
2056 }
2057 break;
2060 if (sn.count == 3)
2061 ret |= FixedWidth;
2062 break;
2063 case AmPmSection:
2064 // Some locales have different length AM and PM texts.
2065 if (getAmPmText(AmText, Case(sn.count)).size()
2066 == getAmPmText(PmText, Case(sn.count)).size()) {
2067 // Only relevant to DateTimeEdit's fixups in parse().
2068 ret |= FixedWidth;
2069 }
2070 break;
2071 case TimeZoneSection:
2072 break;
2073 default:
2074 qWarning("QDateTimeParser::fieldInfo Internal error 2 (%d %ls %d)",
2075 index, qUtf16Printable(sn.name()), sn.count);
2076 break;
2077 }
2078 return ret;
2079}
2080
2082{
2083 QChar fillChar;
2084 switch (type) {
2085 case AmPmSection: return count == 1 ? "ap"_L1 : count == 2 ? "AP"_L1 : "Ap"_L1;
2086 case MSecSection: fillChar = u'z'; break;
2087 case SecondSection: fillChar = u's'; break;
2088 case MinuteSection: fillChar = u'm'; break;
2089 case Hour24Section: fillChar = u'H'; break;
2090 case Hour12Section: fillChar = u'h'; break;
2093 case DaySection: fillChar = u'd'; break;
2094 case MonthSection: fillChar = u'M'; break;
2095 case YearSection2Digits:
2096 case YearSection: fillChar = u'y'; break;
2097 default:
2098 qWarning("QDateTimeParser::sectionFormat Internal error (%ls)",
2100 return QString();
2101 }
2102 if (fillChar.isNull()) {
2103 qWarning("QDateTimeParser::sectionFormat Internal error 2");
2104 return QString();
2105 }
2106 return QString(count, fillChar);
2107}
2108
2109
2117bool QDateTimeParser::potentialValue(QStringView str, int min, int max, int index,
2118 const QDateTime &currentValue, int insert) const
2119{
2120 if (str.isEmpty())
2121 return true;
2122
2123 const int size = sectionMaxSize(index);
2124 int val = (int)locale().toUInt(str);
2125 const SectionNode &sn = sectionNode(index);
2126 if (sn.type == YearSection2Digits) {
2127 const int year = currentValue.date().year(calendar);
2128 val += year - (year % 100);
2129 }
2130 if (val >= min && val <= max && str.size() == size)
2131 return true;
2132 if (val > max || (str.size() == size && val < min))
2133 return false;
2134
2135 const int len = size - str.size();
2136 for (int i=0; i<len; ++i) {
2137 for (int j=0; j<10; ++j) {
2138 if (potentialValue(str + QLatin1Char('0' + j), min, max, index, currentValue, insert)) {
2139 return true;
2140 } else if (insert >= 0) {
2141 const QString tmp = str.left(insert) + QLatin1Char('0' + j) + str.mid(insert);
2142 if (potentialValue(tmp, min, max, index, currentValue, insert))
2143 return true;
2144 }
2145 }
2146 }
2147
2148 return false;
2149}
2150
2155{
2156 Q_ASSERT(text.size() < sectionMaxSize(index));
2157 const SectionNode &node = sectionNode(index);
2158 int min = absoluteMin(index);
2159 int max = absoluteMax(index, current);
2160 // Time-zone field is only numeric if given as offset from UTC:
2161 if (node.type != TimeZoneSection || current.timeSpec() == Qt::OffsetFromUTC) {
2162 const QDateTime maximum = getMaximum();
2163 const QDateTime minimum = getMinimum();
2164 Q_ASSERT(current >= minimum && current <= maximum);
2165
2166 QDateTime tmp = current;
2167 if (!setDigit(tmp, index, min) || tmp < minimum)
2168 min = getDigit(minimum, index);
2169
2170 if (!setDigit(tmp, index, max) || tmp > maximum)
2171 max = getDigit(maximum, index);
2172 }
2173 int pos = cursorPosition() - node.pos;
2174 if (pos < 0 || pos >= text.size())
2175 pos = -1;
2176
2177 /*
2178 If the value potentially can become another valid entry we don't want to
2179 skip to the next. E.g. In a M field (month without leading 0) if you type
2180 1 we don't want to autoskip (there might be [012] following) but if you
2181 type 3 we do.
2182 */
2183 return !potentialValue(text, min, max, index, current, pos);
2184}
2185
2192{
2193 switch (s) {
2194 case AmPmSection: return "AmPmSection"_L1;
2195 case DaySection: return "DaySection"_L1;
2196 case DayOfWeekSectionShort: return "DayOfWeekSectionShort"_L1;
2197 case DayOfWeekSectionLong: return "DayOfWeekSectionLong"_L1;
2198 case Hour24Section: return "Hour24Section"_L1;
2199 case Hour12Section: return "Hour12Section"_L1;
2200 case MSecSection: return "MSecSection"_L1;
2201 case MinuteSection: return "MinuteSection"_L1;
2202 case MonthSection: return "MonthSection"_L1;
2203 case SecondSection: return "SecondSection"_L1;
2204 case TimeZoneSection: return "TimeZoneSection"_L1;
2205 case YearSection: return "YearSection"_L1;
2206 case YearSection2Digits: return "YearSection2Digits"_L1;
2207 case NoSection: return "NoSection"_L1;
2208 case FirstSection: return "FirstSection"_L1;
2209 case LastSection: return "LastSection"_L1;
2210 default: return "Unknown section "_L1 + QString::number(int(s));
2211 }
2212}
2213
2220{
2221 switch (s) {
2222 case Invalid: return "Invalid"_L1;
2223 case Intermediate: return "Intermediate"_L1;
2224 case Acceptable: return "Acceptable"_L1;
2225 default: return "Unknown state "_L1 + QString::number(s);
2226 }
2227}
2228
2229
2234QDateTime QDateTimeParser::baseDate(const QTimeZone &zone) const
2235{
2236 QDateTime when = QDate(defaultCenturyStart, 1, 1).startOfDay(zone);
2237 if (const QDateTime start = getMinimum(); when < start)
2238 return start;
2239 if (const QDateTime end = getMaximum(); when > end)
2240 return end;
2241 return when;
2242}
2243
2244// Only called when we want only one of date or time; use UTC to avoid bogus DST issues.
2245bool QDateTimeParser::fromString(const QString &t, QDate *date, QTime *time, int baseYear) const
2246{
2247 defaultCenturyStart = baseYear;
2248 const StateNode tmp = parse(t, -1, baseDate(QTimeZone::UTC), false);
2249 if (tmp.state != Acceptable || tmp.conflicts)
2250 return false;
2251
2252 if (time) {
2253 Q_ASSERT(!date);
2254 const QTime t = tmp.value.time();
2255 if (!t.isValid())
2256 return false;
2257 *time = t;
2258 }
2259
2260 if (date) {
2261 Q_ASSERT(!time);
2262 const QDate d = tmp.value.date();
2263 if (!d.isValid())
2264 return false;
2265 *date = d;
2266 }
2267 return true;
2268}
2269
2270// Only called when we want both date and time; default to local time.
2271bool QDateTimeParser::fromString(const QString &t, QDateTime *datetime, int baseYear) const
2272{
2273 defaultCenturyStart = baseYear;
2274 const StateNode tmp = parse(t, -1, baseDate(QTimeZone::LocalTime), false);
2275 if (datetime)
2276 *datetime = tmp.value;
2277 return tmp.state >= Intermediate && !tmp.conflicts && tmp.value.isValid();
2278}
2279
2281{
2282 // NB: QDateTimeParser always uses Qt::LocalTime time spec by default. If
2283 // any subclass needs a changing time spec, it must override this
2284 // method. At the time of writing, this is done by QDateTimeEditPrivate.
2285
2286 // Cache the only case
2287 static const QDateTime localTimeMin(QDATETIMEEDIT_DATE_MIN.startOfDay());
2288 return localTimeMin;
2289}
2290
2292{
2293 // NB: QDateTimeParser always uses Qt::LocalTime time spec by default. If
2294 // any subclass needs a changing time spec, it must override this
2295 // method. At the time of writing, this is done by QDateTimeEditPrivate.
2296
2297 // Cache the only case
2298 static const QDateTime localTimeMax(QDATETIMEEDIT_DATE_MAX.endOfDay());
2299 return localTimeMax;
2300}
2301
2302QString QDateTimeParser::getAmPmText(AmPm ap, Case cs) const
2303{
2304 const QLocale loc = locale();
2305 QString raw = ap == AmText ? loc.amText() : loc.pmText();
2306 switch (cs)
2307 {
2308 case UpperCase: return std::move(raw).toUpper();
2309 case LowerCase: return std::move(raw).toLower();
2310 case NativeCase: return raw;
2311 }
2312 Q_UNREACHABLE_RETURN(raw);
2313}
2314
2315/*
2316 \internal
2317*/
2318
2320{
2321 return (s1.type == s2.type) && (s1.pos == s2.pos) && (s1.count == s2.count);
2322}
2323
2329{
2330 calendar = cal;
2331}
2332
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
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.
@ Unspecified
Definition qcalendar.h:57
QString monthName(const QLocale &locale, int month, int year=Unspecified, QLocale::FormatType format=QLocale::LongFormat) const
Returns a suitably localised name for a month.
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
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
int getDigit(const QDateTime &dt, int index) const
StateNode parse(const QString &input, int position, const QDateTime &defaultValue, bool fixup) const
virtual int cursorPosition() const
virtual QDateTime getMinimum() const
bool skipToNextSection(int section, const QDateTime &current, QStringView sectionText) const
bool fromString(const QString &text, QDate *date, QTime *time, int baseYear=QLocale::DefaultTwoDigitBaseYear) 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
void setCalendar(QCalendar calendar)
Sets cal as the calendar to use.
bool parseFormat(QStringView format)
\inmodule QtCore\reentrant
Definition qdatetime.h:283
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.
QString timeZoneAbbreviation() const
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:29
constexpr bool isValid() const
Returns true if this date is valid; otherwise returns false.
Definition qdatetime.h:71
constexpr qint64 toJulianDay() const
Converts the date to a Julian day.
Definition qdatetime.h:170
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...
QDateTime startOfDay(const QTimeZone &zone) const
void append(parameter_type t)
Definition qlist.h:458
uint toUInt(const QString &s, bool *ok=nullptr) const
Returns the unsigned int represented by the localized string s.
Definition qlocale.h:957
QString dayName(int, FormatType format=LongFormat) const
Definition qlocale.cpp:2997
@ LongFormat
Definition qlocale.h:875
@ ShortFormat
Definition qlocale.h:875
QString amText() const
Definition qlocale.cpp:3439
QString pmText() const
Definition qlocale.cpp:3459
QString toString(qlonglong i) const
Returns a localized string representation of i.
Definition qlocale.cpp:2052
\inmodule QtCore
\inmodule QtCore
Definition qstringview.h:78
bool startsWith(QStringView s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const noexcept
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 left(qsizetype n) const noexcept
QString toString() const
Returns a deep copy of this string view's data as a QString.
Definition qstring.h:1121
constexpr QStringView sliced(qsizetype pos) const noexcept
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
QString left(qsizetype n) const &
Definition qstring.h:363
QByteArray toLatin1() const &
Definition qstring.h:630
qsizetype indexOf(QLatin1StringView s, qsizetype from=0, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4517
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:731
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:5455
QString & replace(qsizetype i, qsizetype len, QChar after)
Definition qstring.cpp:3824
QString sliced(qsizetype pos) const &
Definition qstring.h:394
void truncate(qsizetype pos)
Truncates the string at the given position index.
Definition qstring.cpp:6319
QString mid(qsizetype position, qsizetype n=-1) const &
Definition qstring.cpp:5300
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
const_iterator cbegin() const
Definition qstring.h:1353
qsizetype size() const noexcept
Returns the number of characters in this string.
Definition qstring.h:186
const_iterator cend() const
Definition qstring.h:1361
QString first(qsizetype n) const &
Definition qstring.h:390
const QChar at(qsizetype i) const
Returns the character at the given index position in the string.
Definition qstring.h:1226
QString & insert(qsizetype i, QChar c)
Definition qstring.cpp:3132
QString toLower() const &
Definition qstring.h:435
qsizetype count(QChar c, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4833
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:8084
static QString static QString asprintf(const char *format,...) Q_ATTRIBUTE_FORMAT_PRINTF(1
Definition qstring.cpp:7263
\inmodule QtCore
Definition qtimezone.h:26
static constexpr int MaxUtcOffsetSecs
Definition qtimezone.h:90
static QTimeZone fromSecondsAheadOfUtc(int offset)
Definition qtimezone.h:132
static constexpr int MinUtcOffsetSecs
Definition qtimezone.h:87
\inmodule QtCore \reentrant
Definition qdatetime.h:215
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.
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.
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 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)
#define QDTPDEBUGN
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)
#define QDTPDEBUG
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
#define QDATETIMEEDIT_DATE_MIN
#define QDATETIMEEDIT_DATE_MAX
Q_CORE_EXPORT bool operator==(QDateTimeParser::SectionNode s1, QDateTimeParser::SectionNode s2)
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
qsizetype qt_repeatCount(QStringView s)
Definition qlocale.cpp:675
static constexpr int digits(int number)
#define qWarning
Definition qlogging.h:166
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
GLuint start
GLenum GLuint GLintptr offset
GLboolean GLboolean g
GLuint name
GLint first
GLfloat n
GLint GLsizei GLsizei GLenum format
GLint y
GLdouble s
[6]
Definition qopenglext.h:235
const GLubyte * c
GLuint GLfloat * val
GLint limit
GLsizei maxCount
Definition qopenglext.h:677
GLdouble GLdouble t
Definition qopenglext.h:243
GLuint64EXT * result
[6]
GLenum GLsizei len
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:1543
#define zero
#define s2
QString qTzName(int dstIndex)
#define Q_UNUSED(x)
ptrdiff_t qsizetype
Definition qtypes.h:165
long long qint64
Definition qtypes.h:60
static double WeekDay(double t)
static QString quote(const QString &str)
QList< int > list
[14]
QCalendarWidget * calendar
[0]
static QString name(Section s)
\inmodule QtCore \reentrant
Definition qchar.h:18