/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qplatformdefs.h" #include "private/qdatetimeparser_p.h" #include "qdatastream.h" #include "qset.h" #include "qlocale.h" #include "qdatetime.h" #include "qregexp.h" #include "qdebug.h" //#define QDATETIMEPARSER_DEBUG #if defined (QDATETIMEPARSER_DEBUG) && !defined(QT_NO_DEBUG_STREAM) # define QDTPDEBUG qDebug() # define QDTPDEBUGN qDebug #else # define QDTPDEBUG if (false) qDebug() # define QDTPDEBUGN if (false) qDebug #endif QT_BEGIN_NAMESPACE #ifndef QT_BOOTSTRAPPED QDateTimeParser::~QDateTimeParser() { } /*! \internal Gets the digit from a datetime. E.g. QDateTime var(QDate(2004, 02, 02)); int digit = getDigit(var, Year); // digit = 2004 */ int QDateTimeParser::getDigit(const QDateTime &t, int index) const { if (index < 0 || index >= sectionNodes.size()) { #ifndef QT_NO_DATESTRING qWarning("QDateTimeParser::getDigit() Internal error (%s %d)", qPrintable(t.toString()), index); #else qWarning("QDateTimeParser::getDigit() Internal error (%d)", index); #endif return -1; } const SectionNode &node = sectionNodes.at(index); switch (node.type) { case Hour24Section: case Hour12Section: return t.time().hour(); case MinuteSection: return t.time().minute(); case SecondSection: return t.time().second(); case MSecSection: return t.time().msec(); case YearSection2Digits: case YearSection: return t.date().year(); case MonthSection: return t.date().month(); case DaySection: return t.date().day(); case DayOfWeekSectionShort: case DayOfWeekSectionLong: return t.date().day(); case AmPmSection: return t.time().hour() > 11 ? 1 : 0; default: break; } #ifndef QT_NO_DATESTRING qWarning("QDateTimeParser::getDigit() Internal error 2 (%s %d)", qPrintable(t.toString()), index); #else qWarning("QDateTimeParser::getDigit() Internal error 2 (%d)", index); #endif return -1; } /*! \internal Sets a digit in a datetime. E.g. QDateTime var(QDate(2004, 02, 02)); int digit = getDigit(var, Year); // digit = 2004 setDigit(&var, Year, 2005); digit = getDigit(var, Year); // digit = 2005 */ bool QDateTimeParser::setDigit(QDateTime &v, int index, int newVal) const { if (index < 0 || index >= sectionNodes.size()) { #ifndef QT_NO_DATESTRING qWarning("QDateTimeParser::setDigit() Internal error (%s %d %d)", qPrintable(v.toString()), index, newVal); #else qWarning("QDateTimeParser::setDigit() Internal error (%d %d)", index, newVal); #endif return false; } const SectionNode &node = sectionNodes.at(index); const QDate date = v.date(); const QTime time = v.time(); int year = date.year(); int month = date.month(); int day = date.day(); int hour = time.hour(); int minute = time.minute(); int second = time.second(); int msec = time.msec(); switch (node.type) { case Hour24Section: case Hour12Section: hour = newVal; break; case MinuteSection: minute = newVal; break; case SecondSection: second = newVal; break; case MSecSection: msec = newVal; break; case YearSection2Digits: case YearSection: year = newVal; break; case MonthSection: month = newVal; break; case DaySection: case DayOfWeekSectionShort: case DayOfWeekSectionLong: if (newVal > 31) { // have to keep legacy behavior. setting the // date to 32 should return false. Setting it // to 31 for february should return true return false; } day = newVal; break; case AmPmSection: hour = (newVal == 0 ? hour % 12 : (hour % 12) + 12); break; default: qWarning("QDateTimeParser::setDigit() Internal error (%s)", qPrintable(node.name())); break; } if (!(node.type & DaySectionMask)) { if (day < cachedDay) day = cachedDay; const int max = QDate(year, month, 1).daysInMonth(); if (day > max) { day = max; } } if (QDate::isValid(year, month, day) && QTime::isValid(hour, minute, second, msec)) { v = QDateTime(QDate(year, month, day), QTime(hour, minute, second, msec), spec); return true; } return false; } /*! \ Returns the absolute maximum for a section */ int QDateTimeParser::absoluteMax(int s, const QDateTime &cur) const { const SectionNode &sn = sectionNode(s); switch (sn.type) { case Hour24Section: case Hour12Section: return 23; // this is special-cased in // parseSection. We want it to be // 23 for the stepBy case. case MinuteSection: case SecondSection: return 59; case MSecSection: return 999; case YearSection2Digits: case YearSection: return 9999; // sectionMaxSize will prevent // people from typing in a larger // number in count == 2 sections. // stepBy() will work on real years anyway case MonthSection: return 12; case DaySection: case DayOfWeekSectionShort: case DayOfWeekSectionLong: return cur.isValid() ? cur.date().daysInMonth() : 31; case AmPmSection: return 1; default: break; } qWarning("QDateTimeParser::absoluteMax() Internal error (%s)", qPrintable(sn.name())); return -1; } /*! \internal Returns the absolute minimum for a section */ int QDateTimeParser::absoluteMin(int s) const { const SectionNode &sn = sectionNode(s); switch (sn.type) { case Hour24Section: case Hour12Section: case MinuteSection: case SecondSection: case MSecSection: case YearSection2Digits: case YearSection: return 0; case MonthSection: case DaySection: case DayOfWeekSectionShort: case DayOfWeekSectionLong: return 1; case AmPmSection: return 0; default: break; } qWarning("QDateTimeParser::absoluteMin() Internal error (%s, %0x)", qPrintable(sn.name()), sn.type); return -1; } /*! \internal Returns the sectionNode for the Section \a s. */ const QDateTimeParser::SectionNode &QDateTimeParser::sectionNode(int sectionIndex) const { if (sectionIndex < 0) { switch (sectionIndex) { case FirstSectionIndex: return first; case LastSectionIndex: return last; case NoSectionIndex: return none; } } else if (sectionIndex < sectionNodes.size()) { return sectionNodes.at(sectionIndex); } qWarning("QDateTimeParser::sectionNode() Internal error (%d)", sectionIndex); return none; } QDateTimeParser::Section QDateTimeParser::sectionType(int sectionIndex) const { return sectionNode(sectionIndex).type; } /*! \internal Returns the starting position for section \a s. */ int QDateTimeParser::sectionPos(int sectionIndex) const { return sectionPos(sectionNode(sectionIndex)); } int QDateTimeParser::sectionPos(const SectionNode &sn) const { switch (sn.type) { case FirstSection: return 0; case LastSection: return displayText().size() - 1; default: break; } if (sn.pos == -1) { qWarning("QDateTimeParser::sectionPos Internal error (%s)", qPrintable(sn.name())); return -1; } return sn.pos; } /*! \internal helper function for parseFormat. removes quotes that are not escaped and removes the escaping on those that are escaped */ static QString unquote(const QStringRef &str) { const QChar quote(QLatin1Char('\'')); const QChar slash(QLatin1Char('\\')); const QChar zero(QLatin1Char('0')); QString ret; QChar status(zero); const int max = str.size(); for (int i=0; iappend(lastQuote >= from ? unquote(separator) : separator.toString()); } bool QDateTimeParser::parseFormat(const QString &newFormat) { const QLatin1Char quote('\''); const QLatin1Char slash('\\'); const QLatin1Char zero('0'); if (newFormat == displayFormat && !newFormat.isEmpty()) { return true; } QDTPDEBUGN("parseFormat: %s", newFormat.toLatin1().constData()); QVector newSectionNodes; Sections newDisplay = 0; QStringList newSeparators; int i, index = 0; int add = 0; QChar status(zero); const int max = newFormat.size(); int lastQuote = -1; for (i = 0; i 0 && newFormat.at(i - 1) != slash) { status = zero; } } else if (status != quote) { const char sect = newFormat.at(i).toLatin1(); switch (sect) { case 'H': case 'h': if (parserType != QVariant::Date) { const Section hour = (sect == 'h') ? Hour12Section : Hour24Section; const SectionNode sn = { hour, i - add, countRepeat(newFormat, i, 2), 0 }; newSectionNodes.append(sn); appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); i += sn.count - 1; index = i + 1; newDisplay |= hour; } break; case 'm': if (parserType != QVariant::Date) { const SectionNode sn = { MinuteSection, i - add, countRepeat(newFormat, i, 2), 0 }; newSectionNodes.append(sn); appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); i += sn.count - 1; index = i + 1; newDisplay |= MinuteSection; } break; case 's': if (parserType != QVariant::Date) { const SectionNode sn = { SecondSection, i - add, countRepeat(newFormat, i, 2), 0 }; newSectionNodes.append(sn); appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); i += sn.count - 1; index = i + 1; newDisplay |= SecondSection; } break; case 'z': if (parserType != QVariant::Date) { const SectionNode sn = { MSecSection, i - add, countRepeat(newFormat, i, 3) < 3 ? 1 : 3, 0 }; newSectionNodes.append(sn); appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); i += sn.count - 1; index = i + 1; newDisplay |= MSecSection; } break; case 'A': case 'a': if (parserType != QVariant::Date) { const bool cap = (sect == 'A'); const SectionNode sn = { AmPmSection, i - add, (cap ? 1 : 0), 0 }; newSectionNodes.append(sn); appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); newDisplay |= AmPmSection; if (i + 1 < newFormat.size() && newFormat.at(i+1) == (cap ? QLatin1Char('P') : QLatin1Char('p'))) { ++i; } index = i + 1; } break; case 'y': if (parserType != QVariant::Time) { const int repeat = countRepeat(newFormat, i, 4); if (repeat >= 2) { const SectionNode sn = { repeat == 4 ? YearSection : YearSection2Digits, i - add, repeat == 4 ? 4 : 2, 0 }; newSectionNodes.append(sn); appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); i += sn.count - 1; index = i + 1; newDisplay |= sn.type; } } break; case 'M': if (parserType != QVariant::Time) { const SectionNode sn = { MonthSection, i - add, countRepeat(newFormat, i, 4), 0 }; newSectionNodes.append(sn); newSeparators.append(unquote(newFormat.midRef(index, i - index))); i += sn.count - 1; index = i + 1; newDisplay |= MonthSection; } break; case 'd': if (parserType != QVariant::Time) { const int repeat = countRepeat(newFormat, i, 4); const Section sectionType = (repeat == 4 ? DayOfWeekSectionLong : (repeat == 3 ? DayOfWeekSectionShort : DaySection)); const SectionNode sn = { sectionType, i - add, repeat, 0 }; newSectionNodes.append(sn); appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); i += sn.count - 1; index = i + 1; newDisplay |= sn.type; } break; default: break; } } } if (newSectionNodes.isEmpty() && context == DateTimeEdit) { return false; } if ((newDisplay & (AmPmSection|Hour12Section)) == Hour12Section) { const int count = newSectionNodes.size(); for (int i = 0; i < count; ++i) { SectionNode &node = newSectionNodes[i]; if (node.type == Hour12Section) node.type = Hour24Section; } } if (index < max) { appendSeparator(&newSeparators, newFormat, index, index - max, lastQuote); } else { newSeparators.append(QString()); } displayFormat = newFormat; separators = newSeparators; sectionNodes = newSectionNodes; display = newDisplay; last.pos = -1; // for (int i=0; i= sectionNodes.size()) { qWarning("QDateTimeParser::sectionSize Internal error (%d)", sectionIndex); return -1; } if (sectionIndex == sectionNodes.size() - 1) { // In some cases there is a difference between displayText() and text. // e.g. when text is 2000/01/31 and displayText() is "2000/2/31" - text // is the previous value and displayText() is the new value. // The size difference is always due to leading zeroes. int sizeAdjustment = 0; const int displayTextSize = displayText().size(); if (displayTextSize != text.size()) { // Any zeroes added before this section will affect our size. int preceedingZeroesAdded = 0; if (sectionNodes.size() > 1 && context == DateTimeEdit) { const auto begin = sectionNodes.cbegin(); const auto end = begin + sectionIndex; for (auto sectionIt = begin; sectionIt != end; ++sectionIt) preceedingZeroesAdded += sectionIt->zeroesAdded; } sizeAdjustment = preceedingZeroesAdded; } return displayTextSize + sizeAdjustment - sectionPos(sectionIndex) - separators.last().size(); } else { return sectionPos(sectionIndex + 1) - sectionPos(sectionIndex) - separators.at(sectionIndex + 1).size(); } } int QDateTimeParser::sectionMaxSize(Section s, int count) const { #ifndef QT_NO_TEXTDATE int mcount = 12; #endif switch (s) { case FirstSection: case NoSection: case LastSection: return 0; case AmPmSection: { const int lowerMax = qMin(getAmPmText(AmText, LowerCase).size(), getAmPmText(PmText, LowerCase).size()); const int upperMax = qMin(getAmPmText(AmText, UpperCase).size(), getAmPmText(PmText, UpperCase).size()); return qMin(4, qMin(lowerMax, upperMax)); } case Hour24Section: case Hour12Section: case MinuteSection: case SecondSection: case DaySection: return 2; case DayOfWeekSectionShort: case DayOfWeekSectionLong: #ifdef QT_NO_TEXTDATE return 2; #else mcount = 7; Q_FALLTHROUGH(); #endif case MonthSection: #ifdef QT_NO_TEXTDATE return 2; #else if (count <= 2) return 2; { int ret = 0; const QLocale l = locale(); const QLocale::FormatType format = count == 4 ? QLocale::LongFormat : QLocale::ShortFormat; for (int i=1; i<=mcount; ++i) { const QString str = (s == MonthSection ? l.monthName(i, format) : l.dayName(i, format)); ret = qMax(str.size(), ret); } return ret; } #endif case MSecSection: return 3; case YearSection: return 4; case YearSection2Digits: return 2; case CalendarPopupSection: case Internal: case TimeSectionMask: case DateSectionMask: case HourSectionMask: case YearSectionMask: case DayOfWeekSectionMask: case DaySectionMask: qWarning("QDateTimeParser::sectionMaxSize: Invalid section %s", SectionNode::name(s).toLatin1().constData()); case NoSectionIndex: case FirstSectionIndex: case LastSectionIndex: case CalendarPopupIndex: // these cases can't happen break; } return -1; } int QDateTimeParser::sectionMaxSize(int index) const { const SectionNode &sn = sectionNode(index); return sectionMaxSize(sn.type, sn.count); } /*! \internal Returns the text of section \a s. This function operates on the arg text rather than edit->text(). */ QString QDateTimeParser::sectionText(const QString &text, int sectionIndex, int index) const { const SectionNode &sn = sectionNode(sectionIndex); switch (sn.type) { case NoSectionIndex: case FirstSectionIndex: case LastSectionIndex: return QString(); default: break; } return text.mid(index, sectionSize(sectionIndex)); } QString QDateTimeParser::sectionText(int sectionIndex) const { const SectionNode &sn = sectionNode(sectionIndex); return sectionText(displayText(), sectionIndex, sn.pos); } #ifndef QT_NO_TEXTDATE /*! \internal:skipToNextSection Parses the part of \a text that corresponds to \a s and returns the value of that field. Sets *stateptr to the right state if stateptr != 0. */ int QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, QString &text, int &cursorPosition, int index, State &state, int *usedptr) const { state = Invalid; int num = 0; const SectionNode &sn = sectionNode(sectionIndex); if ((sn.type & Internal) == Internal) { qWarning("QDateTimeParser::parseSection Internal error (%s %d)", qPrintable(sn.name()), sectionIndex); return -1; } const int sectionmaxsize = sectionMaxSize(sectionIndex); QStringRef sectionTextRef = text.midRef(index, sectionmaxsize); int sectiontextSize = sectionTextRef.size(); QDTPDEBUG << "sectionValue for" << sn.name() << "with text" << text << "and st" << sectionTextRef << text.midRef(index, sectionmaxsize) << index; int used = 0; switch (sn.type) { case AmPmSection: { QString sectiontext = sectionTextRef.toString(); const int ampm = findAmPm(sectiontext, sectionIndex, &used); switch (ampm) { case AM: // sectiontext == AM case PM: // sectiontext == PM num = ampm; state = Acceptable; break; case PossibleAM: // sectiontext => AM case PossiblePM: // sectiontext => PM num = ampm - 2; state = Intermediate; break; case PossibleBoth: // sectiontext => AM|PM num = 0; state = Intermediate; break; case Neither: state = Invalid; QDTPDEBUG << "invalid because findAmPm(" << sectiontext << ") returned -1"; break; default: QDTPDEBUGN("This should never happen (findAmPm returned %d)", ampm); break; } if (state != Invalid) text.replace(index, used, sectiontext.constData(), used); break; } case MonthSection: case DayOfWeekSectionShort: case DayOfWeekSectionLong: if (sn.count >= 3) { QString sectiontext = sectionTextRef.toString(); if (sn.type == MonthSection) { int min = 1; const QDate minDate = getMinimum().date(); if (currentValue.date().year() == minDate.year()) { min = minDate.month(); } num = findMonth(sectiontext.toLower(), min, sectionIndex, §iontext, &used); } else { num = findDay(sectiontext.toLower(), 1, sectionIndex, §iontext, &used); } if (num != -1) { state = (used == sectiontext.size() ? Acceptable : Intermediate); text.replace(index, used, sectiontext.constData(), used); } else { state = Intermediate; } break; } Q_FALLTHROUGH(); case DaySection: case YearSection: case YearSection2Digits: case Hour12Section: case Hour24Section: case MinuteSection: case SecondSection: case MSecSection: { if (sectiontextSize == 0) { num = 0; used = 0; state = Intermediate; } else { const int absMax = absoluteMax(sectionIndex); QLocale loc; bool ok = true; int last = -1; used = -1; QStringRef digitsStr = sectionTextRef; for (int i = 0; i < sectiontextSize; ++i) { if (digitsStr.at(i).isSpace()) { sectiontextSize = i; break; } } const int max = qMin(sectionmaxsize, sectiontextSize); for (int digits = max; digits >= 1; --digits) { digitsStr.truncate(digits); int tmp = (int)loc.toUInt(digitsStr, &ok); if (ok && sn.type == Hour12Section) { if (tmp > 12) { tmp = -1; ok = false; } else if (tmp == 12) { tmp = 0; } } if (ok && tmp <= absMax) { QDTPDEBUG << sectionTextRef.left(digits) << tmp << digits; last = tmp; used = digits; break; } } if (last == -1) { QChar first(sectionTextRef.at(0)); if (separators.at(sectionIndex + 1).startsWith(first)) { used = 0; state = Intermediate; } else { state = Invalid; QDTPDEBUG << "invalid because" << sectionTextRef << "can't become a uint" << last << ok; } } else { num += last; const FieldInfo fi = fieldInfo(sectionIndex); const bool done = (used == sectionmaxsize); if (!done && fi & Fraction) { // typing 2 in a zzz field should be .200, not .002 for (int i=used; i absMax) { state = Intermediate; } else if (!done && (fi & (FixedWidth|Numeric)) == (FixedWidth|Numeric)) { if (skipToNextSection(sectionIndex, currentValue, digitsStr)) { state = Acceptable; const int missingZeroes = sectionmaxsize - digitsStr.size(); text.insert(index, QString(missingZeroes, QLatin1Char('0'))); used = sectionmaxsize; cursorPosition += missingZeroes; ++(const_cast(this)->sectionNodes[sectionIndex].zeroesAdded); } else { state = Intermediate;; } } else { state = Acceptable; } } } break; } default: qWarning("QDateTimeParser::parseSection Internal error (%s %d)", qPrintable(sn.name()), sectionIndex); return -1; } if (usedptr) *usedptr = used; return (state != Invalid ? num : -1); } #endif // QT_NO_TEXTDATE #ifndef QT_NO_DATESTRING /*! \internal */ QDateTimeParser::StateNode QDateTimeParser::parse(QString &input, int &cursorPosition, const QDateTime ¤tValue, bool fixup) const { const QDateTime minimum = getMinimum(); const QDateTime maximum = getMaximum(); State state = Acceptable; QDateTime newCurrentValue; bool conflicts = false; const int sectionNodesCount = sectionNodes.size(); QDTPDEBUG << "parse" << input; { int pos = 0; int year, month, day; const QDate currentDate = currentValue.date(); const QTime currentTime = currentValue.time(); currentDate.getDate(&year, &month, &day); int year2digits = year % 100; int hour = currentTime.hour(); int hour12 = -1; int minute = currentTime.minute(); int second = currentTime.second(); int msec = currentTime.msec(); int dayofweek = currentDate.dayOfWeek(); int ampm = -1; Sections isSet = NoSection; int num; State tmpstate; for (int index=0; state != Invalid && index(state, tmpstate); if (state == Intermediate && context == FromString) { state = Invalid; break; } QDTPDEBUG << index << sn.name() << "is set to" << pos << "state is" << stateName(state); if (state != Invalid) { switch (sn.type) { case Hour24Section: current = &hour; break; case Hour12Section: current = &hour12; break; case MinuteSection: current = &minute; break; case SecondSection: current = &second; break; case MSecSection: current = &msec; break; case YearSection: current = &year; break; case YearSection2Digits: current = &year2digits; break; case MonthSection: current = &month; break; case DayOfWeekSectionShort: case DayOfWeekSectionLong: current = &dayofweek; break; case DaySection: current = &day; num = qMax(1, num); break; case AmPmSection: current = &m; break; default: qWarning("QDateTimeParser::parse Internal error (%s)", qPrintable(sn.name())); break; } if (!current) { qWarning("QDateTimeParser::parse Internal error 2"); return StateNode(); } if (isSet & sn.type && *current != num) { QDTPDEBUG << "CONFLICT " << sn.name() << *current << num; conflicts = true; if (index != currentSectionIndex || num == -1) { continue; } } if (num != -1) *current = num; isSet |= sn.type; } } if (state != Invalid && QStringRef(&input, pos, input.size() - pos) != separators.last()) { QDTPDEBUG << "invalid because" << input.midRef(pos) << "!=" << separators.last() << pos; state = Invalid; } if (state != Invalid) { if (parserType != QVariant::Time) { if (year % 100 != year2digits && (isSet & YearSection2Digits)) { if (!(isSet & YearSection)) { year = (year / 100) * 100; year += year2digits; } else { conflicts = true; const SectionNode &sn = sectionNode(currentSectionIndex); if (sn.type == YearSection2Digits) { year = (year / 100) * 100; year += year2digits; } } } const QDate date(year, month, day); const int diff = dayofweek - date.dayOfWeek(); if (diff != 0 && state == Acceptable && isSet & DayOfWeekSectionMask) { if (isSet & DaySection) conflicts = true; const SectionNode &sn = sectionNode(currentSectionIndex); if (sn.type & DayOfWeekSectionMask || currentSectionIndex == -1) { // dayofweek should be preferred day += diff; if (day <= 0) { day += 7; } else if (day > date.daysInMonth()) { day -= 7; } QDTPDEBUG << year << month << day << dayofweek << diff << QDate(year, month, day).dayOfWeek(); } } bool needfixday = false; if (sectionType(currentSectionIndex) & DaySectionMask) { cachedDay = day; } else if (cachedDay > day) { day = cachedDay; needfixday = true; } if (!QDate::isValid(year, month, day)) { if (day < 32) { cachedDay = day; } if (day > 28 && QDate::isValid(year, month, 1)) { needfixday = true; } } if (needfixday) { if (context == FromString) { state = Invalid; goto end; } if (state == Acceptable && fixday) { day = qMin(day, QDate(year, month, 1).daysInMonth()); const QLocale loc = locale(); for (int i=0; i '%s'(%s)", input.toLatin1().constData(), newCurrentValue.toString(QLatin1String("yyyy/MM/dd hh:mm:ss.zzz")).toLatin1().constData(), stateName(state).toLatin1().constData()); } end: if (newCurrentValue.isValid()) { if (context != FromString && state != Invalid && newCurrentValue < minimum) { const QLatin1Char space(' '); if (newCurrentValue >= minimum) qWarning("QDateTimeParser::parse Internal error 3 (%s %s)", qPrintable(newCurrentValue.toString()), qPrintable(minimum.toString())); bool done = false; state = Invalid; for (int i=0; i= minimum && copy <= maximum) { state = Intermediate; done = true; } break; } } Q_FALLTHROUGH(); case MonthSection: if (sn.count >= 3) { const int currentMonth = newCurrentValue.date().month(); int tmp = currentMonth; // I know the first possible month makes the date too early while ((tmp = findMonth(t, tmp + 1, i)) != -1) { const QDateTime copy(newCurrentValue.addMonths(tmp - currentMonth)); if (copy >= minimum && copy <= maximum) break; // break out of while } if (tmp == -1) { break; } state = Intermediate; done = true; break; } Q_FALLTHROUGH(); default: { int toMin; int toMax; if (sn.type & TimeSectionMask) { if (newCurrentValue.daysTo(minimum) != 0) { break; } const QTime time = newCurrentValue.time(); toMin = time.msecsTo(minimum.time()); if (newCurrentValue.daysTo(maximum) > 0) { toMax = -1; // can't get to max } else { toMax = time.msecsTo(maximum.time()); } } else { toMin = newCurrentValue.daysTo(minimum); toMax = newCurrentValue.daysTo(maximum); } const int maxChange = sn.maxChange(); if (toMin > maxChange) { QDTPDEBUG << "invalid because toMin > maxChange" << toMin << maxChange << t << newCurrentValue << minimum; state = Invalid; done = true; break; } else if (toMax > maxChange) { toMax = -1; // can't get to max } const int min = getDigit(minimum, i); if (min == -1) { qWarning("QDateTimeParser::parse Internal error 4 (%s)", qPrintable(sn.name())); state = Invalid; done = true; break; } int max = toMax != -1 ? getDigit(maximum, i) : absoluteMax(i, newCurrentValue); int pos = cursorPosition - sn.pos; if (pos < 0 || pos >= t.size()) pos = -1; if (!potentialValue(t.simplified(), min, max, i, newCurrentValue, pos)) { QDTPDEBUG << "invalid because potentialValue(" << t.simplified() << min << max << sn.name() << "returned" << toMax << toMin << pos; state = Invalid; done = true; break; } state = Intermediate; done = true; break; } } } } } else { if (context == FromString) { // optimization Q_ASSERT(maximum.date().toJulianDay() == 4642999); if (newCurrentValue.date().toJulianDay() > 4642999) state = Invalid; } else { if (newCurrentValue > maximum) state = Invalid; } QDTPDEBUG << "not checking intermediate because newCurrentValue is" << newCurrentValue << minimum << maximum; } } StateNode node; node.input = input; node.state = state; node.conflicts = conflicts; node.value = newCurrentValue.toTimeSpec(spec); text = input; return node; } #endif // QT_NO_DATESTRING #ifndef QT_NO_TEXTDATE /* \internal \brief Returns the index in \a entries with the best prefix match to \a text Scans \a entries looking for an entry overlapping \a text as much as possible (an exact match beats any prefix match; a match of the full entry as prefix of text beats any entry but one matching a longer prefix; otherwise, the match of longest prefix wins, earlier entries beating later on a draw). Records the length of overlap in *used (if \a used is non-NULL) and the first entry that overlapped this much in *usedText (if \a usedText is non-NULL). */ static int findTextEntry(const QString &text, const QVector &entries, QString *usedText, int *used) { if (text.isEmpty()) return -1; int bestMatch = -1; int bestCount = 0; for (int n = 0; n < entries.size(); ++n) { const QString &name = entries.at(n); const int limit = qMin(text.size(), name.size()); int i = 0; while (i < limit && text.at(i) == name.at(i).toLower()) ++i; // Full match beats an equal prefix match: if (i > bestCount || (i == bestCount && i == name.size())) { bestCount = i; bestMatch = n; if (i == name.size() && i == text.size()) break; // Exact match, name == text, wins. } } if (usedText && bestMatch != -1) *usedText = entries.at(bestMatch); if (used) *used = bestCount; return bestMatch; } /*! \internal finds the first possible monthname that \a str1 can match. Starting from \a index; str should already by lowered */ int QDateTimeParser::findMonth(const QString &str1, int startMonth, int sectionIndex, QString *usedMonth, int *used) const { const SectionNode &sn = sectionNode(sectionIndex); if (sn.type != MonthSection) { qWarning("QDateTimeParser::findMonth Internal error"); return -1; } QLocale::FormatType type = sn.count == 3 ? QLocale::ShortFormat : QLocale::LongFormat; QLocale l = locale(); QVector monthNames; monthNames.reserve(13 - startMonth); for (int month = startMonth; month <= 12; ++month) monthNames.append(l.monthName(month, type)); const int index = findTextEntry(str1, monthNames, usedMonth, used); return index < 0 ? index : index + startMonth; } int QDateTimeParser::findDay(const QString &str1, int startDay, int sectionIndex, QString *usedDay, int *used) const { const SectionNode &sn = sectionNode(sectionIndex); if (!(sn.type & DaySectionMask)) { qWarning("QDateTimeParser::findDay Internal error"); return -1; } QLocale::FormatType type = sn.count == 4 ? QLocale::LongFormat : QLocale::ShortFormat; QLocale l = locale(); QVector daysOfWeek; daysOfWeek.reserve(8 - startDay); for (int day = startDay; day <= 7; ++day) daysOfWeek.append(l.dayName(day, type)); const int index = findTextEntry(str1, daysOfWeek, usedDay, used); return index < 0 ? index : index + startDay; } #endif // QT_NO_TEXTDATE /*! \internal Returns AM if str == tr("AM") PM if str == tr("PM") PossibleAM if str can become tr("AM") PossiblePM if str can become tr("PM") PossibleBoth if str can become tr("PM") and can become tr("AM") Neither if str can't become anything sensible */ QDateTimeParser::AmPmFinder QDateTimeParser::findAmPm(QString &str, int sectionIndex, int *used) const { const SectionNode &s = sectionNode(sectionIndex); if (s.type != AmPmSection) { qWarning("QDateTimeParser::findAmPm Internal error"); return Neither; } if (used) *used = str.size(); if (QStringRef(&str).trimmed().isEmpty()) { return PossibleBoth; } const QLatin1Char space(' '); int size = sectionMaxSize(sectionIndex); enum { amindex = 0, pmindex = 1 }; QString ampm[2]; ampm[amindex] = getAmPmText(AmText, s.count == 1 ? UpperCase : LowerCase); ampm[pmindex] = getAmPmText(PmText, s.count == 1 ? UpperCase : LowerCase); for (int i=0; i<2; ++i) ampm[i].truncate(size); QDTPDEBUG << "findAmPm" << str << ampm[0] << ampm[1]; if (str.indexOf(ampm[amindex], 0, Qt::CaseInsensitive) == 0) { str = ampm[amindex]; return AM; } else if (str.indexOf(ampm[pmindex], 0, Qt::CaseInsensitive) == 0) { str = ampm[pmindex]; return PM; } else if (context == FromString || (str.count(space) == 0 && str.size() >= size)) { return Neither; } size = qMin(size, str.size()); bool broken[2] = {false, false}; for (int i=0; i= min && val <= max && str.size() == size) { return true; } else if (val > max) { return false; } else if (str.size() == size && val < min) { return false; } const int len = size - str.size(); for (int i=0; i= 0) { const QString tmp = str.left(insert) + QLatin1Char('0' + j) + str.mid(insert); if (potentialValue(tmp, min, max, index, currentValue, insert)) return true; } } } return false; } bool QDateTimeParser::skipToNextSection(int index, const QDateTime ¤t, const QStringRef &text) const { const SectionNode &node = sectionNode(index); Q_ASSERT(text.size() < sectionMaxSize(index)); const QDateTime maximum = getMaximum(); const QDateTime minimum = getMinimum(); Q_ASSERT(current >= minimum && current <= maximum); QDateTime tmp = current; int min = absoluteMin(index); setDigit(tmp, index, min); if (tmp < minimum) { min = getDigit(minimum, index); } int max = absoluteMax(index, current); setDigit(tmp, index, max); if (tmp > maximum) { max = getDigit(maximum, index); } int pos = cursorPosition() - node.pos; if (pos < 0 || pos >= text.size()) pos = -1; const bool potential = potentialValue(text, min, max, index, current, pos); return !potential; /* If the value potentially can become another valid entry we * don't want to skip to the next. E.g. In a M field (month * without leading 0 if you type 1 we don't want to autoskip but * if you type 3 we do */ } /*! \internal For debugging. Returns the name of the section \a s. */ QString QDateTimeParser::SectionNode::name(QDateTimeParser::Section s) { switch (s) { case QDateTimeParser::AmPmSection: return QLatin1String("AmPmSection"); case QDateTimeParser::DaySection: return QLatin1String("DaySection"); case QDateTimeParser::DayOfWeekSectionShort: return QLatin1String("DayOfWeekSectionShort"); case QDateTimeParser::DayOfWeekSectionLong: return QLatin1String("DayOfWeekSectionLong"); case QDateTimeParser::Hour24Section: return QLatin1String("Hour24Section"); case QDateTimeParser::Hour12Section: return QLatin1String("Hour12Section"); case QDateTimeParser::MSecSection: return QLatin1String("MSecSection"); case QDateTimeParser::MinuteSection: return QLatin1String("MinuteSection"); case QDateTimeParser::MonthSection: return QLatin1String("MonthSection"); case QDateTimeParser::SecondSection: return QLatin1String("SecondSection"); case QDateTimeParser::YearSection: return QLatin1String("YearSection"); case QDateTimeParser::YearSection2Digits: return QLatin1String("YearSection2Digits"); case QDateTimeParser::NoSection: return QLatin1String("NoSection"); case QDateTimeParser::FirstSection: return QLatin1String("FirstSection"); case QDateTimeParser::LastSection: return QLatin1String("LastSection"); default: return QLatin1String("Unknown section ") + QString::number(int(s)); } } /*! \internal For debugging. Returns the name of the state \a s. */ QString QDateTimeParser::stateName(State s) const { switch (s) { case Invalid: return QLatin1String("Invalid"); case Intermediate: return QLatin1String("Intermediate"); case Acceptable: return QLatin1String("Acceptable"); default: return QLatin1String("Unknown state ") + QString::number(s); } } #ifndef QT_NO_DATESTRING bool QDateTimeParser::fromString(const QString &t, QDate *date, QTime *time) const { QDateTime val(QDate(1900, 1, 1), QDATETIMEEDIT_TIME_MIN); QString text = t; int copy = -1; const StateNode tmp = parse(text, copy, val, false); if (tmp.state != Acceptable || tmp.conflicts) { return false; } if (time) { const QTime t = tmp.value.time(); if (!t.isValid()) { return false; } *time = t; } if (date) { const QDate d = tmp.value.date(); if (!d.isValid()) { return false; } *date = d; } return true; } #endif // QT_NO_DATESTRING QDateTime QDateTimeParser::getMinimum() const { // Cache the most common case if (spec == Qt::LocalTime) { static const QDateTime localTimeMin(QDATETIMEEDIT_DATE_MIN, QDATETIMEEDIT_TIME_MIN, Qt::LocalTime); return localTimeMin; } return QDateTime(QDATETIMEEDIT_DATE_MIN, QDATETIMEEDIT_TIME_MIN, spec); } QDateTime QDateTimeParser::getMaximum() const { // Cache the most common case if (spec == Qt::LocalTime) { static const QDateTime localTimeMax(QDATETIMEEDIT_DATE_MAX, QDATETIMEEDIT_TIME_MAX, Qt::LocalTime); return localTimeMax; } return QDateTime(QDATETIMEEDIT_DATE_MAX, QDATETIMEEDIT_TIME_MAX, spec); } QString QDateTimeParser::getAmPmText(AmPm ap, Case cs) const { const QLocale loc = locale(); QString raw = ap == AmText ? loc.amText() : loc.pmText(); return cs == UpperCase ? raw.toUpper() : raw.toLower(); } /* \internal I give arg2 preference because arg1 is always a QDateTime. */ bool operator==(const QDateTimeParser::SectionNode &s1, const QDateTimeParser::SectionNode &s2) { return (s1.type == s2.type) && (s1.pos == s2.pos) && (s1.count == s2.count); } #endif // QT_BOOTSTRAPPED QT_END_NAMESPACE