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 
61 QT_BEGIN_NAMESPACE
62 
63 template <typename T>
64 using ShortVector = QVarLengthArray<T, 13>; // enough for month (incl. leap) and day-of-week names
65 
~QDateTimeParser()66 QDateTimeParser::~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 
getDigit(const QDateTime & t,int index) const79 int QDateTimeParser::getDigit(const QDateTime &t, int index) const
80 {
81     if (index < 0 || index >= sectionNodes.size()) {
82 #if QT_CONFIG(datestring)
83         qWarning("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(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(calendar);
99     case MonthSection: return t.date().month(calendar);
100     case DaySection: return t.date().day(calendar);
101     case DayOfWeekSectionShort:
102     case DayOfWeekSectionLong: return t.date().day(calendar);
103     case AmPmSection: return t.time().hour() > 11 ? 1 : 0;
104 
105     default: break;
106     }
107 
108 #if QT_CONFIG(datestring)
109     qWarning("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 
setDigit(QDateTime & v,int index,int newVal) const129 bool QDateTimeParser::setDigit(QDateTime &v, int index, int newVal) const
130 {
131     if (index < 0 || index >= sectionNodes.size()) {
132 #if QT_CONFIG(datestring)
133         qWarning("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(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(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("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(date.month, date.year);
191         if (date.day > max)
192             date.day = max;
193     }
194 
195     const QDate newDate = calendar.dateFromParts(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 
absoluteMax(int s,const QDateTime & cur) const217 int QDateTimeParser::absoluteMax(int s, const QDateTime &cur) const
218 {
219     const SectionNode &sn = sectionNode(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(calendar) : calendar.maximumDaysInMonth();
248     case AmPmSection:
249         return 1;
250     default:
251         break;
252     }
253     qWarning("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 
absoluteMin(int s) const264 int QDateTimeParser::absoluteMin(int s) const
265 {
266     const SectionNode &sn = sectionNode(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("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 
sectionNode(int sectionIndex) const299 const 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(sectionIndex);
312     }
313 
314     qWarning("QDateTimeParser::sectionNode() Internal error (%d)",
315              sectionIndex);
316     return none;
317 }
318 
sectionType(int sectionIndex) const319 QDateTimeParser::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 
sectionPos(int sectionIndex) const331 int QDateTimeParser::sectionPos(int sectionIndex) const
332 {
333     return sectionPos(sectionNode(sectionIndex));
334 }
335 
sectionPos(const SectionNode & sn) const336 int 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("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 
unquote(const QStringRef & str)359 static 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 - 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 
countRepeat(const QString & str,int index,int maxCount)383 static inline int countRepeat(const QString &str, int index, int maxCount)
384 {
385     int count = 1;
386     const QChar ch(str.at(index));
387     const int max = qMin(index + maxCount, str.size());
388     while (index + count < max && str.at(index + count) == ch) {
389         ++count;
390     }
391     return count;
392 }
393 
appendSeparator(QStringList * list,const QString & string,int from,int size,int lastQuote)394 static inline void appendSeparator(QStringList *list, const QString &string, int from, int size, int lastQuote)
395 {
396     const QStringRef separator = string.midRef(from, size);
397     list->append(lastQuote >= from ? unquote(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 */
parseFormat(const QString & newFormat)406 bool 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("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 - 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 = { hour, i - add, countRepeat(newFormat, i, 2), 0 };
442                     newSectionNodes.append(sn);
443                     appendSeparator(&newSeparators, newFormat, index, 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 = { MinuteSection, i - add, countRepeat(newFormat, i, 2), 0 };
452                     newSectionNodes.append(sn);
453                     appendSeparator(&newSeparators, newFormat, index, 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 = { SecondSection, i - add, countRepeat(newFormat, i, 2), 0 };
462                     newSectionNodes.append(sn);
463                     appendSeparator(&newSeparators, newFormat, index, 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 = { MSecSection, i - add, countRepeat(newFormat, i, 3) < 3 ? 1 : 3, 0 };
473                     newSectionNodes.append(sn);
474                     appendSeparator(&newSeparators, newFormat, index, 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 = { AmPmSection, i - add, (cap ? 1 : 0), 0 };
485                     newSectionNodes.append(sn);
486                     appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
487                     newDisplay |= AmPmSection;
488                     if (i + 1 < newFormat.size()
489                         && newFormat.at(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(newFormat, i, 4);
498                     if (repeat >= 2) {
499                         const SectionNode sn = { repeat == 4 ? YearSection : YearSection2Digits,
500                                                  i - add, repeat == 4 ? 4 : 2, 0 };
501                         newSectionNodes.append(sn);
502                         appendSeparator(&newSeparators, newFormat, index, 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 = { MonthSection, i - add, countRepeat(newFormat, i, 4), 0 };
512                     newSectionNodes.append(sn);
513                     newSeparators.append(unquote(newFormat.midRef(index, 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(newFormat, i, 4);
522                     const Section sectionType = (repeat == 4 ? DayOfWeekSectionLong
523                         : (repeat == 3 ? DayOfWeekSectionShort : DaySection));
524                     const SectionNode sn = { sectionType, i - add, repeat, 0 };
525                     newSectionNodes.append(sn);
526                     appendSeparator(&newSeparators, newFormat, index, 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 = { TimeZoneSection, i - add, countRepeat(newFormat, i, 4), 0 };
535                     newSectionNodes.append(sn);
536                     appendSeparator(&newSeparators, newFormat, index, 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(&newSeparators, newFormat, index, index - max, lastQuote);
562     } else {
563         newSeparators.append(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("separators:\n'%s'", separators.join(QLatin1String("\n")).toLatin1().constData());
578 
579     return true;
580 }
581 
582 /*!
583   \internal
584 
585   Returns the size of section \a s.
586 */
587 
sectionSize(int sectionIndex) const588 int QDateTimeParser::sectionSize(int sectionIndex) const
589 {
590     if (sectionIndex < 0)
591         return 0;
592 
593     if (sectionIndex >= sectionNodes.size()) {
594         qWarning("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 + 1) - sectionPos(sectionIndex)
620             - separators.at(sectionIndex + 1).size();
621     }
622 }
623 
624 
sectionMaxSize(Section s,int count) const625 int 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(getAmPmText(AmText, LowerCase).size(),
639                                   getAmPmText(PmText, LowerCase).size());
640         const int upperMax = qMax(getAmPmText(AmText, UpperCase).size(),
641                                   getAmPmText(PmText, UpperCase).size());
642         return qMax(lowerMax, 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(l, i, QCalendar::Unspecified, format)
674                                      : l.dayName(i, format));
675                 ret = qMax(str.size(), 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("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 
sectionMaxSize(int index) const712 int QDateTimeParser::sectionMaxSize(int index) const
713 {
714     const SectionNode &sn = sectionNode(index);
715     return sectionMaxSize(sn.type, 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 
sectionText(const QString & text,int sectionIndex,int index) const726 QString 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(index, sectionSize(sectionIndex));
738 }
739 
sectionText(int sectionIndex) const740 QString QDateTimeParser::sectionText(int sectionIndex) const
741 {
742     const SectionNode &sn = sectionNode(sectionIndex);
743     return sectionText(displayText(), sectionIndex, sn.pos);
744 }
745 
746 
747 #if QT_CONFIG(datestring)
748 
749 QDateTimeParser::ParsedSection
parseSection(const QDateTime & currentValue,int sectionIndex,int offset,QString * text) const750 QDateTimeParser::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("QDateTimeParser::parseSection Internal error (%ls %d)",
757                  qUtf16Printable(sn.name()), sectionIndex);
758         return result;
759     }
760 
761     const int sectionmaxsize = sectionMaxSize(sectionIndex);
762     QStringRef sectionTextRef = text->midRef(offset, 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(sectiontext, sectionIndex, &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("This should never happen (findAmPm returned %d)", ampm);
790             break;
791         }
792         if (result.state != Invalid)
793             text->replace(offset, used, sectiontext.constData(), used);
794         break; }
795     case TimeZoneSection:
796         result = findTimeZone(sectionTextRef, currentValue,
797                               absoluteMax(sectionIndex),
798                               absoluteMin(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(calendar);
809                 const int min = (year == minDate.year(calendar)) ? minDate.month(calendar) : 1;
810                 num = findMonth(sectiontext.toLower(), min, sectionIndex, year, &sectiontext, &used);
811             } else {
812                 num = findDay(sectiontext.toLower(), 1, sectionIndex, &sectiontext, &used);
813             }
814 
815             result = ParsedSection(Intermediate, num, used);
816             if (num != -1) {
817                 text->replace(offset, used, sectiontext.constData(), 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(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(sectiontextSize);
849             for (int digits = sectiontextSize; digits >= 1; --digits) {
850                 digitsStr.truncate(digits);
851                 int tmp = (int)loc.toUInt(digitsStr, &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(digits) << tmp << digits;
862                     last = tmp;
863                     used = digits;
864                     break;
865                 }
866             }
867 
868             if (last == -1) {
869                 QChar first(sectionTextRef.at(0));
870                 if (separators.at(sectionIndex + 1).startsWith(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(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(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(sectionIndex, currentValue, digitsStr)) {
891                         const int missingZeroes = sectionmaxsize - digitsStr.size();
892                         result = ParsedSection(Acceptable, last, sectionmaxsize, missingZeroes);
893                         text->insert(offset, 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("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 
weekDayWithinMonth(QCalendar calendar,QDate rough,int weekDay)922 static 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(calendar) + weekDay - calendar.dayOfWeek(rough);
927     if (day <= 0)
928         return day + 7;
929     if (day > rough.daysInMonth(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 
actualDate(QDateTimeParser::Sections known,const QCalendar & calendar,int year,int year2digits,int month,int day,int dayofweek)942 static 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(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(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(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(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(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, actual, 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(actual) == dayofweek)
1032                     return actual;
1033             }
1034             if (m + month <= 12) {
1035                 actual = QDate(year, month + m, day, calendar);
1036                 if (calendar.dayOfWeek(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(actual) == dayofweek)
1056                 return actual;
1057             actual = QDate(year - 100, month, day, calendar);
1058             if (calendar.dayOfWeek(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(actual) == dayofweek)
1065                     return actual;
1066                 actual = QDate(year + y, month, day, calendar);
1067                 if (calendar.dayOfWeek(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 
actualTime(QDateTimeParser::Sections known,int hour,int hour12,int ampm,int minute,int second,int msec)1081 static 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 */
1115 QDateTimeParser::StateNode
scanString(const QDateTime & defaultValue,bool fixup,QString * input) const1116 QDateTimeParser::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, &month, &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(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(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(index);
1162         if (input->midRef(pos, separator.size()) != separator) {
1163             QDTPDEBUG << "invalid because" << input->midRef(pos, 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(index);
1172         ParsedSection sect;
1173 
1174         {
1175             const QDate date = actualDate(isSet, calendar, year, year2digits,
1176                                           month, day, dayofweek);
1177             const QTime time = actualTime(isSet, hour, hour12, ampm, minute, second, msec);
1178             sect = parseSection(
1179 #if QT_CONFIG(timezone)
1180                                 tspec == Qt::TimeZone ? QDateTime(date, time, timeZone) :
1181 #endif
1182                                 QDateTime(date, time, tspec, zoneOffset),
1183                                 index, pos, input);
1184         }
1185 
1186         QDTPDEBUG << "sectionValue" << sn.name() << *input
1187                   << "pos" << pos << "used" << sect.used << stateName(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("%1").arg(sect.value, sn.count, 10, QLatin1Char('0'));
1194                 input->replace(pos, sect.used, newText);
1195                 sect.used = sn.count;
1196             }
1197         }
1198 
1199         state = qMin<State>(state, 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(pos, sect.used);
1210                 Q_ASSERT(!zoneName.isEmpty()); // sect.used > 0
1211 
1212                 const QStringRef offsetStr = zoneName.startsWith(QLatin1String("UTC"))
1213                                              ? zoneName.mid(3) : zoneName;
1214                 const bool isUtcOffset = offsetStr.startsWith(QLatin1Char('+'))
1215                                          || offsetStr.startsWith(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>(1, sect.value); break;
1244         case AmPmSection: current = &ampm; break;
1245         default:
1246             qWarning("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(state);
1255 
1256         if (!current) {
1257             qWarning("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(pos) != separators.last()) {
1275         QDTPDEBUG << "invalid because" << input->midRef(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(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(currentSectionIndex);
1301             if (sn.type & DayOfWeekSectionMask || currentSectionIndex == -1) {
1302                 // dayofweek should be preferred
1303                 day = weekDayWithinMonth(calendar, date, dayofweek);
1304                 QDTPDEBUG << year << month << day << dayofweek
1305                           << calendar.dayOfWeek(QDate(year, month, day, calendar));
1306             }
1307         }
1308 
1309         bool needfixday = false;
1310         if (sectionType(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, 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>(day, calendar.daysInMonth(month, year));
1329 
1330                 const QLocale loc = locale();
1331                 for (int i=0; i<sectionNodesCount; ++i) {
1332                     const SectionNode sn = sectionNode(i);
1333                     if (sn.type & DaySection) {
1334                         input->replace(sectionPos(sn), sectionSize(i), loc.toString(day));
1335                     } else if (sn.type & DayOfWeekSectionMask) {
1336                         const int dayOfWeek = calendar.dayOfWeek(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, dayFormat));
1341                         input->replace(sectionPos(sn), sectionSize(i), 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 (parserType == QMetaType::QDateTime && !(isSet & HourSectionMask) && !when.isValid()) {
1389         qint64 msecs = when.toMSecsSinceEpoch();
1390         // Fortunately, that gets a useful answer, even though when is invalid ...
1391         const QDateTime replace =
1392 #if QT_CONFIG(timezone)
1393             tspec == Qt::TimeZone
1394             ? QDateTime::fromMSecsSinceEpoch(msecs, timeZone) :
1395 #endif
1396             QDateTime::fromMSecsSinceEpoch(msecs, tspec, zoneOffset);
1397         const QTime tick = replace.time();
1398         if (replace.date() == date
1399             && (!(isSet & MinuteSection) || tick.minute() == minute)
1400             && (!(isSet & SecondSection) || tick.second() == second)
1401             && (!(isSet & MSecSection)   || tick.msec() == msec)) {
1402             return StateNode(replace, state, padding, conflicts);
1403         }
1404     }
1405 
1406     return StateNode(when, state, padding, conflicts);
1407 }
1408 
1409 /*!
1410   \internal
1411 */
1412 
1413 QDateTimeParser::StateNode
parse(QString input,int position,const QDateTime & defaultValue,bool fixup) const1414 QDateTimeParser::parse(QString input, int position, const QDateTime &defaultValue, bool fixup) const
1415 {
1416     const QDateTime minimum = getMinimum();
1417     const QDateTime maximum = getMaximum();
1418 
1419     QDTPDEBUG << "parse" << input;
1420     StateNode scan = scanString(defaultValue, fixup, &input);
1421     QDTPDEBUGN("'%s' => '%s'(%s)", input.toLatin1().constData(),
1422                scan.value.toString(QLatin1String("yyyy/MM/dd hh:mm:ss.zzz")).toLatin1().constData(),
1423                stateName(scan.state).toLatin1().constData());
1424 
1425     if (scan.value.isValid() && scan.state != Invalid) {
1426         if (context != FromString && scan.value < minimum) {
1427             const QLatin1Char space(' ');
1428             if (scan.value >= minimum)
1429                 qWarning("QDateTimeParser::parse Internal error 3 (%ls %ls)",
1430                          qUtf16Printable(scan.value.toString()), qUtf16Printable(minimum.toString()));
1431 
1432             bool done = false;
1433             scan.state = Invalid;
1434             const int sectionNodesCount = sectionNodes.size();
1435             for (int i=0; i<sectionNodesCount && !done; ++i) {
1436                 const SectionNode &sn = sectionNodes.at(i);
1437                 QString t = sectionText(input, i, sn.pos).toLower();
1438                 if ((t.size() < sectionMaxSize(i)
1439                      && (((int)fieldInfo(i) & (FixedWidth|Numeric)) != Numeric))
1440                     || t.contains(space)) {
1441                     switch (sn.type) {
1442                     case AmPmSection:
1443                         switch (findAmPm(t, i)) {
1444                         case AM:
1445                         case PM:
1446                             scan.state = Acceptable;
1447                             done = true;
1448                             break;
1449                         case Neither:
1450                             scan.state = Invalid;
1451                             done = true;
1452                             break;
1453                         case PossibleAM:
1454                         case PossiblePM:
1455                         case PossibleBoth: {
1456                             const QDateTime copy(scan.value.addSecs(12 * 60 * 60));
1457                             if (copy >= minimum && copy <= maximum) {
1458                                 scan.state = Intermediate;
1459                                 done = true;
1460                             }
1461                             break; }
1462                         }
1463                         Q_FALLTHROUGH();
1464                     case MonthSection:
1465                         if (sn.count >= 3) {
1466                             const QDate when = scan.value.date();
1467                             const int finalMonth = when.month(calendar);
1468                             int tmp = finalMonth;
1469                             // I know the first possible month makes the date too early
1470                             while ((tmp = findMonth(t, tmp + 1, i, when.year(calendar))) != -1) {
1471                                 const QDateTime copy(scan.value.addMonths(tmp - finalMonth));
1472                                 if (copy >= minimum && copy <= maximum)
1473                                     break; // break out of while
1474                             }
1475                             if (tmp != -1) {
1476                                 scan.state = Intermediate;
1477                                 done = true;
1478                             }
1479                             break;
1480                         }
1481                         Q_FALLTHROUGH();
1482                     default: {
1483                         int toMin;
1484                         int toMax;
1485 
1486                         if (sn.type & TimeSectionMask) {
1487                             if (scan.value.daysTo(minimum) != 0) {
1488                                 break;
1489                             }
1490                             const QTime time = scan.value.time();
1491                             toMin = time.msecsTo(minimum.time());
1492                             if (scan.value.daysTo(maximum) > 0)
1493                                 toMax = -1; // can't get to max
1494                             else
1495                                 toMax = time.msecsTo(maximum.time());
1496                         } else {
1497                             toMin = scan.value.daysTo(minimum);
1498                             toMax = scan.value.daysTo(maximum);
1499                         }
1500                         const int maxChange = sn.maxChange();
1501                         if (toMin > maxChange) {
1502                             QDTPDEBUG << "invalid because toMin > maxChange" << toMin
1503                                       << maxChange << t << scan.value << minimum;
1504                             scan.state = Invalid;
1505                             done = true;
1506                             break;
1507                         } else if (toMax > maxChange) {
1508                             toMax = -1; // can't get to max
1509                         }
1510 
1511                         const int min = getDigit(minimum, i);
1512                         if (min == -1) {
1513                             qWarning("QDateTimeParser::parse Internal error 4 (%ls)",
1514                                      qUtf16Printable(sn.name()));
1515                             scan.state = Invalid;
1516                             done = true;
1517                             break;
1518                         }
1519 
1520                         int max = toMax != -1 ? getDigit(maximum, i) : absoluteMax(i, scan.value);
1521                         int pos = position + scan.padded - sn.pos;
1522                         if (pos < 0 || pos >= t.size())
1523                             pos = -1;
1524                         if (!potentialValue(t.simplified(), min, max, i, scan.value, pos)) {
1525                             QDTPDEBUG << "invalid because potentialValue(" << t.simplified() << min << max
1526                                       << sn.name() << "returned" << toMax << toMin << pos;
1527                             scan.state = Invalid;
1528                             done = true;
1529                             break;
1530                         }
1531                         scan.state = Intermediate;
1532                         done = true;
1533                         break; }
1534                     }
1535                 }
1536             }
1537         } else {
1538             if (context == FromString) {
1539                 // optimization
1540                 Q_ASSERT(maximum.date().toJulianDay() == 5373484);
1541                 if (scan.value.date().toJulianDay() > 5373484)
1542                     scan.state = Invalid;
1543             } else {
1544                 if (scan.value > maximum)
1545                     scan.state = Invalid;
1546             }
1547 
1548             QDTPDEBUG << "not checking intermediate because scanned value is" << scan.value << minimum << maximum;
1549         }
1550     }
1551     text = scan.input = input;
1552 
1553     /*
1554         We might have ended up with an invalid datetime: the non-existent hour
1555         during dst changes, for instance.
1556     */
1557     if (!scan.value.isValid() && scan.state == Acceptable)
1558         scan.state = Intermediate;
1559 
1560     return scan;
1561 }
1562 
1563 /*
1564   \internal
1565   \brief Returns the index in \a entries with the best prefix match to \a text
1566 
1567   Scans \a entries looking for an entry overlapping \a text as much as possible
1568   (an exact match beats any prefix match; a match of the full entry as prefix of
1569   text beats any entry but one matching a longer prefix; otherwise, the match of
1570   longest prefix wins, earlier entries beating later on a draw).  Records the
1571   length of overlap in *used (if \a used is non-NULL) and the first entry that
1572   overlapped this much in *usedText (if \a usedText is non-NULL).
1573  */
findTextEntry(const QString & text,const ShortVector<QString> & entries,QString * usedText,int * used)1574 static int findTextEntry(const QString &text, const ShortVector<QString> &entries, QString *usedText, int *used)
1575 {
1576     if (text.isEmpty())
1577         return -1;
1578 
1579     int bestMatch = -1;
1580     int bestCount = 0;
1581     for (int n = 0; n < entries.size(); ++n)
1582     {
1583         const QString &name = entries.at(n);
1584 
1585         const int limit = qMin(text.size(), name.size());
1586         int i = 0;
1587         while (i < limit && text.at(i) == name.at(i).toLower())
1588             ++i;
1589         // Full match beats an equal prefix match:
1590         if (i > bestCount || (i == bestCount && i == name.size())) {
1591             bestCount = i;
1592             bestMatch = n;
1593             if (i == name.size() && i == text.size())
1594                 break; // Exact match, name == text, wins.
1595         }
1596     }
1597     if (usedText && bestMatch != -1)
1598         *usedText = entries.at(bestMatch);
1599     if (used)
1600         *used = bestCount;
1601 
1602     return bestMatch;
1603 }
1604 
1605 /*!
1606   \internal
1607   finds the first possible monthname that \a str1 can
1608   match. Starting from \a index; str should already by lowered
1609 */
1610 
findMonth(const QString & str1,int startMonth,int sectionIndex,int year,QString * usedMonth,int * used) const1611 int QDateTimeParser::findMonth(const QString &str1, int startMonth, int sectionIndex,
1612                                int year, QString *usedMonth, int *used) const
1613 {
1614     const SectionNode &sn = sectionNode(sectionIndex);
1615     if (sn.type != MonthSection) {
1616         qWarning("QDateTimeParser::findMonth Internal error");
1617         return -1;
1618     }
1619 
1620     QLocale::FormatType type = sn.count == 3 ? QLocale::ShortFormat : QLocale::LongFormat;
1621     QLocale l = locale();
1622     ShortVector<QString> monthNames;
1623     monthNames.reserve(13 - startMonth);
1624     for (int month = startMonth; month <= 12; ++month)
1625         monthNames.append(calendar.monthName(l, month, year, type));
1626 
1627     const int index = findTextEntry(str1, monthNames, usedMonth, used);
1628     return index < 0 ? index : index + startMonth;
1629 }
1630 
findDay(const QString & str1,int startDay,int sectionIndex,QString * usedDay,int * used) const1631 int QDateTimeParser::findDay(const QString &str1, int startDay, int sectionIndex, QString *usedDay, int *used) const
1632 {
1633     const SectionNode &sn = sectionNode(sectionIndex);
1634     if (!(sn.type & DaySectionMask)) {
1635         qWarning("QDateTimeParser::findDay Internal error");
1636         return -1;
1637     }
1638 
1639     QLocale::FormatType type = sn.count == 4 ? QLocale::LongFormat : QLocale::ShortFormat;
1640     QLocale l = locale();
1641     ShortVector<QString> daysOfWeek;
1642     daysOfWeek.reserve(8 - startDay);
1643     for (int day = startDay; day <= 7; ++day)
1644         daysOfWeek.append(l.dayName(day, type));
1645 
1646     const int index = findTextEntry(str1, daysOfWeek, usedDay, used);
1647     return index < 0 ? index : index + startDay;
1648 }
1649 
1650 /*!
1651   \internal
1652 
1653   Return's .value is UTC offset in seconds.
1654   The caller must verify that the offset is within a valid range.
1655  */
findUtcOffset(QStringRef str) const1656 QDateTimeParser::ParsedSection QDateTimeParser::findUtcOffset(QStringRef str) const
1657 {
1658     const bool startsWithUtc = str.startsWith(QLatin1String("UTC"));
1659     // Get rid of UTC prefix if it exists
1660     if (startsWithUtc)
1661         str = str.mid(3);
1662 
1663     const bool negativeSign = str.startsWith(QLatin1Char('-'));
1664     // Must start with a sign:
1665     if (!negativeSign && !str.startsWith(QLatin1Char('+')))
1666         return ParsedSection();
1667     str = str.mid(1);  // drop sign
1668 
1669     const int colonPosition = str.indexOf(QLatin1Char(':'));
1670     // Colon that belongs to offset is at most at position 2 (hh:mm)
1671     bool hasColon = (colonPosition >= 0 && colonPosition < 3);
1672 
1673     // We deal only with digits at this point (except ':'), so collect them
1674     const int digits = hasColon ? colonPosition + 3 : 4;
1675     int i = 0;
1676     for (const int offsetLength = qMin(digits, str.size()); i < offsetLength; ++i) {
1677         if (i != colonPosition && !str.at(i).isDigit())
1678             break;
1679     }
1680     const int hoursLength = qMin(i, hasColon ? colonPosition : 2);
1681     if (hoursLength < 1)
1682         return ParsedSection();
1683     // Field either ends with hours or also has two digits of minutes
1684     if (i < digits) {
1685         // Only allow single-digit hours with UTC prefix or :mm suffix
1686         if (!startsWithUtc && hoursLength != 2)
1687             return ParsedSection();
1688         i = hoursLength;
1689         hasColon = false;
1690     }
1691     str.truncate(i);  // The rest of the string is not part of the UTC offset
1692 
1693     bool isInt = false;
1694     const int hours = str.mid(0, hoursLength).toInt(&isInt);
1695     if (!isInt)
1696         return ParsedSection();
1697     const QStringRef minutesStr = str.mid(hasColon ? colonPosition + 1 : 2, 2);
1698     const int minutes = minutesStr.isEmpty() ? 0 : minutesStr.toInt(&isInt);
1699     if (!isInt)
1700         return ParsedSection();
1701 
1702     // Keep in sync with QTimeZone::maxUtcOffset hours (14 at most). Also, user
1703     // could be in the middle of updating the offset (e.g. UTC+14:23) which is
1704     // an intermediate state
1705     const State status = (hours > 14 || minutes >= 60) ? Invalid
1706                          : (hours == 14 && minutes > 0) ? Intermediate : Acceptable;
1707 
1708     int offset = 3600 * hours + 60 * minutes;
1709     if (negativeSign)
1710         offset = -offset;
1711 
1712     // Used: UTC, sign, hours, colon, minutes
1713     const int usedSymbols = (startsWithUtc ? 3 : 0) + 1 + hoursLength + (hasColon ? 1 : 0)
1714                             + minutesStr.size();
1715 
1716     return ParsedSection(status, offset, usedSymbols);
1717 }
1718 
1719 /*!
1720   \internal
1721 
1722   Return's .value is zone's offset, zone time - UTC time, in seconds.
1723   The caller must verify that the offset is within a valid range.
1724   See QTimeZonePrivate::isValidId() for the format of zone names.
1725  */
1726 QDateTimeParser::ParsedSection
findTimeZoneName(QStringRef str,const QDateTime & when) const1727 QDateTimeParser::findTimeZoneName(QStringRef str, const QDateTime &when) const
1728 {
1729     const int systemLength = startsWithLocalTimeZone(str);
1730 #if QT_CONFIG(timezone)
1731     // Collect up plausibly-valid characters; let QTimeZone work out what's
1732     // truly valid.
1733     const auto invalidZoneNameCharacter = [] (const QChar &c) {
1734         return c.unicode() >= 127u
1735                || (!c.isLetterOrNumber() && !QLatin1String("+-./:_").contains(c));
1736     };
1737     int index = std::distance(str.cbegin(),
1738                               std::find_if(str.cbegin(), str.cend(), invalidZoneNameCharacter));
1739 
1740     for (; index > systemLength; --index) {  // Find longest match
1741         str.truncate(index);
1742         QTimeZone zone(str.toLatin1());
1743         if (zone.isValid())
1744             return ParsedSection(Acceptable, zone.offsetFromUtc(when), index);
1745     }
1746 #endif
1747     if (systemLength > 0)  // won't actually use the offset, but need it to be valid
1748         return ParsedSection(Acceptable, when.toLocalTime().offsetFromUtc(), systemLength);
1749     return ParsedSection();
1750 }
1751 
1752 /*!
1753   \internal
1754 
1755   Return's .value is zone's offset, zone time - UTC time, in seconds.
1756   See QTimeZonePrivate::isValidId() for the format of zone names.
1757  */
1758 QDateTimeParser::ParsedSection
findTimeZone(QStringRef str,const QDateTime & when,int maxVal,int minVal) const1759 QDateTimeParser::findTimeZone(QStringRef str, const QDateTime &when,
1760                               int maxVal, int minVal) const
1761 {
1762     ParsedSection section = findUtcOffset(str);
1763     if (section.used <= 0)  // if nothing used, try time zone parsing
1764         section = findTimeZoneName(str, when);
1765     // It can be a well formed time zone specifier, but with value out of range
1766     if (section.state == Acceptable && (section.value < minVal || section.value > maxVal))
1767         section.state = Intermediate;
1768     if (section.used > 0)
1769         return section;
1770 
1771     // Check if string is UTC or alias to UTC, after all other options
1772     if (str.startsWith(QLatin1String("UTC")))
1773         return ParsedSection(Acceptable, 0, 3);
1774     if (str.startsWith(QLatin1Char('Z')))
1775         return ParsedSection(Acceptable, 0, 1);
1776 
1777     return ParsedSection();
1778 }
1779 
1780 /*!
1781   \internal
1782 
1783   Compares str to the am/pm texts returned by getAmPmText().
1784   Returns AM or PM if str is one of those texts. Failing that, it looks to see
1785   whether, ignoring spaces and case, each character of str appears in one of
1786   the am/pm texts.
1787   If neither text can be the result of the user typing more into str, returns
1788   Neither. If both texts are possible results of further typing, returns
1789   PossibleBoth. Otherwise, only one of them is a possible completion, so this
1790   returns PossibleAM or PossiblePM to indicate which.
1791 
1792   \sa getAmPmText()
1793 */
findAmPm(QString & str,int sectionIndex,int * used) const1794 QDateTimeParser::AmPmFinder QDateTimeParser::findAmPm(QString &str, int sectionIndex, int *used) const
1795 {
1796     const SectionNode &s = sectionNode(sectionIndex);
1797     if (s.type != AmPmSection) {
1798         qWarning("QDateTimeParser::findAmPm Internal error");
1799         return Neither;
1800     }
1801     if (used)
1802         *used = str.size();
1803     if (QStringRef(&str).trimmed().isEmpty()) {
1804         return PossibleBoth;
1805     }
1806     const QLatin1Char space(' ');
1807     int size = sectionMaxSize(sectionIndex);
1808 
1809     enum {
1810         amindex = 0,
1811         pmindex = 1
1812     };
1813     QString ampm[2];
1814     ampm[amindex] = getAmPmText(AmText, s.count == 1 ? UpperCase : LowerCase);
1815     ampm[pmindex] = getAmPmText(PmText, s.count == 1 ? UpperCase : LowerCase);
1816     for (int i=0; i<2; ++i)
1817         ampm[i].truncate(size);
1818 
1819     QDTPDEBUG << "findAmPm" << str << ampm[0] << ampm[1];
1820 
1821     if (str.startsWith(ampm[amindex], Qt::CaseInsensitive)) {
1822         str = ampm[amindex];
1823         return AM;
1824     } else if (str.startsWith(ampm[pmindex], Qt::CaseInsensitive)) {
1825         str = ampm[pmindex];
1826         return PM;
1827     } else if (context == FromString || (str.count(space) == 0 && str.size() >= size)) {
1828         return Neither;
1829     }
1830     size = qMin(size, str.size());
1831 
1832     bool broken[2] = {false, false};
1833     for (int i=0; i<size; ++i) {
1834         if (str.at(i) != space) {
1835             for (int j=0; j<2; ++j) {
1836                 if (!broken[j]) {
1837                     int index = ampm[j].indexOf(str.at(i));
1838                     QDTPDEBUG << "looking for" << str.at(i)
1839                               << "in" << ampm[j] << "and got" << index;
1840                     if (index == -1) {
1841                         if (str.at(i).category() == QChar::Letter_Uppercase) {
1842                             index = ampm[j].indexOf(str.at(i).toLower());
1843                             QDTPDEBUG << "trying with" << str.at(i).toLower()
1844                                       << "in" << ampm[j] << "and got" << index;
1845                         } else if (str.at(i).category() == QChar::Letter_Lowercase) {
1846                             index = ampm[j].indexOf(str.at(i).toUpper());
1847                             QDTPDEBUG << "trying with" << str.at(i).toUpper()
1848                                       << "in" << ampm[j] << "and got" << index;
1849                         }
1850                         if (index == -1) {
1851                             broken[j] = true;
1852                             if (broken[amindex] && broken[pmindex]) {
1853                                 QDTPDEBUG << str << "didn't make it";
1854                                 return Neither;
1855                             }
1856                             continue;
1857                         } else {
1858                             str[i] = ampm[j].at(index); // fix case
1859                         }
1860                     }
1861                     ampm[j].remove(index, 1);
1862                 }
1863             }
1864         }
1865     }
1866     if (!broken[pmindex] && !broken[amindex])
1867         return PossibleBoth;
1868     return (!broken[amindex] ? PossibleAM : PossiblePM);
1869 }
1870 #endif // datestring
1871 
1872 /*!
1873   \internal
1874   Max number of units that can be changed by this section.
1875 */
1876 
maxChange() const1877 int QDateTimeParser::SectionNode::maxChange() const
1878 {
1879     switch (type) {
1880         // Time. unit is msec
1881     case MSecSection: return 999;
1882     case SecondSection: return 59 * 1000;
1883     case MinuteSection: return 59 * 60 * 1000;
1884     case Hour24Section: case Hour12Section: return 59 * 60 * 60 * 1000;
1885 
1886         // Date. unit is day
1887     case DayOfWeekSectionShort:
1888     case DayOfWeekSectionLong: return 7;
1889     case DaySection: return 30;
1890     case MonthSection: return 365 - 31;
1891     case YearSection: return 9999 * 365;
1892     case YearSection2Digits: return 100 * 365;
1893     default:
1894         qWarning("QDateTimeParser::maxChange() Internal error (%ls)",
1895                  qUtf16Printable(name()));
1896     }
1897 
1898     return -1;
1899 }
1900 
fieldInfo(int index) const1901 QDateTimeParser::FieldInfo QDateTimeParser::fieldInfo(int index) const
1902 {
1903     FieldInfo ret;
1904     const SectionNode &sn = sectionNode(index);
1905     switch (sn.type) {
1906     case MSecSection:
1907         ret |= Fraction;
1908         Q_FALLTHROUGH();
1909     case SecondSection:
1910     case MinuteSection:
1911     case Hour24Section:
1912     case Hour12Section:
1913     case YearSection2Digits:
1914         ret |= AllowPartial;
1915         Q_FALLTHROUGH();
1916     case YearSection:
1917         ret |= Numeric;
1918         if (sn.count != 1)
1919             ret |= FixedWidth;
1920         break;
1921     case MonthSection:
1922     case DaySection:
1923         switch (sn.count) {
1924         case 2:
1925             ret |= FixedWidth;
1926             Q_FALLTHROUGH();
1927         case 1:
1928             ret |= (Numeric|AllowPartial);
1929             break;
1930         }
1931         break;
1932     case DayOfWeekSectionShort:
1933     case DayOfWeekSectionLong:
1934         if (sn.count == 3)
1935             ret |= FixedWidth;
1936         break;
1937     case AmPmSection:
1938         ret |= FixedWidth;
1939         break;
1940     case TimeZoneSection:
1941         break;
1942     default:
1943         qWarning("QDateTimeParser::fieldInfo Internal error 2 (%d %ls %d)",
1944                  index, qUtf16Printable(sn.name()), sn.count);
1945         break;
1946     }
1947     return ret;
1948 }
1949 
format() const1950 QString QDateTimeParser::SectionNode::format() const
1951 {
1952     QChar fillChar;
1953     switch (type) {
1954     case AmPmSection: return count == 1 ? QLatin1String("AP") : QLatin1String("ap");
1955     case MSecSection: fillChar = QLatin1Char('z'); break;
1956     case SecondSection: fillChar = QLatin1Char('s'); break;
1957     case MinuteSection: fillChar = QLatin1Char('m'); break;
1958     case Hour24Section: fillChar = QLatin1Char('H'); break;
1959     case Hour12Section: fillChar = QLatin1Char('h'); break;
1960     case DayOfWeekSectionShort:
1961     case DayOfWeekSectionLong:
1962     case DaySection: fillChar = QLatin1Char('d'); break;
1963     case MonthSection: fillChar = QLatin1Char('M'); break;
1964     case YearSection2Digits:
1965     case YearSection: fillChar = QLatin1Char('y'); break;
1966     default:
1967         qWarning("QDateTimeParser::sectionFormat Internal error (%ls)",
1968                  qUtf16Printable(name(type)));
1969         return QString();
1970     }
1971     if (fillChar.isNull()) {
1972         qWarning("QDateTimeParser::sectionFormat Internal error 2");
1973         return QString();
1974     }
1975     return QString(count, fillChar);
1976 }
1977 
1978 
1979 /*!
1980   \internal
1981 
1982   Returns \c true if str can be modified to represent a
1983   number that is within min and max.
1984 */
1985 
potentialValue(const QStringRef & str,int min,int max,int index,const QDateTime & currentValue,int insert) const1986 bool QDateTimeParser::potentialValue(const QStringRef &str, int min, int max, int index,
1987                                      const QDateTime &currentValue, int insert) const
1988 {
1989     if (str.isEmpty()) {
1990         return true;
1991     }
1992     const int size = sectionMaxSize(index);
1993     int val = (int)locale().toUInt(str);
1994     const SectionNode &sn = sectionNode(index);
1995     if (sn.type == YearSection2Digits) {
1996         const int year = currentValue.date().year(calendar);
1997         val += year - (year % 100);
1998     }
1999     if (val >= min && val <= max && str.size() == size) {
2000         return true;
2001     } else if (val > max) {
2002         return false;
2003     } else if (str.size() == size && val < min) {
2004         return false;
2005     }
2006 
2007     const int len = size - str.size();
2008     for (int i=0; i<len; ++i) {
2009         for (int j=0; j<10; ++j) {
2010             if (potentialValue(str + QLatin1Char('0' + j), min, max, index, currentValue, insert)) {
2011                 return true;
2012             } else if (insert >= 0) {
2013                 const QString tmp = str.left(insert) + QLatin1Char('0' + j) + str.mid(insert);
2014                 if (potentialValue(tmp, min, max, index, currentValue, insert))
2015                     return true;
2016             }
2017         }
2018     }
2019 
2020     return false;
2021 }
2022 
2023 /*!
2024   \internal
2025 */
skipToNextSection(int index,const QDateTime & current,const QStringRef & text) const2026 bool QDateTimeParser::skipToNextSection(int index, const QDateTime &current, const QStringRef &text) const
2027 {
2028     Q_ASSERT(text.size() < sectionMaxSize(index));
2029     const SectionNode &node = sectionNode(index);
2030     int min = absoluteMin(index);
2031     int max = absoluteMax(index, current);
2032     // Time-zone field is only numeric if given as offset from UTC:
2033     if (node.type != TimeZoneSection || current.timeSpec() == Qt::OffsetFromUTC) {
2034         const QDateTime maximum = getMaximum();
2035         const QDateTime minimum = getMinimum();
2036         Q_ASSERT(current >= minimum && current <= maximum);
2037 
2038         QDateTime tmp = current;
2039         if (!setDigit(tmp, index, min) || tmp < minimum)
2040             min = getDigit(minimum, index);
2041 
2042         if (!setDigit(tmp, index, max) || tmp > maximum)
2043             max = getDigit(maximum, index);
2044     }
2045     int pos = cursorPosition() - node.pos;
2046     if (pos < 0 || pos >= text.size())
2047         pos = -1;
2048 
2049     /*
2050       If the value potentially can become another valid entry we don't want to
2051       skip to the next. E.g. In a M field (month without leading 0) if you type
2052       1 we don't want to autoskip (there might be [012] following) but if you
2053       type 3 we do.
2054     */
2055     return !potentialValue(text, min, max, index, current, pos);
2056 }
2057 
2058 /*!
2059   \internal
2060   For debugging. Returns the name of the section \a s.
2061 */
2062 
name(QDateTimeParser::Section s)2063 QString QDateTimeParser::SectionNode::name(QDateTimeParser::Section s)
2064 {
2065     switch (s) {
2066     case QDateTimeParser::AmPmSection: return QLatin1String("AmPmSection");
2067     case QDateTimeParser::DaySection: return QLatin1String("DaySection");
2068     case QDateTimeParser::DayOfWeekSectionShort: return QLatin1String("DayOfWeekSectionShort");
2069     case QDateTimeParser::DayOfWeekSectionLong: return QLatin1String("DayOfWeekSectionLong");
2070     case QDateTimeParser::Hour24Section: return QLatin1String("Hour24Section");
2071     case QDateTimeParser::Hour12Section: return QLatin1String("Hour12Section");
2072     case QDateTimeParser::MSecSection: return QLatin1String("MSecSection");
2073     case QDateTimeParser::MinuteSection: return QLatin1String("MinuteSection");
2074     case QDateTimeParser::MonthSection: return QLatin1String("MonthSection");
2075     case QDateTimeParser::SecondSection: return QLatin1String("SecondSection");
2076     case QDateTimeParser::TimeZoneSection: return QLatin1String("TimeZoneSection");
2077     case QDateTimeParser::YearSection: return QLatin1String("YearSection");
2078     case QDateTimeParser::YearSection2Digits: return QLatin1String("YearSection2Digits");
2079     case QDateTimeParser::NoSection: return QLatin1String("NoSection");
2080     case QDateTimeParser::FirstSection: return QLatin1String("FirstSection");
2081     case QDateTimeParser::LastSection: return QLatin1String("LastSection");
2082     default: return QLatin1String("Unknown section ") + QString::number(int(s));
2083     }
2084 }
2085 
2086 /*!
2087   \internal
2088   For debugging. Returns the name of the state \a s.
2089 */
2090 
stateName(State s) const2091 QString QDateTimeParser::stateName(State s) const
2092 {
2093     switch (s) {
2094     case Invalid: return QLatin1String("Invalid");
2095     case Intermediate: return QLatin1String("Intermediate");
2096     case Acceptable: return QLatin1String("Acceptable");
2097     default: return QLatin1String("Unknown state ") + QString::number(s);
2098     }
2099 }
2100 
2101 #if QT_CONFIG(datestring)
fromString(const QString & t,QDate * date,QTime * time) const2102 bool QDateTimeParser::fromString(const QString &t, QDate *date, QTime *time) const
2103 {
2104     QDateTime datetime;
2105     if (!fromString(t, &datetime))
2106         return false;
2107 
2108     if (time) {
2109         const QTime t = datetime.time();
2110         if (!t.isValid()) {
2111             return false;
2112         }
2113         *time = t;
2114     }
2115 
2116     if (date) {
2117         const QDate d = datetime.date();
2118         if (!d.isValid()) {
2119             return false;
2120         }
2121         *date = d;
2122     }
2123     return true;
2124 }
2125 
fromString(const QString & t,QDateTime * datetime) const2126 bool QDateTimeParser::fromString(const QString &t, QDateTime* datetime) const
2127 {
2128     QDateTime val(QDate(1900, 1, 1).startOfDay());
2129     const StateNode tmp = parse(t, -1, val, false);
2130     if (tmp.state != Acceptable || tmp.conflicts)
2131         return false;
2132     if (datetime) {
2133         if (!tmp.value.isValid())
2134             return false;
2135         *datetime = tmp.value;
2136     }
2137 
2138     return true;
2139 }
2140 #endif // datestring
2141 
getMinimum() const2142 QDateTime QDateTimeParser::getMinimum() const
2143 {
2144     // NB: QDateTimeParser always uses Qt::LocalTime time spec by default. If
2145     //     any subclass needs a changing time spec, it must override this
2146     //     method. At the time of writing, this is done by QDateTimeEditPrivate.
2147 
2148     // Cache the only case
2149     static const QDateTime localTimeMin(QDATETIMEEDIT_DATE_MIN.startOfDay(Qt::LocalTime));
2150     return localTimeMin;
2151 }
2152 
getMaximum() const2153 QDateTime QDateTimeParser::getMaximum() const
2154 {
2155     // NB: QDateTimeParser always uses Qt::LocalTime time spec by default. If
2156     //     any subclass needs a changing time spec, it must override this
2157     //     method. At the time of writing, this is done by QDateTimeEditPrivate.
2158 
2159     // Cache the only case
2160     static const QDateTime localTimeMax(QDATETIMEEDIT_DATE_MAX.endOfDay(Qt::LocalTime));
2161     return localTimeMax;
2162 }
2163 
getAmPmText(AmPm ap,Case cs) const2164 QString QDateTimeParser::getAmPmText(AmPm ap, Case cs) const
2165 {
2166     const QLocale loc = locale();
2167     QString raw = ap == AmText ? loc.amText() : loc.pmText();
2168     return cs == UpperCase ? raw.toUpper() : raw.toLower();
2169 }
2170 
2171 /*
2172   \internal
2173 
2174   I give arg2 preference because arg1 is always a QDateTime.
2175 */
2176 
operator ==(const QDateTimeParser::SectionNode & s1,const QDateTimeParser::SectionNode & s2)2177 bool operator==(const QDateTimeParser::SectionNode &s1, const QDateTimeParser::SectionNode &s2)
2178 {
2179     return (s1.type == s2.type) && (s1.pos == s2.pos) && (s1.count == s2.count);
2180 }
2181 
2182 /*!
2183   Sets \a cal as the calendar to use. The default is Gregorian.
2184 */
2185 
setCalendar(const QCalendar & cal)2186 void QDateTimeParser::setCalendar(const QCalendar &cal)
2187 {
2188     calendar = cal;
2189 }
2190 
2191 QT_END_NAMESPACE
2192