diff options
Diffstat (limited to 'src/corelib/tools/qdatetimeparser.cpp')
-rw-r--r-- | src/corelib/tools/qdatetimeparser.cpp | 1005 |
1 files changed, 643 insertions, 362 deletions
diff --git a/src/corelib/tools/qdatetimeparser.cpp b/src/corelib/tools/qdatetimeparser.cpp index 62dd25e072..3908e6710e 100644 --- a/src/corelib/tools/qdatetimeparser.cpp +++ b/src/corelib/tools/qdatetimeparser.cpp @@ -44,6 +44,7 @@ #include "qset.h" #include "qlocale.h" #include "qdatetime.h" +#include "qtimezone.h" #include "qregexp.h" #include "qdebug.h" @@ -86,6 +87,7 @@ int QDateTimeParser::getDigit(const QDateTime &t, int index) const } const SectionNode &node = sectionNodes.at(index); switch (node.type) { + case TimeZoneSection: return t.offsetFromUtc(); case Hour24Section: case Hour12Section: return t.time().hour(); case MinuteSection: return t.time().minute(); case SecondSection: return t.time().second(); @@ -144,6 +146,9 @@ bool QDateTimeParser::setDigit(QDateTime &v, int index, int newVal) const int minute = time.minute(); int second = time.second(); int msec = time.msec(); + Qt::TimeSpec tspec = v.timeSpec(); + // Only offset from UTC is amenable to setting an int value: + int offset = tspec == Qt::OffsetFromUTC ? v.offsetFromUtc() : 0; switch (node.type) { case Hour24Section: case Hour12Section: hour = newVal; break; @@ -164,6 +169,12 @@ bool QDateTimeParser::setDigit(QDateTime &v, int index, int newVal) const } day = newVal; break; + case TimeZoneSection: + if (newVal < absoluteMin(index) || newVal > absoluteMax(index)) + return false; + tspec = Qt::OffsetFromUTC; + offset = newVal; + break; case AmPmSection: hour = (newVal == 0 ? hour % 12 : (hour % 12) + 12); break; default: qWarning("QDateTimeParser::setDigit() Internal error (%s)", @@ -179,17 +190,23 @@ bool QDateTimeParser::setDigit(QDateTime &v, int index, int newVal) const 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; + + const QDate newDate(year, month, day); + const QTime newTime(hour, minute, second, msec); + if (!newDate.isValid() || !newTime.isValid()) + return false; + + // Preserve zone: + v = (tspec == Qt::TimeZone + ? QDateTime(newDate, newTime, v.timeZone()) + : QDateTime(newDate, newTime, tspec, offset)); + return true; } /*! - \ + \internal Returns the absolute maximum for a section */ @@ -198,6 +215,7 @@ int QDateTimeParser::absoluteMax(int s, const QDateTime &cur) const { const SectionNode &sn = sectionNode(s); switch (sn.type) { + case TimeZoneSection: return QTimeZone::MaxUtcOffsetSecs; case Hour24Section: case Hour12Section: return 23; // this is special-cased in // parseSection. We want it to be @@ -232,6 +250,7 @@ int QDateTimeParser::absoluteMin(int s) const { const SectionNode &sn = sectionNode(s); switch (sn.type) { + case TimeZoneSection: return QTimeZone::MinUtcOffsetSecs; case Hour24Section: case Hour12Section: case MinuteSection: @@ -492,7 +511,16 @@ bool QDateTimeParser::parseFormat(const QString &newFormat) newDisplay |= sn.type; } break; - + case 't': + if (parserType != QVariant::Time) { + const SectionNode sn = { TimeZoneSection, i - add, countRepeat(newFormat, i, 4), 0 }; + newSectionNodes.append(sn); + appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote); + i += sn.count - 1; + index = i + 1; + newDisplay |= TimeZoneSection; + } + break; default: break; } @@ -631,6 +659,8 @@ int QDateTimeParser::sectionMaxSize(Section s, int count) const case MSecSection: return 3; case YearSection: return 4; case YearSection2Digits: return 2; + // Arbitrarily many tokens (each up to 14 bytes) joined with / separators: + case TimeZoneSection: return std::numeric_limits<int>::max(); case CalendarPopupSection: case Internal: @@ -689,89 +719,79 @@ QString QDateTimeParser::sectionText(int sectionIndex) const } -#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. -*/ +#ifndef QT_NO_DATESTRING -int QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, - QString &text, int &cursorPosition, int index, - State &state, int *usedptr) const +QDateTimeParser::ParsedSection +QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, + int offset, QString *text) const { - state = Invalid; - int num = 0; + ParsedSection result; // initially Invalid 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; + return result; } const int sectionmaxsize = sectionMaxSize(sectionIndex); - QStringRef sectionTextRef = text.midRef(index, sectionmaxsize); - int sectiontextSize = sectionTextRef.size(); + QStringRef sectionTextRef = text->midRef(offset, sectionmaxsize); QDTPDEBUG << "sectionValue for" << sn.name() - << "with text" << text << "and st" << sectionTextRef - << text.midRef(index, sectionmaxsize) - << index; + << "with text" << *text << "and (at" << offset + << ") st:" << sectionTextRef; - int used = 0; switch (sn.type) { case AmPmSection: { QString sectiontext = sectionTextRef.toString(); + int used; const int ampm = findAmPm(sectiontext, sectionIndex, &used); switch (ampm) { case AM: // sectiontext == AM case PM: // sectiontext == PM - num = ampm; - state = Acceptable; + result = ParsedSection(Acceptable, ampm, used); break; case PossibleAM: // sectiontext => AM case PossiblePM: // sectiontext => PM - num = ampm - 2; - state = Intermediate; + result = ParsedSection(Intermediate, ampm - 2, used); break; case PossibleBoth: // sectiontext => AM|PM - num = 0; - state = Intermediate; + result = ParsedSection(Intermediate, 0, used); break; case Neither: - state = Invalid; QDTPDEBUG << "invalid because findAmPm(" << sectiontext << ") returned -1"; break; default: QDTPDEBUGN("This should never happen (findAmPm returned %d)", ampm); break; } - if (state != Invalid) - text.replace(index, used, sectiontext.constData(), used); + if (result.state != Invalid) + text->replace(offset, used, sectiontext.constData(), used); break; } + case TimeZoneSection: + result = findTimeZone(sectionTextRef, currentValue, + absoluteMax(sectionIndex), + absoluteMin(sectionIndex)); + break; case MonthSection: case DayOfWeekSectionShort: case DayOfWeekSectionLong: if (sn.count >= 3) { QString sectiontext = sectionTextRef.toString(); + int num = 0, used = 0; 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); } + result = ParsedSection(Intermediate, num, used); if (num != -1) { - state = (used == sectiontext.size() ? Acceptable : Intermediate); - text.replace(index, used, sectiontext.constData(), used); - } else { - state = Intermediate; + text->replace(offset, used, sectiontext.constData(), used); + if (used == sectiontext.size()) + result = ParsedSection(Acceptable, num, used); } break; } @@ -784,26 +804,22 @@ 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; + result = ParsedSection(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; - } - } + int last = -1, used = -1; 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); @@ -825,42 +841,36 @@ int QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionInde if (last == -1) { QChar first(sectionTextRef.at(0)); - if (separators.at(sectionIndex + 1).startsWith(first)) { - used = 0; - state = Intermediate; - } else { - state = Invalid; + if (separators.at(sectionIndex + 1).startsWith(first)) + result = ParsedSection(Intermediate, 0, used); + else QDTPDEBUG << "invalid because" << sectionTextRef << "can't become a uint" << last << ok; - } } else { - num += last; const FieldInfo fi = fieldInfo(sectionIndex); const bool done = (used == sectionmaxsize); if (!done && fi & Fraction) { // typing 2 in a zzz field should be .200, not .002 - for (int i=used; i<sectionmaxsize; ++i) { - num *= 10; - } + for (int i = used; i < sectionmaxsize; ++i) + last *= 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; + if (last < absMin) { + if (!done) // reversed test to dodge QDTPDEBUG ugliness ! + result = ParsedSection(Intermediate, last, used); + else + QDTPDEBUG << "invalid because" << last << "is less than absoluteMin" << absMin; + } else if (last > absMax) { + result = ParsedSection(Intermediate, last, used); } else if (!done && (fi & (FixedWidth|Numeric)) == (FixedWidth|Numeric)) { if (skipToNextSection(sectionIndex, currentValue, digitsStr)) { - state = Acceptable; const int missingZeroes = sectionmaxsize - digitsStr.size(); - text.insert(index, QString(missingZeroes, QLatin1Char('0'))); - used = sectionmaxsize; - cursorPosition += missingZeroes; + result = ParsedSection(Acceptable, last, sectionmaxsize, missingZeroes); + text->insert(offset, QString(missingZeroes, QLatin1Char('0'))); ++(const_cast<QDateTimeParser*>(this)->sectionNodes[sectionIndex].zeroesAdded); } else { - state = Intermediate;; + result = ParsedSection(Intermediate, last, used);; } } else { - state = Acceptable; + result = ParsedSection(Acceptable, last, used); } } } @@ -868,277 +878,506 @@ int QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionInde default: qWarning("QDateTimeParser::parseSection Internal error (%s %d)", qPrintable(sn.name()), sectionIndex); - return -1; + return result; + } + Q_ASSERT(result.state != Invalid || result.value == -1); + + return result; +} + +/*! + \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 (usedptr) - *usedptr = used; + 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 (state != Invalid ? num : -1); + return actual; // It'll just have to do :-( } -#endif // QT_NO_TEXTDATE -#ifndef QT_NO_DATESTRING /*! \internal */ -QDateTimeParser::StateNode QDateTimeParser::parse(QString &input, int &cursorPosition, - const QDateTime ¤tValue, bool fixup) const +static QTime actualTime(QDateTimeParser::Sections known, + int hour, int hour12, int ampm, + int minute, int second, int msec) { - const QDateTime minimum = getMinimum(); - const QDateTime maximum = getMaximum(); + // 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; + } - State state = Acceptable; + if (ampm == -1 || (known & QDateTimeParser::AmPmSection) == 0) { + if ((known & QDateTimeParser::Hour12Section) == 0 || hour % 12 == hour12) + return actual; - QDateTime newCurrentValue; + 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::scanString(const QDateTime &defaultValue, + bool fixup, QString *input) const +{ + State state = Acceptable; bool conflicts = false; const int sectionNodesCount = sectionNodes.size(); + int padding = 0; + int pos = 0; + int year, month, day; + const QDate defaultDate = defaultValue.date(); + const QTime defaultTime = defaultValue.time(); + defaultDate.getDate(&year, &month, &day); + int year2digits = year % 100; + int hour = defaultTime.hour(); + int hour12 = -1; + int minute = defaultTime.minute(); + int second = defaultTime.second(); + int msec = defaultTime.msec(); + int dayofweek = defaultDate.dayOfWeek(); + Qt::TimeSpec tspec = defaultValue.timeSpec(); + int zoneOffset = 0; // In seconds; local - UTC + QString zoneName; + QTimeZone timeZone; + switch (tspec) { + case Qt::OffsetFromUTC: // timeZone is ignored + zoneOffset = defaultValue.offsetFromUtc(); + break; + case Qt::TimeZone: + timeZone = defaultValue.timeZone(); + if (timeZone.isValid()) + zoneOffset = timeZone.offsetFromUtc(defaultValue); + // else: is there anything we can do about this ? + break; + default: // zoneOffset and timeZone are ignored + break; + } - QDTPDEBUG << "parse" << input; - { - int pos = 0; - int year, month, day; - const QDate currentDate = currentValue.date(); - const QTime currentTime = currentValue.time(); - currentDate.getDate(&year, &month, &day); - int year2digits = year % 100; - int hour = currentTime.hour(); - int hour12 = -1; - int minute = currentTime.minute(); - int second = currentTime.second(); - int msec = currentTime.msec(); - int dayofweek = currentDate.dayOfWeek(); - - int ampm = -1; - Sections isSet = NoSection; - int num; - State tmpstate; - - for (int index=0; state != Invalid && index<sectionNodesCount; ++index) { - if (QStringRef(&input, pos, separators.at(index).size()) != separators.at(index)) { - QDTPDEBUG << "invalid because" << input.midRef(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" << sn.name() << 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); + int ampm = -1; + Sections isSet = NoSection; - state = qMin<State>(state, tmpstate); - if (state == Intermediate && context == FromString) { - state = Invalid; - break; + for (int index = 0; index < sectionNodesCount; ++index) { + Q_ASSERT(state != Invalid); + if (QStringRef(input, pos, separators.at(index).size()) != separators.at(index)) { + QDTPDEBUG << "invalid because" << input->midRef(pos, separators.at(index).size()) + << "!=" << separators.at(index) + << index << pos << currentSectionIndex; + return StateNode(); + } + pos += separators.at(index).size(); + sectionNodes[index].pos = pos; + int *current = 0; + const SectionNode sn = sectionNodes.at(index); + ParsedSection sect; + + { + const QDate date = actualDate(isSet, year, year2digits, month, day, dayofweek); + const QTime time = actualTime(isSet, hour, hour12, ampm, minute, second, msec); + sect = parseSection(tspec == Qt::TimeZone + ? QDateTime(date, time, timeZone) + : QDateTime(date, time, tspec, zoneOffset), + index, pos, input); + } + + QDTPDEBUG << "sectionValue" << sn.name() << *input + << "pos" << pos << "used" << sect.used << stateName(sect.state); + + padding += sect.zeroes; + if (fixup && sect.state == Intermediate && sect.used < sn.count) { + const FieldInfo fi = fieldInfo(index); + if ((fi & (Numeric|FixedWidth)) == (Numeric|FixedWidth)) { + const QString newText = QString::fromLatin1("%1").arg(sect.value, sn.count, 10, QLatin1Char('0')); + input->replace(pos, sect.used, newText); + sect.used = sn.count; } + } - QDTPDEBUG << index << sn.name() << "is set to" - << pos << "state is" << stateName(state); - - - if (state != Invalid) { - switch (sn.type) { - case Hour24Section: current = &hour; break; - case Hour12Section: current = &hour12; break; - case MinuteSection: current = &minute; break; - case SecondSection: current = &second; break; - case MSecSection: current = &msec; break; - case YearSection: current = &year; break; - case YearSection2Digits: current = &year2digits; break; - case MonthSection: current = &month; break; - case DayOfWeekSectionShort: - case DayOfWeekSectionLong: current = &dayofweek; break; - case DaySection: current = &day; num = qMax<int>(1, num); break; - case AmPmSection: current = &m; break; - default: - qWarning("QDateTimeParser::parse Internal error (%s)", - qPrintable(sn.name())); - break; - } - if (!current) { - qWarning("QDateTimeParser::parse Internal error 2"); - return StateNode(); - } - if (isSet & sn.type && *current != num) { - QDTPDEBUG << "CONFLICT " << sn.name() << *current << num; - conflicts = true; - if (index != currentSectionIndex || num == -1) { - continue; - } - } - if (num != -1) - *current = num; - isSet |= sn.type; + state = qMin<State>(state, sect.state); + if (state == Invalid || (state == Intermediate && context == FromString)) + return StateNode(); + + switch (sn.type) { + case TimeZoneSection: + current = &zoneOffset; + if (sect.used > 0) { + // Synchronize with what findTimeZone() found: + QStringRef zoneName = input->midRef(pos, sect.used); + Q_ASSERT(!zoneName.isEmpty()); // sect.used > 0 + const QByteArray latinZone(zoneName.toLatin1()); + timeZone = QTimeZone(latinZone); + tspec = timeZone.isValid() + ? (QTimeZone::isTimeZoneIdAvailable(latinZone) + ? Qt::TimeZone + : Qt::OffsetFromUTC) + : (Q_ASSERT(startsWithLocalTimeZone(zoneName)), Qt::LocalTime); } + break; + 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; sect.value = qMax<int>(1, sect.value); break; + case AmPmSection: current = &m; break; + default: + qWarning("QDateTimeParser::parse Internal error (%s)", + qPrintable(sn.name())); + break; } - if (state != Invalid && QStringRef(&input, pos, input.size() - pos) != separators.last()) { - QDTPDEBUG << "invalid because" << input.midRef(pos) - << "!=" << separators.last() << pos; - state = Invalid; + if (sect.used > 0) + pos += sect.used; + QDTPDEBUG << index << sn.name() << "is set to" + << pos << "state is" << stateName(state); + + if (!current) { + qWarning("QDateTimeParser::parse Internal error 2"); + return StateNode(); + } + if (isSet & sn.type && *current != sect.value) { + QDTPDEBUG << "CONFLICT " << sn.name() << *current << sect.value; + conflicts = true; + if (index != currentSectionIndex || sect.state == Invalid) { + continue; + } } + if (sect.state != Invalid) + *current = sect.value; - if (state != Invalid) { - if (parserType != QVariant::Time) { - if (year % 100 != year2digits && (isSet & YearSection2Digits)) { - if (!(isSet & YearSection)) { - year = (year / 100) * 100; - year += year2digits; - } else { - conflicts = true; - const SectionNode &sn = sectionNode(currentSectionIndex); - if (sn.type == YearSection2Digits) { - year = (year / 100) * 100; - year += year2digits; - } - } - } + // Record the present section: + isSet |= sn.type; + } - const QDate date(year, month, day); - const int diff = dayofweek - date.dayOfWeek(); - if (diff != 0 && state == Acceptable && isSet & DayOfWeekSectionMask) { - if (isSet & DaySection) - conflicts = true; - const SectionNode &sn = sectionNode(currentSectionIndex); - if (sn.type & DayOfWeekSectionMask || currentSectionIndex == -1) { - // dayofweek should be preferred - day += diff; - if (day <= 0) { - day += 7; - } else if (day > date.daysInMonth()) { - day -= 7; - } - QDTPDEBUG << year << month << day << dayofweek - << diff << QDate(year, month, day).dayOfWeek(); - } - } + if (QStringRef(input, pos, input->size() - pos) != separators.last()) { + QDTPDEBUG << "invalid because" << input->midRef(pos) + << "!=" << separators.last() << pos; + return StateNode(); + } - bool needfixday = false; - if (sectionType(currentSectionIndex) & DaySectionMask) { - cachedDay = day; - } else if (cachedDay > day) { - day = cachedDay; - needfixday = true; + if (parserType != QVariant::Time) { + if (year % 100 != year2digits && (isSet & YearSection2Digits)) { + if (!(isSet & YearSection)) { + year = (year / 100) * 100; + year += year2digits; + } else { + conflicts = true; + const SectionNode &sn = sectionNode(currentSectionIndex); + if (sn.type == YearSection2Digits) { + year = (year / 100) * 100; + year += year2digits; } + } + } - 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 SectionNode sn = sectionNode(i); - if (sn.type & DaySection) { - input.replace(sectionPos(sn), sectionSize(i), loc.toString(day)); - } else if (sn.type & DayOfWeekSectionMask) { - const int dayOfWeek = QDate(year, month, day).dayOfWeek(); - const QLocale::FormatType dayFormat = - (sn.type == DayOfWeekSectionShort - ? QLocale::ShortFormat : QLocale::LongFormat); - const QString dayName(loc.dayName(dayOfWeek, dayFormat)); - input.replace(sectionPos(sn), sectionSize(i), dayName); - } - } - } else { - state = qMin(Intermediate, state); - } + const QDate date(year, month, day); + const int diff = dayofweek - date.dayOfWeek(); + if (diff != 0 && state == Acceptable && isSet & DayOfWeekSectionMask) { + if (isSet & DaySection) + conflicts = true; + const SectionNode &sn = sectionNode(currentSectionIndex); + if (sn.type & DayOfWeekSectionMask || currentSectionIndex == -1) { + // dayofweek should be preferred + day += diff; + if (day <= 0) { + day += 7; + } else if (day > date.daysInMonth()) { + day -= 7; } + QDTPDEBUG << year << month << day << dayofweek + << diff << QDate(year, month, day).dayOfWeek(); } + } - 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; + bool needfixday = false; + if (sectionType(currentSectionIndex) & DaySectionMask) { + cachedDay = day; + } else if (cachedDay > day) { + day = cachedDay; + needfixday = true; + } + + if (!QDate::isValid(year, month, day)) { + if (day < 32) { + cachedDay = day; + } + if (day > 28 && QDate::isValid(year, month, 1)) { + needfixday = true; + } + } + if (needfixday) { + if (context == FromString) { + return StateNode(); + } + 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 SectionNode sn = sectionNode(i); + if (sn.type & DaySection) { + input->replace(sectionPos(sn), sectionSize(i), loc.toString(day)); + } else if (sn.type & DayOfWeekSectionMask) { + const int dayOfWeek = QDate(year, month, day).dayOfWeek(); + const QLocale::FormatType dayFormat = + (sn.type == DayOfWeekSectionShort + ? QLocale::ShortFormat : QLocale::LongFormat); + const QString dayName(loc.dayName(dayOfWeek, dayFormat)); + input->replace(sectionPos(sn), sectionSize(i), dayName); } } - + } else if (state > Intermediate) { + state = Intermediate; } + } + } - newCurrentValue = QDateTime(QDate(year, month, day), QTime(hour, minute, second, msec), spec); - QDTPDEBUG << year << month << day << hour << minute << second << msec; + 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; + } } - 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) { + + QDTPDEBUG << year << month << day << hour << minute << second << msec; + Q_ASSERT(state != Invalid); + + const QDate date(year, month, day); + const QTime time(hour, minute, second, msec); + return StateNode(tspec == Qt::TimeZone + ? QDateTime(date, time, timeZone) + : QDateTime(date, time, tspec, zoneOffset), + state, padding, conflicts); +} + +/*! + \internal +*/ + +QDateTimeParser::StateNode +QDateTimeParser::parse(QString input, int position, const QDateTime &defaultValue, bool fixup) const +{ + const QDateTime minimum = getMinimum(); + const QDateTime maximum = getMaximum(); + + QDTPDEBUG << "parse" << input; + StateNode scan = scanString(defaultValue, fixup, &input); + QDTPDEBUGN("'%s' => '%s'(%s)", input.toLatin1().constData(), + scan.value.toString(QLatin1String("yyyy/MM/dd hh:mm:ss.zzz")).toLatin1().constData(), + stateName(scan.state).toLatin1().constData()); + + if (scan.value.isValid() && scan.state != Invalid) { + if (context != FromString && scan.value < minimum) { const QLatin1Char space(' '); - if (newCurrentValue >= minimum) + if (scan.value >= minimum) qWarning("QDateTimeParser::parse Internal error 3 (%s %s)", - qPrintable(newCurrentValue.toString()), qPrintable(minimum.toString())); + qPrintable(scan.value.toString()), qPrintable(minimum.toString())); bool done = false; - state = Invalid; + scan.state = Invalid; + const int sectionNodesCount = sectionNodes.size(); 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)) + 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; + scan.state = Acceptable; done = true; break; case Neither: - state = Invalid; + scan.state = Invalid; done = true; break; case PossibleAM: case PossiblePM: case PossibleBoth: { - const QDateTime copy(newCurrentValue.addSecs(12 * 60 * 60)); + const QDateTime copy(scan.value.addSecs(12 * 60 * 60)); if (copy >= minimum && copy <= maximum) { - state = Intermediate; + scan.state = Intermediate; done = true; } break; } @@ -1146,19 +1385,18 @@ end: Q_FALLTHROUGH(); case MonthSection: if (sn.count >= 3) { - const int currentMonth = newCurrentValue.date().month(); - int tmp = currentMonth; + const int finalMonth = scan.value.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(scan.value.addMonths(tmp - finalMonth)); if (copy >= minimum && copy <= maximum) break; // break out of while } - if (tmp == -1) { - break; + if (tmp != -1) { + scan.state = Intermediate; + done = true; } - state = Intermediate; - done = true; break; } Q_FALLTHROUGH(); @@ -1167,25 +1405,24 @@ end: int toMax; if (sn.type & TimeSectionMask) { - if (newCurrentValue.daysTo(minimum) != 0) { + if (scan.value.daysTo(minimum) != 0) { break; } - const QTime time = newCurrentValue.time(); + const QTime time = scan.value.time(); toMin = time.msecsTo(minimum.time()); - if (newCurrentValue.daysTo(maximum) > 0) { + if (scan.value.daysTo(maximum) > 0) toMax = -1; // can't get to max - } else { + else toMax = time.msecsTo(maximum.time()); - } } else { - toMin = newCurrentValue.daysTo(minimum); - toMax = newCurrentValue.daysTo(maximum); + toMin = scan.value.daysTo(minimum); + toMax = scan.value.daysTo(maximum); } const int maxChange = sn.maxChange(); if (toMin > maxChange) { QDTPDEBUG << "invalid because toMin > maxChange" << toMin - << maxChange << t << newCurrentValue << minimum; - state = Invalid; + << maxChange << t << scan.value << minimum; + scan.state = Invalid; done = true; break; } else if (toMax > maxChange) { @@ -1196,23 +1433,23 @@ end: if (min == -1) { qWarning("QDateTimeParser::parse Internal error 4 (%s)", qPrintable(sn.name())); - state = Invalid; + scan.state = Invalid; done = true; break; } - int max = toMax != -1 ? getDigit(maximum, i) : absoluteMax(i, newCurrentValue); - int pos = cursorPosition - sn.pos; + int max = toMax != -1 ? getDigit(maximum, i) : absoluteMax(i, scan.value); + int pos = position + scan.padded - 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, scan.value, pos)) { QDTPDEBUG << "invalid because potentialValue(" << t.simplified() << min << max << sn.name() << "returned" << toMax << toMin << pos; - state = Invalid; + scan.state = Invalid; done = true; break; } - state = Intermediate; + scan.state = Intermediate; done = true; break; } } @@ -1222,27 +1459,21 @@ end: if (context == FromString) { // optimization Q_ASSERT(maximum.date().toJulianDay() == 4642999); - if (newCurrentValue.date().toJulianDay() > 4642999) - state = Invalid; + if (scan.value.date().toJulianDay() > 4642999) + scan.state = Invalid; } else { - if (newCurrentValue > maximum) - state = Invalid; + if (scan.value > maximum) + scan.state = Invalid; } - QDTPDEBUG << "not checking intermediate because newCurrentValue is" << newCurrentValue << minimum << maximum; + QDTPDEBUG << "not checking intermediate because scanned value is" << scan.value << minimum << maximum; } } - StateNode node; - node.input = input; - node.state = state; - node.conflicts = conflicts; - node.value = newCurrentValue.toTimeSpec(spec); - text = input; - return node; + text = scan.input = input; + // Set spec *after* all checking, so validity is a property of the string: + scan.value = scan.value.toTimeSpec(spec); + return scan; } -#endif // QT_NO_DATESTRING - -#ifndef QT_NO_TEXTDATE /* \internal @@ -1330,7 +1561,57 @@ int QDateTimeParser::findDay(const QString &str1, int startDay, int sectionIndex const int index = findTextEntry(str1, daysOfWeek, usedDay, used); return index < 0 ? index : index + startDay; } -#endif // QT_NO_TEXTDATE + +/*! + \internal + + Return's .value is zone's offset, zone time - UTC time, in seconds. + See QTimeZonePrivate::isValidId() for the format of zone names. + */ +QDateTimeParser::ParsedSection +QDateTimeParser::findTimeZone(QStringRef str, const QDateTime &when, + int maxVal, int minVal) const +{ + int index = startsWithLocalTimeZone(str); + int offset; + + if (index > 0) { + // We won't actually use this, but we need a valid return: + offset = QDateTime(when.date(), when.time(), Qt::LocalTime).offsetFromUtc(); + } else { + int size = str.length(); + offset = std::numeric_limits<int>::max(); // deliberately out of range + Q_ASSERT(offset > QTimeZone::MaxUtcOffsetSecs); // cf. absoluteMax() + + // Collect up plausibly-valid characters; let QTimeZone work out what's truly valid. + while (index < size) { + QChar here = str[index]; + if (here < 127 + && (here.isLetterOrNumber() + || here == '/' || here == '-' + || here == '_' || here == '.' + || here == '+' || here == ':')) + index++; + else + break; + } + + while (index > 0) { + str.truncate(index); + QTimeZone zone(str.toLatin1()); + if (zone.isValid()) { + offset = zone.offsetFromUtc(when); + break; + } + index--; // maybe we collected too much ... + } + } + + if (index > 0 && maxVal >= offset && offset >= minVal) + return ParsedSection(Acceptable, offset, index); + + return ParsedSection(); +} /*! \internal @@ -1419,6 +1700,7 @@ QDateTimeParser::AmPmFinder QDateTimeParser::findAmPm(QString &str, int sectionI return PossibleBoth; return (!broken[amindex] ? PossibleAM : PossiblePM); } +#endif // QT_NO_DATESTRING /*! \internal @@ -1461,15 +1743,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: @@ -1490,6 +1770,8 @@ QDateTimeParser::FieldInfo QDateTimeParser::fieldInfo(int index) const case AmPmSection: ret |= FixedWidth; break; + case TimeZoneSection: + break; default: qWarning("QDateTimeParser::fieldInfo Internal error 2 (%d %s %d)", index, qPrintable(sn.name()), sn.count); @@ -1571,39 +1853,39 @@ bool QDateTimeParser::potentialValue(const QStringRef &str, int min, int max, in return false; } +/*! + \internal +*/ bool QDateTimeParser::skipToNextSection(int index, const QDateTime ¤t, const QStringRef &text) const { - const SectionNode &node = sectionNode(index); Q_ASSERT(text.size() < sectionMaxSize(index)); - - const QDateTime maximum = getMaximum(); - const QDateTime minimum = getMinimum(); - Q_ASSERT(current >= minimum && current <= maximum); - - QDateTime tmp = current; + const SectionNode &node = sectionNode(index); 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); + // Time-zone field is only numeric if given as offset from UTC: + if (node.type != TimeZoneSection || current.timeSpec() == Qt::OffsetFromUTC) { + const QDateTime maximum = getMaximum(); + const QDateTime minimum = getMinimum(); + Q_ASSERT(current >= minimum && current <= maximum); + + QDateTime tmp = current; + if (!setDigit(tmp, index, min) || tmp < minimum) + min = getDigit(minimum, index); + + if (!setDigit(tmp, index, max) || 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 + /* + 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); } /*! @@ -1624,6 +1906,7 @@ QString QDateTimeParser::SectionNode::name(QDateTimeParser::Section s) case QDateTimeParser::MinuteSection: return QLatin1String("MinuteSection"); case QDateTimeParser::MonthSection: return QLatin1String("MonthSection"); case QDateTimeParser::SecondSection: return QLatin1String("SecondSection"); + case QDateTimeParser::TimeZoneSection: return QLatin1String("TimeZoneSection"); case QDateTimeParser::YearSection: return QLatin1String("YearSection"); case QDateTimeParser::YearSection2Digits: return QLatin1String("YearSection2Digits"); case QDateTimeParser::NoSection: return QLatin1String("NoSection"); @@ -1652,9 +1935,7 @@ QString QDateTimeParser::stateName(State s) const 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); + const StateNode tmp = parse(t, -1, val, false); if (tmp.state != Acceptable || tmp.conflicts) { return false; } |