From e5734c2f08ba629d9d6bffbb1b5504f78cb71ca6 Mon Sep 17 00:00:00 2001 From: John Layt Date: Thu, 8 Aug 2013 23:17:57 +0200 Subject: QDateTime - Split QDateTimeParser into a separate file Not strictly needed yet, but helps to reduce the size of QDateTime and make it easier to read a major re-write. Imposes separation of parser which will be needed if we make QLocale always use the system backends, after which only QDateTimeEdit widget will need the parser and it can be moved there. Change-Id: I6a5e9a3edf6fe8ff2340af6afecd8ba4bfde9dd4 Reviewed-by: Lars Knoll Reviewed-by: Thiago Macieira Reviewed-by: Mitch Curtis --- src/corelib/tools/qdatetime.cpp | 1716 +---------------------------- src/corelib/tools/qdatetime_p.h | 208 ---- src/corelib/tools/qdatetimeparser.cpp | 1769 ++++++++++++++++++++++++++++++ src/corelib/tools/qdatetimeparser_p.h | 272 +++++ src/corelib/tools/qlocale.cpp | 1 + src/corelib/tools/tools.pri | 2 + src/widgets/widgets/qabstractspinbox.cpp | 2 +- src/widgets/widgets/qdatetimeedit_p.h | 2 +- 8 files changed, 2047 insertions(+), 1925 deletions(-) create mode 100644 src/corelib/tools/qdatetimeparser.cpp create mode 100644 src/corelib/tools/qdatetimeparser_p.h (limited to 'src') diff --git a/src/corelib/tools/qdatetime.cpp b/src/corelib/tools/qdatetime.cpp index ab5a516e8a..6c8fb85233 100644 --- a/src/corelib/tools/qdatetime.cpp +++ b/src/corelib/tools/qdatetime.cpp @@ -41,6 +41,7 @@ #include "qplatformdefs.h" #include "private/qdatetime_p.h" +#include "private/qdatetimeparser_p.h" #include "qdatastream.h" #include "qset.h" @@ -61,15 +62,6 @@ # endif #endif -//#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 - #if defined(Q_OS_MAC) #include #endif @@ -4460,1710 +4452,4 @@ uint qHash(const QTime &key, uint seed) Q_DECL_NOTHROW return qHash(QTime(0, 0, 0, 0).msecsTo(key), seed); } -#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) { - QString str = text; - 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); - QString str = text; - 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; - getDateFromJulianDay(currentValue.date().toJulianDay(), &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 diff --git a/src/corelib/tools/qdatetime_p.h b/src/corelib/tools/qdatetime_p.h index f3abcf02d8..d466637eb0 100644 --- a/src/corelib/tools/qdatetime_p.h +++ b/src/corelib/tools/qdatetime_p.h @@ -56,23 +56,6 @@ #include "qplatformdefs.h" #include "QtCore/qatomic.h" #include "QtCore/qdatetime.h" -#include "QtCore/qstringlist.h" -#include "QtCore/qlocale.h" -#ifndef QT_BOOTSTRAPPED -# include "QtCore/qvariant.h" -#endif -#include "QtCore/qvector.h" - - -#define QDATETIMEEDIT_TIME_MIN QTime(0, 0, 0, 0) -#define QDATETIMEEDIT_TIME_MAX QTime(23, 59, 59, 999) -#define QDATETIMEEDIT_DATE_MIN QDate(100, 1, 1) -#define QDATETIMEEDIT_COMPAT_DATE_MIN QDate(1752, 9, 14) -#define QDATETIMEEDIT_DATE_MAX QDate(7999, 12, 31) -#define QDATETIMEEDIT_DATETIME_MIN QDateTime(QDATETIMEEDIT_DATE_MIN, QDATETIMEEDIT_TIME_MIN) -#define QDATETIMEEDIT_COMPAT_DATETIME_MIN QDateTime(QDATETIMEEDIT_COMPAT_DATE_MIN, QDATETIMEEDIT_TIME_MIN) -#define QDATETIMEEDIT_DATETIME_MAX QDateTime(QDATETIMEEDIT_DATE_MAX, QDATETIMEEDIT_TIME_MAX) -#define QDATETIMEEDIT_DATE_INITIAL QDate(2000, 1, 1) QT_BEGIN_NAMESPACE @@ -108,197 +91,6 @@ public: static inline qint64 maxJd() { return QDate::maxJd(); } }; -#ifndef QT_BOOTSTRAPPED - -class Q_CORE_EXPORT QDateTimeParser -{ -public: - enum Context { - FromString, - DateTimeEdit - }; - QDateTimeParser(QVariant::Type t, Context ctx) - : currentSectionIndex(-1), display(0), cachedDay(-1), parserType(t), - fixday(false), spec(Qt::LocalTime), context(ctx) - { - defaultLocale = QLocale::system(); - first.type = FirstSection; - first.pos = -1; - first.count = -1; - first.zeroesAdded = 0; - last.type = FirstSection; - last.pos = -1; - last.count = -1; - last.zeroesAdded = 0; - none.type = NoSection; - none.pos = -1; - none.count = -1; - none.zeroesAdded = 0; - } - virtual ~QDateTimeParser() {} - enum { - Neither = -1, - AM = 0, - PM = 1, - PossibleAM = 2, - PossiblePM = 3, - PossibleBoth = 4 - }; - - enum Section { - NoSection = 0x00000, - AmPmSection = 0x00001, - MSecSection = 0x00002, - SecondSection = 0x00004, - MinuteSection = 0x00008, - Hour12Section = 0x00010, - Hour24Section = 0x00020, - TimeSectionMask = (AmPmSection|MSecSection|SecondSection|MinuteSection|Hour12Section|Hour24Section), - Internal = 0x10000, - DaySection = 0x00100, - MonthSection = 0x00200, - YearSection = 0x00400, - YearSection2Digits = 0x00800, - DayOfWeekSectionShort = 0x01000, - DayOfWeekSectionLong = 0x20000, - DateSectionMask = (DaySection|MonthSection|YearSection|YearSection2Digits|DayOfWeekSectionShort|DayOfWeekSectionLong), - FirstSection = 0x02000|Internal, - LastSection = 0x04000|Internal, - CalendarPopupSection = 0x08000|Internal, - - NoSectionIndex = -1, - FirstSectionIndex = -2, - LastSectionIndex = -3, - CalendarPopupIndex = -4 - }; // duplicated from qdatetimeedit.h - Q_DECLARE_FLAGS(Sections, Section) - - struct SectionNode { - Section type; - mutable int pos; - int count; - int zeroesAdded; - }; - - enum State { // duplicated from QValidator - Invalid, - Intermediate, - Acceptable - }; - - struct StateNode { - StateNode() : state(Invalid), conflicts(false) {} - QString input; - State state; - bool conflicts; - QDateTime value; - }; - - enum AmPm { - AmText, - PmText - }; - - enum Case { - UpperCase, - LowerCase - }; - -#ifndef QT_NO_DATESTRING - StateNode parse(QString &input, int &cursorPosition, const QDateTime ¤tValue, bool fixup) const; -#endif - int sectionMaxSize(int index) const; - int sectionSize(int index) const; - int sectionMaxSize(Section s, int count) const; - int sectionPos(int index) const; - int sectionPos(const SectionNode &sn) const; - - const SectionNode §ionNode(int index) const; - Section sectionType(int index) const; - QString sectionText(int sectionIndex) const; - QString sectionText(const QString &text, int sectionIndex, int index) const; - int getDigit(const QDateTime &dt, int index) const; - bool setDigit(QDateTime &t, int index, int newval) const; - int parseSection(const QDateTime ¤tValue, int sectionIndex, QString &txt, int &cursorPosition, - int index, QDateTimeParser::State &state, int *used = 0) const; - int absoluteMax(int index, const QDateTime &value = QDateTime()) const; - int absoluteMin(int index) const; - bool parseFormat(const QString &format); -#ifndef QT_NO_DATESTRING - bool fromString(const QString &text, QDate *date, QTime *time) const; -#endif - -#ifndef QT_NO_TEXTDATE - int findMonth(const QString &str1, int monthstart, int sectionIndex, - QString *monthName = 0, int *used = 0) const; - int findDay(const QString &str1, int intDaystart, int sectionIndex, - QString *dayName = 0, int *used = 0) const; -#endif - int findAmPm(QString &str1, int index, int *used = 0) const; - int maxChange(int s) const; - bool potentialValue(const QString &str, int min, int max, int index, - const QDateTime ¤tValue, int insert) const; - bool skipToNextSection(int section, const QDateTime ¤t, const QString §ionText) const; - QString sectionName(int s) const; - QString stateName(int s) const; - - QString sectionFormat(int index) const; - QString sectionFormat(Section s, int count) const; - - enum FieldInfoFlag { - Numeric = 0x01, - FixedWidth = 0x02, - AllowPartial = 0x04, - Fraction = 0x08 - }; - Q_DECLARE_FLAGS(FieldInfo, FieldInfoFlag) - - FieldInfo fieldInfo(int index) const; - - virtual QDateTime getMinimum() const; - virtual QDateTime getMaximum() const; - virtual int cursorPosition() const { return -1; } - virtual QString displayText() const { return text; } - virtual QString getAmPmText(AmPm ap, Case cs) const; - virtual QLocale locale() const { return defaultLocale; } - - mutable int currentSectionIndex; - Sections display; - /* - This stores the stores the most recently selected day. - It is useful when considering the following scenario: - - 1. Date is: 31/01/2000 - 2. User increments month: 29/02/2000 - 3. User increments month: 31/03/2000 - - At step 1, cachedDay stores 31. At step 2, the 31 is invalid for February, so the cachedDay is not updated. - At step 3, the the month is changed to March, for which 31 is a valid day. Since 29 < 31, the day is set to cachedDay. - This is good for when users have selected their desired day and are scrolling up or down in the month or year section - and do not want smaller months (or non-leap years) to alter the day that they chose. - */ - mutable int cachedDay; - mutable QString text; - QVector sectionNodes; - SectionNode first, last, none, popup; - QStringList separators; - QString displayFormat; - QLocale defaultLocale; - QVariant::Type parserType; - - bool fixday; - - Qt::TimeSpec spec; // spec if used by QDateTimeEdit - Context context; -}; - -Q_CORE_EXPORT bool operator==(const QDateTimeParser::SectionNode &s1, const QDateTimeParser::SectionNode &s2); - -Q_DECLARE_OPERATORS_FOR_FLAGS(QDateTimeParser::Sections) -Q_DECLARE_OPERATORS_FOR_FLAGS(QDateTimeParser::FieldInfo) - -#endif // QT_BOOTSTRAPPED - QT_END_NAMESPACE #endif // QDATETIME_P_H diff --git a/src/corelib/tools/qdatetimeparser.cpp b/src/corelib/tools/qdatetimeparser.cpp new file mode 100644 index 0000000000..5da0305a69 --- /dev/null +++ b/src/corelib/tools/qdatetimeparser.cpp @@ -0,0 +1,1769 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.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() << 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) { + QString str = text; + 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); + QString str = text; + 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 diff --git a/src/corelib/tools/qdatetimeparser_p.h b/src/corelib/tools/qdatetimeparser_p.h new file mode 100644 index 0000000000..2b4f59a23a --- /dev/null +++ b/src/corelib/tools/qdatetimeparser_p.h @@ -0,0 +1,272 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDATETIMEPARSER_P_H +#define QDATETIMEPARSER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qplatformdefs.h" +#include "QtCore/qatomic.h" +#include "QtCore/qdatetime.h" +#include "QtCore/qstringlist.h" +#include "QtCore/qlocale.h" +#ifndef QT_BOOTSTRAPPED +# include "QtCore/qvariant.h" +#endif +#include "QtCore/qvector.h" + + +#define QDATETIMEEDIT_TIME_MIN QTime(0, 0, 0, 0) +#define QDATETIMEEDIT_TIME_MAX QTime(23, 59, 59, 999) +#define QDATETIMEEDIT_DATE_MIN QDate(100, 1, 1) +#define QDATETIMEEDIT_COMPAT_DATE_MIN QDate(1752, 9, 14) +#define QDATETIMEEDIT_DATE_MAX QDate(7999, 12, 31) +#define QDATETIMEEDIT_DATETIME_MIN QDateTime(QDATETIMEEDIT_DATE_MIN, QDATETIMEEDIT_TIME_MIN) +#define QDATETIMEEDIT_COMPAT_DATETIME_MIN QDateTime(QDATETIMEEDIT_COMPAT_DATE_MIN, QDATETIMEEDIT_TIME_MIN) +#define QDATETIMEEDIT_DATETIME_MAX QDateTime(QDATETIMEEDIT_DATE_MAX, QDATETIMEEDIT_TIME_MAX) +#define QDATETIMEEDIT_DATE_INITIAL QDate(2000, 1, 1) + +QT_BEGIN_NAMESPACE + +#ifndef QT_BOOTSTRAPPED + +class Q_CORE_EXPORT QDateTimeParser +{ +public: + enum Context { + FromString, + DateTimeEdit + }; + QDateTimeParser(QVariant::Type t, Context ctx) + : currentSectionIndex(-1), display(0), cachedDay(-1), parserType(t), + fixday(false), spec(Qt::LocalTime), context(ctx) + { + defaultLocale = QLocale::system(); + first.type = FirstSection; + first.pos = -1; + first.count = -1; + first.zeroesAdded = 0; + last.type = FirstSection; + last.pos = -1; + last.count = -1; + last.zeroesAdded = 0; + none.type = NoSection; + none.pos = -1; + none.count = -1; + none.zeroesAdded = 0; + } + virtual ~QDateTimeParser() {} + enum { + Neither = -1, + AM = 0, + PM = 1, + PossibleAM = 2, + PossiblePM = 3, + PossibleBoth = 4 + }; + + enum Section { + NoSection = 0x00000, + AmPmSection = 0x00001, + MSecSection = 0x00002, + SecondSection = 0x00004, + MinuteSection = 0x00008, + Hour12Section = 0x00010, + Hour24Section = 0x00020, + TimeSectionMask = (AmPmSection|MSecSection|SecondSection|MinuteSection|Hour12Section|Hour24Section), + Internal = 0x10000, + DaySection = 0x00100, + MonthSection = 0x00200, + YearSection = 0x00400, + YearSection2Digits = 0x00800, + DayOfWeekSectionShort = 0x01000, + DayOfWeekSectionLong = 0x20000, + DateSectionMask = (DaySection|MonthSection|YearSection|YearSection2Digits|DayOfWeekSectionShort|DayOfWeekSectionLong), + FirstSection = 0x02000|Internal, + LastSection = 0x04000|Internal, + CalendarPopupSection = 0x08000|Internal, + + NoSectionIndex = -1, + FirstSectionIndex = -2, + LastSectionIndex = -3, + CalendarPopupIndex = -4 + }; // duplicated from qdatetimeedit.h + Q_DECLARE_FLAGS(Sections, Section) + + struct SectionNode { + Section type; + mutable int pos; + int count; + int zeroesAdded; + }; + + enum State { // duplicated from QValidator + Invalid, + Intermediate, + Acceptable + }; + + struct StateNode { + StateNode() : state(Invalid), conflicts(false) {} + QString input; + State state; + bool conflicts; + QDateTime value; + }; + + enum AmPm { + AmText, + PmText + }; + + enum Case { + UpperCase, + LowerCase + }; + +#ifndef QT_NO_DATESTRING + StateNode parse(QString &input, int &cursorPosition, const QDateTime ¤tValue, bool fixup) const; +#endif + int sectionMaxSize(int index) const; + int sectionSize(int index) const; + int sectionMaxSize(Section s, int count) const; + int sectionPos(int index) const; + int sectionPos(const SectionNode &sn) const; + + const SectionNode §ionNode(int index) const; + Section sectionType(int index) const; + QString sectionText(int sectionIndex) const; + QString sectionText(const QString &text, int sectionIndex, int index) const; + int getDigit(const QDateTime &dt, int index) const; + bool setDigit(QDateTime &t, int index, int newval) const; + int parseSection(const QDateTime ¤tValue, int sectionIndex, QString &txt, int &cursorPosition, + int index, QDateTimeParser::State &state, int *used = 0) const; + int absoluteMax(int index, const QDateTime &value = QDateTime()) const; + int absoluteMin(int index) const; + bool parseFormat(const QString &format); +#ifndef QT_NO_DATESTRING + bool fromString(const QString &text, QDate *date, QTime *time) const; +#endif + +#ifndef QT_NO_TEXTDATE + int findMonth(const QString &str1, int monthstart, int sectionIndex, + QString *monthName = 0, int *used = 0) const; + int findDay(const QString &str1, int intDaystart, int sectionIndex, + QString *dayName = 0, int *used = 0) const; +#endif + int findAmPm(QString &str1, int index, int *used = 0) const; + int maxChange(int s) const; + bool potentialValue(const QString &str, int min, int max, int index, + const QDateTime ¤tValue, int insert) const; + bool skipToNextSection(int section, const QDateTime ¤t, const QString §ionText) const; + QString sectionName(int s) const; + QString stateName(int s) const; + + QString sectionFormat(int index) const; + QString sectionFormat(Section s, int count) const; + + enum FieldInfoFlag { + Numeric = 0x01, + FixedWidth = 0x02, + AllowPartial = 0x04, + Fraction = 0x08 + }; + Q_DECLARE_FLAGS(FieldInfo, FieldInfoFlag) + + FieldInfo fieldInfo(int index) const; + + virtual QDateTime getMinimum() const; + virtual QDateTime getMaximum() const; + virtual int cursorPosition() const { return -1; } + virtual QString displayText() const { return text; } + virtual QString getAmPmText(AmPm ap, Case cs) const; + virtual QLocale locale() const { return defaultLocale; } + + mutable int currentSectionIndex; + Sections display; + /* + This stores the stores the most recently selected day. + It is useful when considering the following scenario: + + 1. Date is: 31/01/2000 + 2. User increments month: 29/02/2000 + 3. User increments month: 31/03/2000 + + At step 1, cachedDay stores 31. At step 2, the 31 is invalid for February, so the cachedDay is not updated. + At step 3, the the month is changed to March, for which 31 is a valid day. Since 29 < 31, the day is set to cachedDay. + This is good for when users have selected their desired day and are scrolling up or down in the month or year section + and do not want smaller months (or non-leap years) to alter the day that they chose. + */ + mutable int cachedDay; + mutable QString text; + QVector sectionNodes; + SectionNode first, last, none, popup; + QStringList separators; + QString displayFormat; + QLocale defaultLocale; + QVariant::Type parserType; + + bool fixday; + + Qt::TimeSpec spec; // spec if used by QDateTimeEdit + Context context; +}; + +Q_CORE_EXPORT bool operator==(const QDateTimeParser::SectionNode &s1, const QDateTimeParser::SectionNode &s2); + +Q_DECLARE_OPERATORS_FOR_FLAGS(QDateTimeParser::Sections) +Q_DECLARE_OPERATORS_FOR_FLAGS(QDateTimeParser::FieldInfo) + +#endif // QT_BOOTSTRAPPED + +QT_END_NAMESPACE + +#endif // QDATETIME_P_H diff --git a/src/corelib/tools/qlocale.cpp b/src/corelib/tools/qlocale.cpp index 060b220a4a..fb233c0640 100644 --- a/src/corelib/tools/qlocale.cpp +++ b/src/corelib/tools/qlocale.cpp @@ -55,6 +55,7 @@ #include "qlocale_p.h" #include "qlocale_tools_p.h" #include "qdatetime_p.h" +#include "qdatetimeparser_p.h" #include "qnamespace.h" #include "qdatetime.h" #include "qstringlist.h" diff --git a/src/corelib/tools/tools.pri b/src/corelib/tools/tools.pri index 8aab53998b..2f3697acb8 100644 --- a/src/corelib/tools/tools.pri +++ b/src/corelib/tools/tools.pri @@ -18,6 +18,7 @@ HEADERS += \ tools/qcryptographichash.h \ tools/qdatetime.h \ tools/qdatetime_p.h \ + tools/qdatetimeparser_p.h \ tools/qeasingcurve.h \ tools/qfreelist_p.h \ tools/qhash.h \ @@ -75,6 +76,7 @@ SOURCES += \ tools/qcommandlineparser.cpp \ tools/qcryptographichash.cpp \ tools/qdatetime.cpp \ + tools/qdatetimeparser.cpp \ tools/qeasingcurve.cpp \ tools/qelapsedtimer.cpp \ tools/qfreelist.cpp \ diff --git a/src/widgets/widgets/qabstractspinbox.cpp b/src/widgets/widgets/qabstractspinbox.cpp index 288375fe28..0355578327 100644 --- a/src/widgets/widgets/qabstractspinbox.cpp +++ b/src/widgets/widgets/qabstractspinbox.cpp @@ -41,7 +41,7 @@ #include #include -#include +#include #include #include diff --git a/src/widgets/widgets/qdatetimeedit_p.h b/src/widgets/widgets/qdatetimeedit_p.h index 143979d4bc..3174ec9bde 100644 --- a/src/widgets/widgets/qdatetimeedit_p.h +++ b/src/widgets/widgets/qdatetimeedit_p.h @@ -61,7 +61,7 @@ #include "QtWidgets/qlabel.h" #include "QtWidgets/qdatetimeedit.h" #include "private/qabstractspinbox_p.h" -#include "private/qdatetime_p.h" +#include "private/qdatetimeparser_p.h" #include "qdebug.h" -- cgit v1.2.3