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