/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL21$ ** 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 http://www.qt.io/terms-conditions. For further ** information use the contact form at http://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 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** As a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $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() << QString("%1:%2").arg(__FILE__).arg(__LINE__) # define QDTPDEBUGN qDebug #else # define QDTPDEBUG if (false) qDebug() # define QDTPDEBUGN if (false) qDebug #endif QT_BEGIN_NAMESPACE #ifndef QT_BOOTSTRAPPED /*! \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); int year, month, day, hour, minute, second, msec; year = v.date().year(); month = v.date().month(); day = v.date().day(); hour = v.time().hour(); minute = v.time().minute(); second = v.time().second(); msec = v.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(sectionName(node.type))); break; } if (!(node.type & (DaySection|DayOfWeekSectionShort|DayOfWeekSectionLong))) { 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(sectionName(sn.type))); 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(sectionName(sn.type)), 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(sectionName(sn.type))); 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 QString &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; i= from) str = unquote(str); list->append(str); } 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= 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.mid(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 max = newSectionNodes.size(); 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; if (displayText().size() != text.size()) { // Any zeroes added before this section will affect our size. int preceedingZeroesAdded = 0; if (sectionNodes.size() > 1 && context == DateTimeEdit) { for (QVector::ConstIterator sectionIt = sectionNodes.constBegin(); sectionIt != sectionNodes.constBegin() + sectionIndex; ++sectionIt) { preceedingZeroesAdded += sectionIt->zeroesAdded; } } sizeAdjustment = preceedingZeroesAdded; } return displayText().size() + 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; // fall through #endif case MonthSection: if (count <= 2) return 2; #ifdef QT_NO_TEXTDATE return 2; #else { int ret = 0; const QLocale l = locale(); for (int i=1; i<=mcount; ++i) { const QString str = (s == MonthSection ? l.monthName(i, count == 4 ? QLocale::LongFormat : QLocale::ShortFormat) : l.dayName(i, count == 4 ? QLocale::LongFormat : QLocale::ShortFormat)); 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: qWarning("QDateTimeParser::sectionMaxSize: Invalid section %s", sectionName(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); switch (sn.type) { case NoSectionIndex: case FirstSectionIndex: case LastSectionIndex: return QString(); default: break; } return displayText().mid(sn.pos, sectionSize(sectionIndex)); } #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(sectionName(sn.type)), sectionIndex); return -1; } const int sectionmaxsize = sectionMaxSize(sectionIndex); QString sectiontext = text.mid(index, sectionmaxsize); int sectiontextSize = sectiontext.size(); QDTPDEBUG << "sectionValue for" << sectionName(sn.type) << "with text" << text << "and st" << sectiontext << text.mid(index, sectionmaxsize) << index; int used = 0; switch (sn.type) { case AmPmSection: { 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.left(used)); } break; } case MonthSection: case DayOfWeekSectionShort: case DayOfWeekSectionLong: if (sn.count >= 3) { 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.left(used)); } else { state = Intermediate; } break; } // fall through 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; QString digitsStr(sectiontext); 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 << sectiontext.left(digits) << tmp << digits; last = tmp; used = digits; break; } } if (last == -1) { QChar first(sectiontext.at(0)); if (separators.at(sectionIndex + 1).startsWith(first)) { used = 0; state = Intermediate; } else { state = Invalid; QDTPDEBUG << "invalid because" << sectiontext << "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().fill(QLatin1Char('0'), missingZeroes)); 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(sectionName(sn.type)), 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; int pos = 0; bool conflicts = false; const int sectionNodesCount = sectionNodes.size(); QDTPDEBUG << "parse" << input; { int year, month, day, hour12, hour, minute, second, msec, ampm, dayofweek, year2digits; currentValue.date().getDate(&year, &month, &day); year2digits = year % 100; hour = currentValue.time().hour(); hour12 = -1; minute = currentValue.time().minute(); second = currentValue.time().second(); msec = currentValue.time().msec(); dayofweek = currentValue.date().dayOfWeek(); 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 << sectionName(sectionType(index)) << "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(sectionName(sn.type))); break; } if (!current) { qWarning("QDateTimeParser::parse Internal error 2"); return StateNode(); } if (isSet & sn.type && *current != num) { QDTPDEBUG << "CONFLICT " << sectionName(sn.type) << *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.mid(pos) << "!=" << separators.last() << pos; state = Invalid; } if (state != Invalid) { if (parserType != QVariant::Time) { if (year % 100 != year2digits) { switch (isSet & (YearSection2Digits|YearSection)) { case YearSection2Digits: year = (year / 100) * 100; year += year2digits; break; case ((uint)YearSection2Digits|(uint)YearSection): { conflicts = true; const SectionNode &sn = sectionNode(currentSectionIndex); if (sn.type == YearSection2Digits) { year = (year / 100) * 100; year += year2digits; } break; } default: break; } } const QDate date(year, month, day); const int diff = dayofweek - date.dayOfWeek(); if (diff != 0 && state == Acceptable && isSet & (DayOfWeekSectionShort|DayOfWeekSectionLong)) { conflicts = isSet & DaySection; const SectionNode &sn = sectionNode(currentSectionIndex); if (sn.type & (DayOfWeekSectionShort|DayOfWeekSectionLong) || 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) & (DaySection|DayOfWeekSectionShort|DayOfWeekSectionLong)) { 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; } } case MonthSection: if (sn.count >= 3) { int tmp = newCurrentValue.date().month(); // 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 - newCurrentValue.date().month())); if (copy >= minimum && copy <= maximum) break; // break out of while } if (tmp == -1) { break; } state = Intermediate; done = true; break; } // fallthrough default: { int toMin; int toMax; if (sn.type & TimeSectionMask) { if (newCurrentValue.daysTo(minimum) != 0) { break; } toMin = newCurrentValue.time().msecsTo(minimum.time()); if (newCurrentValue.daysTo(maximum) > 0) { toMax = -1; // can't get to max } else { toMax = newCurrentValue.time().msecsTo(maximum.time()); } } else { toMin = newCurrentValue.daysTo(minimum); toMax = newCurrentValue.daysTo(maximum); } const int maxChange = QDateTimeParser::maxChange(i); 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(sectionName(sn.type))); 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 << sectionName(sn.type) << "returned" << toMax << toMin << pos; state = Invalid; done = true; break; } state = Intermediate; done = true; break; } } } } } else { if (context == FromString) { // optimization Q_ASSERT(getMaximum().date().toJulianDay() == 4642999); if (newCurrentValue.date().toJulianDay() > 4642999) state = Invalid; } else { if (newCurrentValue > getMaximum()) state = Invalid; } QDTPDEBUG << "not checking intermediate because newCurrentValue is" << newCurrentValue << getMinimum() << getMaximum(); } } 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 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 { int bestMatch = -1; int bestCount = 0; if (!str1.isEmpty()) { 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(); for (int month=startMonth; month<=12; ++month) { QString str2 = l.monthName(month, type).toLower(); if (str1.startsWith(str2)) { if (used) { QDTPDEBUG << "used is set to" << str2.size(); *used = str2.size(); } if (usedMonth) *usedMonth = l.monthName(month, type); return month; } if (context == FromString) continue; const int limit = qMin(str1.size(), str2.size()); QDTPDEBUG << "limit is" << limit << str1 << str2; bool equal = true; for (int i=0; i bestCount) { bestCount = i; bestMatch = month; } break; } } if (equal) { if (used) *used = limit; if (usedMonth) *usedMonth = l.monthName(month, type); return month; } } if (usedMonth && bestMatch != -1) *usedMonth = l.monthName(bestMatch, type); } if (used) { QDTPDEBUG << "used is set to" << bestCount; *used = bestCount; } return bestMatch; } int QDateTimeParser::findDay(const QString &str1, int startDay, int sectionIndex, QString *usedDay, int *used) const { int bestMatch = -1; int bestCount = 0; if (!str1.isEmpty()) { const SectionNode &sn = sectionNode(sectionIndex); if (!(sn.type & (DaySection|DayOfWeekSectionShort|DayOfWeekSectionLong))) { qWarning("QDateTimeParser::findDay Internal error"); return -1; } const QLocale l = locale(); for (int day=startDay; day<=7; ++day) { const QString str2 = l.dayName(day, sn.count == 4 ? QLocale::LongFormat : QLocale::ShortFormat); if (str1.startsWith(str2.toLower())) { if (used) *used = str2.size(); if (usedDay) { *usedDay = str2; } return day; } if (context == FromString) continue; const int limit = qMin(str1.size(), str2.size()); bool found = true; for (int i=0; i bestCount) { bestCount = i; bestMatch = day; } found = false; break; } } if (found) { if (used) *used = limit; if (usedDay) *usedDay = str2; return day; } } if (usedDay && bestMatch != -1) { *usedDay = l.dayName(bestMatch, sn.count == 4 ? QLocale::LongFormat : QLocale::ShortFormat); } } if (used) *used = bestCount; return bestMatch; } #endif // QT_NO_TEXTDATE /*! \internal returns 0 if str == QDateTimeEdit::tr("AM") 1 if str == QDateTimeEdit::tr("PM") 2 if str can become QDateTimeEdit::tr("AM") 3 if str can become QDateTimeEdit::tr("PM") 4 if str can become QDateTimeEdit::tr("PM") and can become QDateTimeEdit::tr("AM") -1 can't become anything sensible */ int QDateTimeParser::findAmPm(QString &str, int index, int *used) const { const SectionNode &s = sectionNode(index); if (s.type != AmPmSection) { qWarning("QDateTimeParser::findAmPm Internal error"); return -1; } if (used) *used = str.size(); if (str.trimmed().isEmpty()) { return PossibleBoth; } const QLatin1Char space(' '); int size = sectionMaxSize(index); 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) { QString tmp = str; tmp.insert(insert, QLatin1Char('0' + j)); if (potentialValue(tmp, min, max, index, currentValue, insert)) return true; } } } return false; } bool QDateTimeParser::skipToNextSection(int index, const QDateTime ¤t, const QString &text) const { Q_ASSERT(current >= getMinimum() && current <= getMaximum()); const SectionNode &node = sectionNode(index); Q_ASSERT(text.size() < sectionMaxSize(index)); const QDateTime maximum = getMaximum(); const QDateTime minimum = getMinimum(); 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::sectionName(int s) const { 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(s); } } /*! \internal For debugging. Returns the name of the state \a s. */ QString QDateTimeParser::stateName(int 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 { return QDateTime(QDATETIMEEDIT_DATE_MIN, QDATETIMEEDIT_TIME_MIN, spec); } QDateTime QDateTimeParser::getMaximum() const { return QDateTime(QDATETIMEEDIT_DATE_MAX, QDATETIMEEDIT_TIME_MAX, spec); } QString QDateTimeParser::getAmPmText(AmPm ap, Case cs) const { if (ap == AmText) { return (cs == UpperCase ? QLatin1String("AM") : QLatin1String("am")); } else { return (cs == UpperCase ? QLatin1String("PM") : QLatin1String("pm")); } } /* \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