diff options
Diffstat (limited to 'src/corelib/tools/qdatetimeparser.cpp')
-rw-r--r-- | src/corelib/tools/qdatetimeparser.cpp | 306 |
1 files changed, 244 insertions, 62 deletions
diff --git a/src/corelib/tools/qdatetimeparser.cpp b/src/corelib/tools/qdatetimeparser.cpp index 62dd25e072..d46d9942d3 100644 --- a/src/corelib/tools/qdatetimeparser.cpp +++ b/src/corelib/tools/qdatetimeparser.cpp @@ -705,7 +705,7 @@ int QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionInde state = Invalid; int num = 0; const SectionNode &sn = sectionNode(sectionIndex); - if ((sn.type & Internal) == Internal) { + if (sn.type & Internal) { qWarning("QDateTimeParser::parseSection Internal error (%s %d)", qPrintable(sn.name()), sectionIndex); return -1; @@ -713,7 +713,6 @@ int QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionInde const int sectionmaxsize = sectionMaxSize(sectionIndex); QStringRef sectionTextRef = text.midRef(index, sectionmaxsize); - int sectiontextSize = sectionTextRef.size(); QDTPDEBUG << "sectionValue for" << sn.name() << "with text" << text << "and st" << sectionTextRef @@ -757,11 +756,9 @@ int QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionInde if (sn.count >= 3) { QString sectiontext = sectionTextRef.toString(); if (sn.type == MonthSection) { - int min = 1; const QDate minDate = getMinimum().date(); - if (currentValue.date().year() == minDate.year()) { - min = minDate.month(); - } + const int min = (currentValue.date().year() == minDate.year()) + ? minDate.month() : 1; num = findMonth(sectiontext.toLower(), min, sectionIndex, §iontext, &used); } else { num = findDay(sectiontext.toLower(), 1, sectionIndex, §iontext, &used); @@ -784,26 +781,25 @@ int QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionInde case MinuteSection: case SecondSection: case MSecSection: { + int sectiontextSize = sectionTextRef.size(); if (sectiontextSize == 0) { num = 0; used = 0; state = Intermediate; } else { + for (int i = 0; i < sectiontextSize; ++i) { + if (sectionTextRef.at(i).isSpace()) + sectiontextSize = i; // which exits the loop + } + const int absMax = absoluteMax(sectionIndex); QLocale loc; bool ok = true; int last = -1; used = -1; - QStringRef digitsStr = sectionTextRef; - for (int i = 0; i < sectiontextSize; ++i) { - if (digitsStr.at(i).isSpace()) { - sectiontextSize = i; - break; - } - } - const int max = qMin(sectionmaxsize, sectiontextSize); + QStringRef digitsStr = sectionTextRef.left(max); for (int digits = max; digits >= 1; --digits) { digitsStr.truncate(digits); int tmp = (int)loc.toUInt(digitsStr, &ok); @@ -881,17 +877,199 @@ int QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionInde #ifndef QT_NO_DATESTRING /*! \internal + + Returns a date consistent with the given data on parts specified by known, + while staying as close to the given data as it can. Returns an invalid date + when on valid date is consistent with the data. +*/ + +static QDate actualDate(QDateTimeParser::Sections known, int year, int year2digits, + int month, int day, int dayofweek) +{ + QDate actual(year, month, day); + if (actual.isValid() && year % 100 == year2digits && actual.dayOfWeek() == dayofweek) + return actual; // The obvious candidate is fine :-) + + if (dayofweek < 1 || dayofweek > 7) // Invalid: ignore + known &= ~QDateTimeParser::DayOfWeekSectionMask; + + // Assuming year > 0 ... + if (year % 100 != year2digits) { + if (known & QDateTimeParser::YearSection2Digits) { + // Over-ride year, even if specified: + year += year2digits - year % 100; + known &= ~QDateTimeParser::YearSection; + } else { + year2digits = year % 100; + } + } + Q_ASSERT(year % 100 == year2digits); + + if (month < 1) { // If invalid, clip to nearest valid and ignore in known. + month = 1; + known &= ~QDateTimeParser::MonthSection; + } else if (month > 12) { + month = 12; + known &= ~QDateTimeParser::MonthSection; + } + + QDate first(year, month, 1); + int last = known & QDateTimeParser::YearSection && known & QDateTimeParser::MonthSection + ? first.daysInMonth() : 0; + // If we also know day-of-week, tweak last to the last in the month that matches it: + if (last && known & QDateTimeParser::DayOfWeekSectionMask) { + int diff = (dayofweek - first.dayOfWeek() - last) % 7; + Q_ASSERT(diff <= 0); // C++11 specifies (-ve) % (+ve) to be <= 0. + last += diff; + } + if (day < 1) { + if (known & QDateTimeParser::DayOfWeekSectionMask && last) { + day = 1 + dayofweek - first.dayOfWeek(); + if (day < 1) + day += 7; + } else { + day = 1; + } + known &= ~QDateTimeParser::DaySection; + } else if (day > 31) { + day = last; + known &= ~QDateTimeParser::DaySection; + } else if (last && day > last && (known & QDateTimeParser::DaySection) == 0) { + day = last; + } + + actual = QDate(year, month, day); + if (!actual.isValid() // We can't do better than we have, in this case + || (known & QDateTimeParser::DaySection + && known & QDateTimeParser::MonthSection + && known & QDateTimeParser::YearSection) // ditto + || actual.dayOfWeek() == dayofweek // Good enough, use it. + || (known & QDateTimeParser::DayOfWeekSectionMask) == 0) { // No contradiction, use it. + return actual; + } + + /* + Now it gets trickier. + + We have some inconsistency in our data; we've been told day of week, but + it doesn't fit with our year, month and day. At least one of these is + unknown, though: so we can fix day of week by tweaking it. + */ + + if ((known & QDateTimeParser::DaySection) == 0) { + // Relatively easy to fix. + day += dayofweek - actual.dayOfWeek(); + if (day < 1) + day += 7; + else if (day > actual.daysInMonth()) + day -= 7; + actual = QDate(year, month, day); + return actual; + } + + if ((known & QDateTimeParser::MonthSection) == 0) { + /* + Try possible month-offsets, m, preferring small; at least one (present + month doesn't work) and at most 11 (max month, 12, minus min, 1); try + in both directions, ignoring any offset that takes us out of range. + */ + for (int m = 1; m < 12; m++) { + if (m < month) { + actual = QDate(year, month - m, day); + if (actual.dayOfWeek() == dayofweek) + return actual; + } + if (m + month <= 12) { + actual = QDate(year, month + m, day); + if (actual.dayOfWeek() == dayofweek) + return actual; + } + } + // Should only get here in corner cases; e.g. day == 31 + actual = QDate(year, month, day); // Restore from trial values. + } + + if ((known & QDateTimeParser::YearSection) == 0) { + if (known & QDateTimeParser::YearSection2Digits) { + /* + Two-digit year and month are specified; choice of century can only + fix this if diff is in one of {1, 2, 5} or {2, 4, 6}; but not if + diff is in the other. It's also only reasonable to consider + adjacent century, e.g. if year thinks it's 2012 and two-digit year + is '97, it makes sense to consider 1997. If either adjacent + century does work, the other won't. + */ + actual = QDate(year + 100, month, day); + if (actual.dayOfWeek() == dayofweek) + return actual; + actual = QDate(year - 100, month, day); + if (actual.dayOfWeek() == dayofweek) + return actual; + } else { + // Offset by 7 is usually enough, but rare cases may need more: + for (int y = 1; y < 12; y++) { + actual = QDate(year - y, month, day); + if (actual.dayOfWeek() == dayofweek) + return actual; + actual = QDate(year + y, month, day); + if (actual.dayOfWeek() == dayofweek) + return actual; + } + } + actual = QDate(year, month, day); // Restore from trial values. + } + + return actual; // It'll just have to do :-( +} + +/*! + \internal +*/ + +static QTime actualTime(QDateTimeParser::Sections known, + int hour, int hour12, int ampm, + int minute, int second, int msec) +{ + // If we have no conflict, or don't know enough to diagonose one, use this: + QTime actual(hour, minute, second, msec); + if (hour12 < 0 || hour12 > 12) { // ignore bogus value + known &= ~QDateTimeParser::Hour12Section; + hour12 = hour % 12; + } + + if (ampm == -1 || (known & QDateTimeParser::AmPmSection) == 0) { + if ((known & QDateTimeParser::Hour12Section) == 0 || hour % 12 == hour12) + return actual; + + if ((known & QDateTimeParser::Hour24Section) == 0) + hour = hour12 + (hour > 12 ? 12 : 0); + } else { + Q_ASSERT(ampm == 0 || ampm == 1); + if (hour - hour12 == ampm * 12) + return actual; + + if ((known & QDateTimeParser::Hour24Section) == 0 + && known & QDateTimeParser::Hour12Section) { + hour = hour12 + ampm * 12; + } + } + actual = QTime(hour, minute, second, msec); + return actual; +} + +/*! + \internal */ QDateTimeParser::StateNode QDateTimeParser::parse(QString &input, int &cursorPosition, - const QDateTime ¤tValue, bool fixup) const + const QDateTime &defaultValue, bool fixup) const { const QDateTime minimum = getMinimum(); const QDateTime maximum = getMaximum(); State state = Acceptable; - QDateTime newCurrentValue; + QDateTime finalValue; bool conflicts = false; const int sectionNodesCount = sectionNodes.size(); @@ -899,16 +1077,16 @@ QDateTimeParser::StateNode QDateTimeParser::parse(QString &input, int &cursorPos { int pos = 0; int year, month, day; - const QDate currentDate = currentValue.date(); - const QTime currentTime = currentValue.time(); - currentDate.getDate(&year, &month, &day); + const QDate defaultDate = defaultValue.date(); + const QTime defaultTime = defaultValue.time(); + defaultDate.getDate(&year, &month, &day); int year2digits = year % 100; - int hour = currentTime.hour(); + int hour = defaultTime.hour(); int hour12 = -1; - int minute = currentTime.minute(); - int second = currentTime.second(); - int msec = currentTime.msec(); - int dayofweek = currentDate.dayOfWeek(); + int minute = defaultTime.minute(); + int second = defaultTime.second(); + int msec = defaultTime.msec(); + int dayofweek = defaultDate.dayOfWeek(); int ampm = -1; Sections isSet = NoSection; @@ -929,7 +1107,12 @@ QDateTimeParser::StateNode QDateTimeParser::parse(QString &input, int &cursorPos const SectionNode sn = sectionNodes.at(index); int used; - num = parseSection(currentValue, index, input, cursorPosition, pos, tmpstate, &used); + num = parseSection(QDateTime(actualDate(isSet, year, year2digits, + month, day, dayofweek), + actualTime(isSet, hour, hour12, ampm, + minute, second, msec), + spec), + index, input, cursorPosition, pos, tmpstate, &used); QDTPDEBUG << "sectionValue" << sn.name() << input << "pos" << pos << "used" << used << stateName(tmpstate); if (fixup && tmpstate == Intermediate && used < sn.count) { @@ -1067,8 +1250,8 @@ QDateTimeParser::StateNode QDateTimeParser::parse(QString &input, int &cursorPos input.replace(sectionPos(sn), sectionSize(i), dayName); } } - } else { - state = qMin(Intermediate, state); + } else if (state > Intermediate) { + state = Intermediate; } } } @@ -1099,20 +1282,22 @@ QDateTimeParser::StateNode QDateTimeParser::parse(QString &input, int &cursorPos } - newCurrentValue = QDateTime(QDate(year, month, day), QTime(hour, minute, second, msec), spec); + finalValue = 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(), + finalValue.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) { + if (finalValue.isValid()) { + if (context != FromString && state != Invalid && finalValue < minimum) { const QLatin1Char space(' '); - if (newCurrentValue >= minimum) + if (finalValue >= minimum) qWarning("QDateTimeParser::parse Internal error 3 (%s %s)", - qPrintable(newCurrentValue.toString()), qPrintable(minimum.toString())); + qPrintable(finalValue.toString()), qPrintable(minimum.toString())); bool done = false; state = Invalid; @@ -1136,7 +1321,7 @@ end: case PossibleAM: case PossiblePM: case PossibleBoth: { - const QDateTime copy(newCurrentValue.addSecs(12 * 60 * 60)); + const QDateTime copy(finalValue.addSecs(12 * 60 * 60)); if (copy >= minimum && copy <= maximum) { state = Intermediate; done = true; @@ -1146,11 +1331,11 @@ end: Q_FALLTHROUGH(); case MonthSection: if (sn.count >= 3) { - const int currentMonth = newCurrentValue.date().month(); - int tmp = currentMonth; + const int finalMonth = finalValue.date().month(); + int tmp = finalMonth; // I know the first possible month makes the date too early while ((tmp = findMonth(t, tmp + 1, i)) != -1) { - const QDateTime copy(newCurrentValue.addMonths(tmp - currentMonth)); + const QDateTime copy(finalValue.addMonths(tmp - finalMonth)); if (copy >= minimum && copy <= maximum) break; // break out of while } @@ -1167,24 +1352,24 @@ end: int toMax; if (sn.type & TimeSectionMask) { - if (newCurrentValue.daysTo(minimum) != 0) { + if (finalValue.daysTo(minimum) != 0) { break; } - const QTime time = newCurrentValue.time(); + const QTime time = finalValue.time(); toMin = time.msecsTo(minimum.time()); - if (newCurrentValue.daysTo(maximum) > 0) { + if (finalValue.daysTo(maximum) > 0) { toMax = -1; // can't get to max } else { toMax = time.msecsTo(maximum.time()); } } else { - toMin = newCurrentValue.daysTo(minimum); - toMax = newCurrentValue.daysTo(maximum); + toMin = finalValue.daysTo(minimum); + toMax = finalValue.daysTo(maximum); } const int maxChange = sn.maxChange(); if (toMin > maxChange) { QDTPDEBUG << "invalid because toMin > maxChange" << toMin - << maxChange << t << newCurrentValue << minimum; + << maxChange << t << finalValue << minimum; state = Invalid; done = true; break; @@ -1201,11 +1386,11 @@ end: break; } - int max = toMax != -1 ? getDigit(maximum, i) : absoluteMax(i, newCurrentValue); + int max = toMax != -1 ? getDigit(maximum, i) : absoluteMax(i, finalValue); int pos = cursorPosition - sn.pos; if (pos < 0 || pos >= t.size()) pos = -1; - if (!potentialValue(t.simplified(), min, max, i, newCurrentValue, pos)) { + if (!potentialValue(t.simplified(), min, max, i, finalValue, pos)) { QDTPDEBUG << "invalid because potentialValue(" << t.simplified() << min << max << sn.name() << "returned" << toMax << toMin << pos; state = Invalid; @@ -1222,21 +1407,21 @@ end: if (context == FromString) { // optimization Q_ASSERT(maximum.date().toJulianDay() == 4642999); - if (newCurrentValue.date().toJulianDay() > 4642999) + if (finalValue.date().toJulianDay() > 4642999) state = Invalid; } else { - if (newCurrentValue > maximum) + if (finalValue > maximum) state = Invalid; } - QDTPDEBUG << "not checking intermediate because newCurrentValue is" << newCurrentValue << minimum << maximum; + QDTPDEBUG << "not checking intermediate because finalValue is" << finalValue << minimum << maximum; } } StateNode node; node.input = input; node.state = state; node.conflicts = conflicts; - node.value = newCurrentValue.toTimeSpec(spec); + node.value = finalValue.toTimeSpec(spec); text = input; return node; } @@ -1461,15 +1646,13 @@ QDateTimeParser::FieldInfo QDateTimeParser::fieldInfo(int index) const case MinuteSection: case Hour24Section: case Hour12Section: - case YearSection: case YearSection2Digits: + ret |= AllowPartial; + Q_FALLTHROUGH(); + case YearSection: ret |= Numeric; - if (sn.type != YearSection) { - ret |= AllowPartial; - } - if (sn.count != 1) { + if (sn.count != 1) ret |= FixedWidth; - } break; case MonthSection: case DaySection: @@ -1596,14 +1779,13 @@ bool QDateTimeParser::skipToNextSection(int index, const QDateTime ¤t, con 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 + /* + 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 (there might be [012] following) but if you + type 3 we do. */ + return !potentialValue(text, min, max, index, current, pos); } /*! |