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 ¤tValue, 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, §iontext, &used);
811 } else {
812 num = findDay(sectiontext.toLower(), 1, sectionIndex, §iontext, &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 = &m; 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 ¤tValue, 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 ¤t, 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