diff options
Diffstat (limited to 'src/corelib/tools')
-rw-r--r-- | src/corelib/tools/qdatetime.cpp | 1716 | ||||
-rw-r--r-- | src/corelib/tools/qdatetime_p.h | 208 | ||||
-rw-r--r-- | src/corelib/tools/qdatetimeparser.cpp | 1769 | ||||
-rw-r--r-- | src/corelib/tools/qdatetimeparser_p.h | 272 | ||||
-rw-r--r-- | src/corelib/tools/qlocale.cpp | 1 | ||||
-rw-r--r-- | src/corelib/tools/tools.pri | 2 |
6 files changed, 2045 insertions, 1923 deletions
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 <private/qcore_mac_p.h> #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<max; ++i) { - if (str.at(i) == quote) { - if (status != quote) { - status = quote; - } else if (!ret.isEmpty() && str.at(i - 1) == slash) { - ret[ret.size() - 1] = quote; - } else { - status = zero; - } - } else { - ret += str.at(i); - } - } - return ret; -} -/*! - \internal - - Parses the format \a newFormat. If successful, returns true and - sets up the format. Else keeps the old format and returns false. - -*/ - -static inline int countRepeat(const QString &str, int index, int maxCount) -{ - int count = 1; - const QChar ch(str.at(index)); - const int max = qMin(index + maxCount, str.size()); - while (index + count < max && str.at(index + count) == ch) { - ++count; - } - return count; -} - -static inline void appendSeparator(QStringList *list, const QString &string, int from, int size, int lastQuote) -{ - QString str(string.mid(from, size)); - if (lastQuote >= 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<SectionNode> 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<max; ++i) { - if (newFormat.at(i) == quote) { - lastQuote = i; - ++add; - if (status != quote) { - status = quote; - } else if (newFormat.at(i - 1) != slash) { - status = zero; - } - } else if (status != quote) { - const char sect = newFormat.at(i).toLatin1(); - switch (sect) { - case 'H': - case 'h': - if (parserType != QVariant::Date) { - const Section hour = (sect == 'h') ? Hour12Section : Hour24Section; - const SectionNode sn = { hour, i - add, countRepeat(newFormat, i, 2), 0 }; - newSectionNodes.append(sn); - appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); - i += sn.count - 1; - index = i + 1; - newDisplay |= hour; - } - break; - case 'm': - if (parserType != QVariant::Date) { - const SectionNode sn = { MinuteSection, i - add, countRepeat(newFormat, i, 2), 0 }; - newSectionNodes.append(sn); - appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); - i += sn.count - 1; - index = i + 1; - newDisplay |= MinuteSection; - } - break; - case 's': - if (parserType != QVariant::Date) { - const SectionNode sn = { SecondSection, i - add, countRepeat(newFormat, i, 2), 0 }; - newSectionNodes.append(sn); - appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); - i += sn.count - 1; - index = i + 1; - newDisplay |= SecondSection; - } - break; - - case 'z': - if (parserType != QVariant::Date) { - const SectionNode sn = { MSecSection, i - add, countRepeat(newFormat, i, 3) < 3 ? 1 : 3, 0 }; - newSectionNodes.append(sn); - appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); - i += sn.count - 1; - index = i + 1; - newDisplay |= MSecSection; - } - break; - case 'A': - case 'a': - if (parserType != QVariant::Date) { - const bool cap = (sect == 'A'); - const SectionNode sn = { AmPmSection, i - add, (cap ? 1 : 0), 0 }; - newSectionNodes.append(sn); - appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); - newDisplay |= AmPmSection; - if (i + 1 < newFormat.size() - && newFormat.at(i+1) == (cap ? QLatin1Char('P') : QLatin1Char('p'))) { - ++i; - } - index = i + 1; - } - break; - case 'y': - if (parserType != QVariant::Time) { - const int repeat = countRepeat(newFormat, i, 4); - if (repeat >= 2) { - const SectionNode sn = { repeat == 4 ? YearSection : YearSection2Digits, - i - add, repeat == 4 ? 4 : 2, 0 }; - newSectionNodes.append(sn); - appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); - i += sn.count - 1; - index = i + 1; - newDisplay |= sn.type; - } - } - break; - case 'M': - if (parserType != QVariant::Time) { - const SectionNode sn = { MonthSection, i - add, countRepeat(newFormat, i, 4), 0 }; - newSectionNodes.append(sn); - newSeparators.append(unquote(newFormat.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<max; ++i) { - SectionNode &node = newSectionNodes[i]; - if (node.type == Hour12Section) - node.type = Hour24Section; - } - } - - if (index < newFormat.size()) { - appendSeparator(&newSeparators, newFormat, index, index - max, lastQuote); - } else { - newSeparators.append(QString()); - } - - displayFormat = newFormat; - separators = newSeparators; - sectionNodes = newSectionNodes; - display = newDisplay; - last.pos = -1; - -// for (int i=0; i<sectionNodes.size(); ++i) { -// QDTPDEBUG << sectionName(sectionNodes.at(i).type) << sectionNodes.at(i).count; -// } - - QDTPDEBUG << newFormat << displayFormat; - QDTPDEBUGN("separators:\n'%s'", separators.join(QLatin1String("\n")).toLatin1().constData()); - - return true; -} - -/*! - \internal - - Returns the size of section \a s. -*/ - -int QDateTimeParser::sectionSize(int sectionIndex) const -{ - if (sectionIndex < 0) - return 0; - - if (sectionIndex >= 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<SectionNode>::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<sectionmaxsize; ++i) { - num *= 10; - } - } - const int absMin = absoluteMin(sectionIndex); - if (num < absMin) { - state = done ? Invalid : Intermediate; - if (done) - QDTPDEBUG << "invalid because" << num << "is less than absoluteMin" << absMin; - } else if (num > 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<QDateTimeParser*>(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<sectionNodesCount; ++index) { - if (QStringRef(&input, pos, separators.at(index).size()) != separators.at(index)) { - QDTPDEBUG << "invalid because" << input.mid(pos, separators.at(index).size()) - << "!=" << separators.at(index) - << index << pos << currentSectionIndex; - state = Invalid; - goto end; - } - pos += separators.at(index).size(); - sectionNodes[index].pos = pos; - int *current = 0; - const SectionNode sn = sectionNodes.at(index); - int used; - - num = parseSection(currentValue, index, input, cursorPosition, pos, tmpstate, &used); - QDTPDEBUG << "sectionValue" << sectionName(sectionType(index)) << input - << "pos" << pos << "used" << used << stateName(tmpstate); - if (fixup && tmpstate == Intermediate && used < sn.count) { - const FieldInfo fi = fieldInfo(index); - if ((fi & (Numeric|FixedWidth)) == (Numeric|FixedWidth)) { - const QString newText = QString::fromLatin1("%1").arg(num, sn.count, 10, QLatin1Char('0')); - input.replace(pos, used, newText); - used = sn.count; - } - } - pos += qMax(0, used); - - state = qMin<State>(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<int>(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<int>(day, QDate(year, month, 1).daysInMonth()); - - const QLocale loc = locale(); - for (int i=0; i<sectionNodesCount; ++i) { - const Section thisSectionType = sectionType(i); - if (thisSectionType & (DaySection)) { - input.replace(sectionPos(i), sectionSize(i), loc.toString(day)); - } else if (thisSectionType & (DayOfWeekSectionShort|DayOfWeekSectionLong)) { - const int dayOfWeek = QDate(year, month, day).dayOfWeek(); - const QLocale::FormatType dayFormat = (thisSectionType == DayOfWeekSectionShort - ? QLocale::ShortFormat : QLocale::LongFormat); - const QString dayName(loc.dayName(dayOfWeek, dayFormat)); - input.replace(sectionPos(i), sectionSize(i), dayName); - } - } - } else { - state = qMin(Intermediate, state); - } - } - } - - if (parserType != QVariant::Date) { - if (isSet & Hour12Section) { - const bool hasHour = isSet & Hour24Section; - if (ampm == -1) { - if (hasHour) { - ampm = (hour < 12 ? 0 : 1); - } else { - ampm = 0; // no way to tell if this is am or pm so I assume am - } - } - hour12 = (ampm == 0 ? hour12 % 12 : (hour12 % 12) + 12); - if (!hasHour) { - hour = hour12; - } else if (hour != hour12) { - conflicts = true; - } - } else if (ampm != -1) { - if (!(isSet & (Hour24Section))) { - hour = (12 * ampm); // special case. Only ap section - } else if ((ampm == 0) != (hour < 12)) { - conflicts = true; - } - } - - } - - newCurrentValue = QDateTime(QDate(year, month, day), QTime(hour, minute, second, msec), spec); - QDTPDEBUG << year << month << day << hour << minute << second << msec; - } - QDTPDEBUGN("'%s' => '%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<sectionNodesCount && !done; ++i) { - const SectionNode &sn = sectionNodes.at(i); - QString t = sectionText(input, i, sn.pos).toLower(); - if ((t.size() < sectionMaxSize(i) && (((int)fieldInfo(i) & (FixedWidth|Numeric)) != Numeric)) - || t.contains(space)) { - switch (sn.type) { - case AmPmSection: - switch (findAmPm(t, i)) { - case AM: - case PM: - state = Acceptable; - done = true; - break; - case Neither: - state = Invalid; - done = true; - break; - case PossibleAM: - case PossiblePM: - case PossibleBoth: { - const QDateTime copy(newCurrentValue.addSecs(12 * 60 * 60)); - if (copy >= 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<limit; ++i) { - if (str1.at(i) != str2.at(i)) { - equal = false; - if (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<limit; ++i) { - if (str1.at(i) != str2.at(i) && !str1.at(i).isSpace()) { - if (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<size; ++i) { - if (str.at(i) != space) { - for (int j=0; j<2; ++j) { - if (!broken[j]) { - int index = ampm[j].indexOf(str.at(i)); - QDTPDEBUG << "looking for" << str.at(i) - << "in" << ampm[j] << "and got" << index; - if (index == -1) { - if (str.at(i).category() == QChar::Letter_Uppercase) { - index = ampm[j].indexOf(str.at(i).toLower()); - QDTPDEBUG << "trying with" << str.at(i).toLower() - << "in" << ampm[j] << "and got" << index; - } else if (str.at(i).category() == QChar::Letter_Lowercase) { - index = ampm[j].indexOf(str.at(i).toUpper()); - QDTPDEBUG << "trying with" << str.at(i).toUpper() - << "in" << ampm[j] << "and got" << index; - } - if (index == -1) { - broken[j] = true; - if (broken[amindex] && broken[pmindex]) { - QDTPDEBUG << str << "didn't make it"; - return Neither; - } - continue; - } else { - str[i] = ampm[j].at(index); // fix case - } - } - ampm[j].remove(index, 1); - } - } - } - } - if (!broken[pmindex] && !broken[amindex]) - return PossibleBoth; - return (!broken[amindex] ? PossibleAM : PossiblePM); -} - -/*! - \internal - Max number of units that can be changed by this section. -*/ - -int QDateTimeParser::maxChange(int index) const -{ - const SectionNode &sn = sectionNode(index); - switch (sn.type) { - // Time. unit is msec - case MSecSection: return 999; - case SecondSection: return 59 * 1000; - case MinuteSection: return 59 * 60 * 1000; - case Hour24Section: case Hour12Section: return 59 * 60 * 60 * 1000; - - // Date. unit is day - case DayOfWeekSectionShort: - case DayOfWeekSectionLong: return 7; - case DaySection: return 30; - case MonthSection: return 365 - 31; - case YearSection: return 9999 * 365; - case YearSection2Digits: return 100 * 365; - default: - qWarning("QDateTimeParser::maxChange() Internal error (%s)", - qPrintable(sectionName(sectionType(index)))); - } - - return -1; -} - -QDateTimeParser::FieldInfo QDateTimeParser::fieldInfo(int index) const -{ - FieldInfo ret = 0; - const SectionNode &sn = sectionNode(index); - const Section s = sn.type; - switch (s) { - case MSecSection: - ret |= Fraction; - // fallthrough - case SecondSection: - case MinuteSection: - case Hour24Section: - case Hour12Section: - case YearSection: - case YearSection2Digits: - ret |= Numeric; - if (s != YearSection) { - ret |= AllowPartial; - } - if (sn.count != 1) { - ret |= FixedWidth; - } - break; - case MonthSection: - case DaySection: - switch (sn.count) { - case 2: - ret |= FixedWidth; - // fallthrough - case 1: - ret |= (Numeric|AllowPartial); - break; - } - break; - case DayOfWeekSectionShort: - case DayOfWeekSectionLong: - if (sn.count == 3) - ret |= FixedWidth; - break; - case AmPmSection: - ret |= FixedWidth; - break; - default: - qWarning("QDateTimeParser::fieldInfo Internal error 2 (%d %s %d)", - index, qPrintable(sectionName(sn.type)), sn.count); - break; - } - return ret; -} - -/*! - \internal - - Get a number that str can become which is between min - and max or -1 if this is not possible. -*/ - - -QString QDateTimeParser::sectionFormat(int index) const -{ - const SectionNode &sn = sectionNode(index); - return sectionFormat(sn.type, sn.count); -} - -QString QDateTimeParser::sectionFormat(Section s, int count) const -{ - QChar fillChar; - switch (s) { - case AmPmSection: return count == 1 ? QLatin1String("AP") : QLatin1String("ap"); - case MSecSection: fillChar = QLatin1Char('z'); break; - case SecondSection: fillChar = QLatin1Char('s'); break; - case MinuteSection: fillChar = QLatin1Char('m'); break; - case Hour24Section: fillChar = QLatin1Char('H'); break; - case Hour12Section: fillChar = QLatin1Char('h'); break; - case DayOfWeekSectionShort: - case DayOfWeekSectionLong: - case DaySection: fillChar = QLatin1Char('d'); break; - case MonthSection: fillChar = QLatin1Char('M'); break; - case YearSection2Digits: - case YearSection: fillChar = QLatin1Char('y'); break; - default: - qWarning("QDateTimeParser::sectionFormat Internal error (%s)", - qPrintable(sectionName(s))); - return QString(); - } - if (fillChar.isNull()) { - qWarning("QDateTimeParser::sectionFormat Internal error 2"); - return QString(); - } - - QString str; - str.fill(fillChar, count); - return str; -} - - -/*! - \internal - - Returns true if str can be modified to represent a - number that is within min and max. -*/ - -bool QDateTimeParser::potentialValue(const QString &str, int min, int max, int index, - const QDateTime ¤tValue, int insert) const -{ - if (str.isEmpty()) { - return true; - } - const int size = sectionMaxSize(index); - int val = (int)locale().toUInt(str); - const SectionNode &sn = sectionNode(index); - if (sn.type == YearSection2Digits) { - val += currentValue.date().year() - (currentValue.date().year() % 100); - } - if (val >= 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<len; ++i) { - for (int j=0; j<10; ++j) { - if (potentialValue(str + QLatin1Char('0' + j), min, max, index, currentValue, insert)) { - return true; - } else if (insert >= 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<SectionNode> 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<max; ++i) { + if (str.at(i) == quote) { + if (status != quote) { + status = quote; + } else if (!ret.isEmpty() && str.at(i - 1) == slash) { + ret[ret.size() - 1] = quote; + } else { + status = zero; + } + } else { + ret += str.at(i); + } + } + return ret; +} +/*! + \internal + + Parses the format \a newFormat. If successful, returns true and + sets up the format. Else keeps the old format and returns false. + +*/ + +static inline int countRepeat(const QString &str, int index, int maxCount) +{ + int count = 1; + const QChar ch(str.at(index)); + const int max = qMin(index + maxCount, str.size()); + while (index + count < max && str.at(index + count) == ch) { + ++count; + } + return count; +} + +static inline void appendSeparator(QStringList *list, const QString &string, int from, int size, int lastQuote) +{ + QString str(string.mid(from, size)); + if (lastQuote >= 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<SectionNode> 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<max; ++i) { + if (newFormat.at(i) == quote) { + lastQuote = i; + ++add; + if (status != quote) { + status = quote; + } else if (newFormat.at(i - 1) != slash) { + status = zero; + } + } else if (status != quote) { + const char sect = newFormat.at(i).toLatin1(); + switch (sect) { + case 'H': + case 'h': + if (parserType != QVariant::Date) { + const Section hour = (sect == 'h') ? Hour12Section : Hour24Section; + const SectionNode sn = { hour, i - add, countRepeat(newFormat, i, 2), 0 }; + newSectionNodes.append(sn); + appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); + i += sn.count - 1; + index = i + 1; + newDisplay |= hour; + } + break; + case 'm': + if (parserType != QVariant::Date) { + const SectionNode sn = { MinuteSection, i - add, countRepeat(newFormat, i, 2), 0 }; + newSectionNodes.append(sn); + appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); + i += sn.count - 1; + index = i + 1; + newDisplay |= MinuteSection; + } + break; + case 's': + if (parserType != QVariant::Date) { + const SectionNode sn = { SecondSection, i - add, countRepeat(newFormat, i, 2), 0 }; + newSectionNodes.append(sn); + appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); + i += sn.count - 1; + index = i + 1; + newDisplay |= SecondSection; + } + break; + + case 'z': + if (parserType != QVariant::Date) { + const SectionNode sn = { MSecSection, i - add, countRepeat(newFormat, i, 3) < 3 ? 1 : 3, 0 }; + newSectionNodes.append(sn); + appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); + i += sn.count - 1; + index = i + 1; + newDisplay |= MSecSection; + } + break; + case 'A': + case 'a': + if (parserType != QVariant::Date) { + const bool cap = (sect == 'A'); + const SectionNode sn = { AmPmSection, i - add, (cap ? 1 : 0), 0 }; + newSectionNodes.append(sn); + appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); + newDisplay |= AmPmSection; + if (i + 1 < newFormat.size() + && newFormat.at(i+1) == (cap ? QLatin1Char('P') : QLatin1Char('p'))) { + ++i; + } + index = i + 1; + } + break; + case 'y': + if (parserType != QVariant::Time) { + const int repeat = countRepeat(newFormat, i, 4); + if (repeat >= 2) { + const SectionNode sn = { repeat == 4 ? YearSection : YearSection2Digits, + i - add, repeat == 4 ? 4 : 2, 0 }; + newSectionNodes.append(sn); + appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); + i += sn.count - 1; + index = i + 1; + newDisplay |= sn.type; + } + } + break; + case 'M': + if (parserType != QVariant::Time) { + const SectionNode sn = { MonthSection, i - add, countRepeat(newFormat, i, 4), 0 }; + newSectionNodes.append(sn); + newSeparators.append(unquote(newFormat.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<max; ++i) { + SectionNode &node = newSectionNodes[i]; + if (node.type == Hour12Section) + node.type = Hour24Section; + } + } + + if (index < newFormat.size()) { + appendSeparator(&newSeparators, newFormat, index, index - max, lastQuote); + } else { + newSeparators.append(QString()); + } + + displayFormat = newFormat; + separators = newSeparators; + sectionNodes = newSectionNodes; + display = newDisplay; + last.pos = -1; + +// for (int i=0; i<sectionNodes.size(); ++i) { +// QDTPDEBUG << sectionName(sectionNodes.at(i).type) << sectionNodes.at(i).count; +// } + + QDTPDEBUG << newFormat << displayFormat; + QDTPDEBUGN("separators:\n'%s'", separators.join(QLatin1String("\n")).toLatin1().constData()); + + return true; +} + +/*! + \internal + + Returns the size of section \a s. +*/ + +int QDateTimeParser::sectionSize(int sectionIndex) const +{ + if (sectionIndex < 0) + return 0; + + if (sectionIndex >= 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<SectionNode>::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<sectionmaxsize; ++i) { + num *= 10; + } + } + const int absMin = absoluteMin(sectionIndex); + if (num < absMin) { + state = done ? Invalid : Intermediate; + if (done) + QDTPDEBUG << "invalid because" << num << "is less than absoluteMin" << absMin; + } else if (num > 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<QDateTimeParser*>(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<sectionNodesCount; ++index) { + if (QStringRef(&input, pos, separators.at(index).size()) != separators.at(index)) { + QDTPDEBUG << "invalid because" << input.mid(pos, separators.at(index).size()) + << "!=" << separators.at(index) + << index << pos << currentSectionIndex; + state = Invalid; + goto end; + } + pos += separators.at(index).size(); + sectionNodes[index].pos = pos; + int *current = 0; + const SectionNode sn = sectionNodes.at(index); + int used; + + num = parseSection(currentValue, index, input, cursorPosition, pos, tmpstate, &used); + QDTPDEBUG << "sectionValue" << sectionName(sectionType(index)) << input + << "pos" << pos << "used" << used << stateName(tmpstate); + if (fixup && tmpstate == Intermediate && used < sn.count) { + const FieldInfo fi = fieldInfo(index); + if ((fi & (Numeric|FixedWidth)) == (Numeric|FixedWidth)) { + const QString newText = QString::fromLatin1("%1").arg(num, sn.count, 10, QLatin1Char('0')); + input.replace(pos, used, newText); + used = sn.count; + } + } + pos += qMax(0, used); + + state = qMin<State>(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<int>(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<int>(day, QDate(year, month, 1).daysInMonth()); + + const QLocale loc = locale(); + for (int i=0; i<sectionNodesCount; ++i) { + const Section thisSectionType = sectionType(i); + if (thisSectionType & (DaySection)) { + input.replace(sectionPos(i), sectionSize(i), loc.toString(day)); + } else if (thisSectionType & (DayOfWeekSectionShort|DayOfWeekSectionLong)) { + const int dayOfWeek = QDate(year, month, day).dayOfWeek(); + const QLocale::FormatType dayFormat = (thisSectionType == DayOfWeekSectionShort + ? QLocale::ShortFormat : QLocale::LongFormat); + const QString dayName(loc.dayName(dayOfWeek, dayFormat)); + input.replace(sectionPos(i), sectionSize(i), dayName); + } + } + } else { + state = qMin(Intermediate, state); + } + } + } + + if (parserType != QVariant::Date) { + if (isSet & Hour12Section) { + const bool hasHour = isSet & Hour24Section; + if (ampm == -1) { + if (hasHour) { + ampm = (hour < 12 ? 0 : 1); + } else { + ampm = 0; // no way to tell if this is am or pm so I assume am + } + } + hour12 = (ampm == 0 ? hour12 % 12 : (hour12 % 12) + 12); + if (!hasHour) { + hour = hour12; + } else if (hour != hour12) { + conflicts = true; + } + } else if (ampm != -1) { + if (!(isSet & (Hour24Section))) { + hour = (12 * ampm); // special case. Only ap section + } else if ((ampm == 0) != (hour < 12)) { + conflicts = true; + } + } + + } + + newCurrentValue = QDateTime(QDate(year, month, day), QTime(hour, minute, second, msec), spec); + QDTPDEBUG << year << month << day << hour << minute << second << msec; + } + QDTPDEBUGN("'%s' => '%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<sectionNodesCount && !done; ++i) { + const SectionNode &sn = sectionNodes.at(i); + QString t = sectionText(input, i, sn.pos).toLower(); + if ((t.size() < sectionMaxSize(i) && (((int)fieldInfo(i) & (FixedWidth|Numeric)) != Numeric)) + || t.contains(space)) { + switch (sn.type) { + case AmPmSection: + switch (findAmPm(t, i)) { + case AM: + case PM: + state = Acceptable; + done = true; + break; + case Neither: + state = Invalid; + done = true; + break; + case PossibleAM: + case PossiblePM: + case PossibleBoth: { + const QDateTime copy(newCurrentValue.addSecs(12 * 60 * 60)); + if (copy >= 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<limit; ++i) { + if (str1.at(i) != str2.at(i)) { + equal = false; + if (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<limit; ++i) { + if (str1.at(i) != str2.at(i) && !str1.at(i).isSpace()) { + if (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<size; ++i) { + if (str.at(i) != space) { + for (int j=0; j<2; ++j) { + if (!broken[j]) { + int index = ampm[j].indexOf(str.at(i)); + QDTPDEBUG << "looking for" << str.at(i) + << "in" << ampm[j] << "and got" << index; + if (index == -1) { + if (str.at(i).category() == QChar::Letter_Uppercase) { + index = ampm[j].indexOf(str.at(i).toLower()); + QDTPDEBUG << "trying with" << str.at(i).toLower() + << "in" << ampm[j] << "and got" << index; + } else if (str.at(i).category() == QChar::Letter_Lowercase) { + index = ampm[j].indexOf(str.at(i).toUpper()); + QDTPDEBUG << "trying with" << str.at(i).toUpper() + << "in" << ampm[j] << "and got" << index; + } + if (index == -1) { + broken[j] = true; + if (broken[amindex] && broken[pmindex]) { + QDTPDEBUG << str << "didn't make it"; + return Neither; + } + continue; + } else { + str[i] = ampm[j].at(index); // fix case + } + } + ampm[j].remove(index, 1); + } + } + } + } + if (!broken[pmindex] && !broken[amindex]) + return PossibleBoth; + return (!broken[amindex] ? PossibleAM : PossiblePM); +} + +/*! + \internal + Max number of units that can be changed by this section. +*/ + +int QDateTimeParser::maxChange(int index) const +{ + const SectionNode &sn = sectionNode(index); + switch (sn.type) { + // Time. unit is msec + case MSecSection: return 999; + case SecondSection: return 59 * 1000; + case MinuteSection: return 59 * 60 * 1000; + case Hour24Section: case Hour12Section: return 59 * 60 * 60 * 1000; + + // Date. unit is day + case DayOfWeekSectionShort: + case DayOfWeekSectionLong: return 7; + case DaySection: return 30; + case MonthSection: return 365 - 31; + case YearSection: return 9999 * 365; + case YearSection2Digits: return 100 * 365; + default: + qWarning("QDateTimeParser::maxChange() Internal error (%s)", + qPrintable(sectionName(sectionType(index)))); + } + + return -1; +} + +QDateTimeParser::FieldInfo QDateTimeParser::fieldInfo(int index) const +{ + FieldInfo ret = 0; + const SectionNode &sn = sectionNode(index); + const Section s = sn.type; + switch (s) { + case MSecSection: + ret |= Fraction; + // fallthrough + case SecondSection: + case MinuteSection: + case Hour24Section: + case Hour12Section: + case YearSection: + case YearSection2Digits: + ret |= Numeric; + if (s != YearSection) { + ret |= AllowPartial; + } + if (sn.count != 1) { + ret |= FixedWidth; + } + break; + case MonthSection: + case DaySection: + switch (sn.count) { + case 2: + ret |= FixedWidth; + // fallthrough + case 1: + ret |= (Numeric|AllowPartial); + break; + } + break; + case DayOfWeekSectionShort: + case DayOfWeekSectionLong: + if (sn.count == 3) + ret |= FixedWidth; + break; + case AmPmSection: + ret |= FixedWidth; + break; + default: + qWarning("QDateTimeParser::fieldInfo Internal error 2 (%d %s %d)", + index, qPrintable(sectionName(sn.type)), sn.count); + break; + } + return ret; +} + +/*! + \internal + + Get a number that str can become which is between min + and max or -1 if this is not possible. +*/ + + +QString QDateTimeParser::sectionFormat(int index) const +{ + const SectionNode &sn = sectionNode(index); + return sectionFormat(sn.type, sn.count); +} + +QString QDateTimeParser::sectionFormat(Section s, int count) const +{ + QChar fillChar; + switch (s) { + case AmPmSection: return count == 1 ? QLatin1String("AP") : QLatin1String("ap"); + case MSecSection: fillChar = QLatin1Char('z'); break; + case SecondSection: fillChar = QLatin1Char('s'); break; + case MinuteSection: fillChar = QLatin1Char('m'); break; + case Hour24Section: fillChar = QLatin1Char('H'); break; + case Hour12Section: fillChar = QLatin1Char('h'); break; + case DayOfWeekSectionShort: + case DayOfWeekSectionLong: + case DaySection: fillChar = QLatin1Char('d'); break; + case MonthSection: fillChar = QLatin1Char('M'); break; + case YearSection2Digits: + case YearSection: fillChar = QLatin1Char('y'); break; + default: + qWarning("QDateTimeParser::sectionFormat Internal error (%s)", + qPrintable(sectionName(s))); + return QString(); + } + if (fillChar.isNull()) { + qWarning("QDateTimeParser::sectionFormat Internal error 2"); + return QString(); + } + + QString str; + str.fill(fillChar, count); + return str; +} + + +/*! + \internal + + Returns true if str can be modified to represent a + number that is within min and max. +*/ + +bool QDateTimeParser::potentialValue(const QString &str, int min, int max, int index, + const QDateTime ¤tValue, int insert) const +{ + if (str.isEmpty()) { + return true; + } + const int size = sectionMaxSize(index); + int val = (int)locale().toUInt(str); + const SectionNode &sn = sectionNode(index); + if (sn.type == YearSection2Digits) { + val += currentValue.date().year() - (currentValue.date().year() % 100); + } + if (val >= 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<len; ++i) { + for (int j=0; j<10; ++j) { + if (potentialValue(str + QLatin1Char('0' + j), min, max, index, currentValue, insert)) { + return true; + } else if (insert >= 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<SectionNode> 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 \ |