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

source code of qtbase/src/corelib/time/qdatetimeparser.cpp