summaryrefslogtreecommitdiffstats
path: root/src/corelib/tools/qdatetimeparser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/tools/qdatetimeparser.cpp')
-rw-r--r--src/corelib/tools/qdatetimeparser.cpp306
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 &currentValue, 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 &currentValue, 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 &currentValue, 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, &sectiontext, &used);
} else {
num = findDay(sectiontext.toLower(), 1, sectionIndex, &sectiontext, &used);
@@ -784,26 +781,25 @@ int QDateTimeParser::parseSection(const QDateTime &currentValue, 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 &currentValue, 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 &currentValue, 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 &current, 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);
}
/*!