QDateTimeParser: partial break-up of long method to eliminate goto

Method parse() was very long, had an inner block purely to isolate
many variables and had a clean-up block at the end to which it would
goto from within that 300-line block.  Split out that block as a
separate function, turn goto into premature return, clean up the
result.

Change-Id: I63ded0c0afacf4a1972a3a62c2d81834950ab729
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Edward Welbourne 2016-12-15 10:55:43 +01:00
parent 3fe9e5dff7
commit 0fdbd239c0
2 changed files with 313 additions and 307 deletions

View File

@ -1067,6 +1067,267 @@ static QTime actualTime(QDateTimeParser::Sections known,
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;
}
int ampm = -1;
Sections isSet = NoSection;
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;
}
}
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 = &ampm; break;
default:
qWarning("QDateTimeParser::parse Internal error (%s)",
qPrintable(sn.name()));
break;
}
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;
// Record the present section:
isSet |= sn.type;
}
if (QStringRef(input, pos, input->size() - pos) != separators.last()) {
QDTPDEBUG << "invalid because" << input->midRef(pos)
<< "!=" << separators.last() << pos;
return StateNode();
}
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;
}
}
}
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();
}
}
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;
}
}
}
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;
}
}
}
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
*/
@ -1077,302 +1338,47 @@ QDateTimeParser::StateNode QDateTimeParser::parse(QString &input, int &cursorPos
const QDateTime minimum = getMinimum();
const QDateTime maximum = getMaximum();
State state = Acceptable;
QDateTime finalValue;
bool conflicts = false;
const int sectionNodesCount = sectionNodes.size();
QDTPDEBUG << "parse" << input;
{
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
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;
}
StateNode scan = scanString(defaultValue, fixup, &input);
cursorPosition += scan.padded;
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());
int ampm = -1;
Sections isSet = NoSection;
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);
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);
}
cursorPosition += sect.zeroes;
QDTPDEBUG << "sectionValue" << sn.name() << input
<< "pos" << pos << "used" << sect.used << stateName(sect.state);
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;
}
}
state = qMin<State>(state, sect.state);
if (state == Intermediate && context == FromString) {
state = Invalid;
break;
}
if (state != Invalid) {
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 = &ampm; break;
default:
qWarning("QDateTimeParser::parse Internal error (%s)",
qPrintable(sn.name()));
break;
}
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;
// Record the present section:
isSet |= sn.type;
}
}
if (state != Invalid && QStringRef(&input, pos, input.size() - pos) != separators.last()) {
QDTPDEBUG << "invalid because" << input.midRef(pos)
<< "!=" << separators.last() << pos;
state = Invalid;
}
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;
}
}
}
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();
}
}
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) {
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 if (state > Intermediate) {
state = Intermediate;
}
}
}
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;
}
}
}
{
QDate date(year, month, day);
QTime time(hour, minute, second, msec);
finalValue = tspec == Qt::TimeZone
? QDateTime(date, time, timeZone)
: QDateTime(date, time, tspec, zoneOffset);
}
QDTPDEBUG << year << month << day << hour << minute << second << msec;
}
QDTPDEBUGN("'%s' => '%s'(%s)", input.toLatin1().constData(),
finalValue.toString(QLatin1String("yyyy/MM/dd hh:mm:ss.zzz")).toLatin1().constData(),
stateName(state).toLatin1().constData());
}
end:
if (finalValue.isValid()) {
if (context != FromString && state != Invalid && finalValue < minimum) {
if (scan.value.isValid() && scan.state != Invalid) {
if (context != FromString && scan.value < minimum) {
const QLatin1Char space(' ');
if (finalValue >= minimum)
if (scan.value >= minimum)
qWarning("QDateTimeParser::parse Internal error 3 (%s %s)",
qPrintable(finalValue.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(finalValue.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; }
@ -1380,19 +1386,18 @@ end:
Q_FALLTHROUGH();
case MonthSection:
if (sn.count >= 3) {
const int finalMonth = finalValue.date().month();
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(finalValue.addMonths(tmp - finalMonth));
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();
@ -1401,25 +1406,24 @@ end:
int toMax;
if (sn.type & TimeSectionMask) {
if (finalValue.daysTo(minimum) != 0) {
if (scan.value.daysTo(minimum) != 0) {
break;
}
const QTime time = finalValue.time();
const QTime time = scan.value.time();
toMin = time.msecsTo(minimum.time());
if (finalValue.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 = finalValue.daysTo(minimum);
toMax = finalValue.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 << finalValue << minimum;
state = Invalid;
<< maxChange << t << scan.value << minimum;
scan.state = Invalid;
done = true;
break;
} else if (toMax > maxChange) {
@ -1430,23 +1434,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, finalValue);
int max = toMax != -1 ? getDigit(maximum, i) : absoluteMax(i, scan.value);
int pos = cursorPosition - sn.pos;
if (pos < 0 || pos >= t.size())
pos = -1;
if (!potentialValue(t.simplified(), min, max, i, finalValue, 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; }
}
@ -1456,23 +1460,20 @@ end:
if (context == FromString) {
// optimization
Q_ASSERT(maximum.date().toJulianDay() == 4642999);
if (finalValue.date().toJulianDay() > 4642999)
state = Invalid;
if (scan.value.date().toJulianDay() > 4642999)
scan.state = Invalid;
} else {
if (finalValue > maximum)
state = Invalid;
if (scan.value > maximum)
scan.state = Invalid;
}
QDTPDEBUG << "not checking intermediate because finalValue is" << finalValue << 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 = finalValue.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;
}
/*

View File

@ -160,11 +160,14 @@ public:
};
struct StateNode {
StateNode() : state(Invalid), conflicts(false) {}
StateNode() : state(Invalid), padded(0), conflicts(false) {}
StateNode(const QDateTime &val, State ok=Acceptable, int pad=0, bool bad=false)
: value(val), state(ok), padded(pad), conflicts(bad) {}
QString input;
State state;
bool conflicts;
QDateTime value;
State state;
int padded;
bool conflicts;
};
enum AmPm {
@ -200,6 +203,8 @@ private:
int sectionMaxSize(Section s, int count) const;
QString sectionText(const QString &text, int sectionIndex, int index) const;
#ifndef QT_NO_DATESTRING
StateNode scanString(const QDateTime &defaultValue,
bool fixup, QString *input) const;
struct ParsedSection {
int value;
int used;