Adapt QDate::fromString() to accept negative year numbers
The documentation states that QDate::fromString() accepts negative year numbers, but it did not. This patch adds support for negative year numbers to QDate::fromString() and corresponding unit tests. Furthermore, tests are added for positive signs (+) in date strings. Fixes: QTBUG-84334 Task-number: QTBUG-84349 Change-Id: I575291e7b8317055d4bb530011d7b10c9cd37ae1 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>bb10
parent
2da301fe46
commit
91140eb726
|
|
@ -39,6 +39,7 @@
|
|||
|
||||
#include "qplatformdefs.h"
|
||||
#include "private/qdatetimeparser_p.h"
|
||||
#include "private/qstringiterator_p.h"
|
||||
|
||||
#include "qdatastream.h"
|
||||
#include "qset.h"
|
||||
|
|
@ -265,7 +266,9 @@ int QDateTimeParser::absoluteMin(int s) const
|
|||
case SecondSection:
|
||||
case MSecSection:
|
||||
case YearSection2Digits:
|
||||
case YearSection: return 0;
|
||||
return 0;
|
||||
case YearSection:
|
||||
return -9999;
|
||||
case MonthSection:
|
||||
case DaySection:
|
||||
case DayOfWeekSectionShort:
|
||||
|
|
@ -335,6 +338,22 @@ int QDateTimeParser::sectionPos(const SectionNode &sn) const
|
|||
return sn.pos;
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
|
||||
Helper function for parseSection.
|
||||
*/
|
||||
|
||||
static qsizetype digitCount(QStringView str)
|
||||
{
|
||||
qsizetype digits = 0;
|
||||
for (QStringIterator it(str); it.hasNext();) {
|
||||
if (!QChar::isDigit(it.next()))
|
||||
break;
|
||||
digits++;
|
||||
}
|
||||
return digits;
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
|
|
@ -739,7 +758,12 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, i
|
|||
"QDateTimeParser::parseSection", "Internal error");
|
||||
|
||||
const int sectionmaxsize = sectionMaxSize(sectionIndex);
|
||||
QStringView sectionTextRef = QStringView{m_text}.mid(offset, sectionmaxsize);
|
||||
const bool negate = (sn.type == YearSection && m_text.size() > offset
|
||||
&& m_text.at(offset) == QLatin1Char('-'));
|
||||
const int negativeYearOffset = negate ? 1 : 0;
|
||||
|
||||
QStringView sectionTextRef =
|
||||
QStringView { m_text }.mid(offset + negativeYearOffset, sectionmaxsize);
|
||||
|
||||
QDTPDEBUG << "sectionValue for" << sn.name()
|
||||
<< "with text" << m_text << "and (at" << offset
|
||||
|
|
@ -810,63 +834,75 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, i
|
|||
case MinuteSection:
|
||||
case SecondSection:
|
||||
case MSecSection: {
|
||||
int sectiontextSize = sectionTextRef.size();
|
||||
if (sectiontextSize == 0) {
|
||||
result = ParsedSection(Intermediate);
|
||||
int used = negativeYearOffset;
|
||||
// We already sliced off the - sign if it was legitimately present.
|
||||
if (sectionTextRef.startsWith(QLatin1Char('-'))
|
||||
|| sectionTextRef.startsWith(QLatin1Char('+'))) {
|
||||
if (separators.at(sectionIndex + 1).startsWith(sectionTextRef[0]))
|
||||
result = ParsedSection(Intermediate, 0, used);
|
||||
break;
|
||||
}
|
||||
QStringView digitsStr = sectionTextRef.left(digitCount(sectionTextRef));
|
||||
|
||||
if (digitsStr.isEmpty()) {
|
||||
result = ParsedSection(Intermediate, 0, used);
|
||||
} else {
|
||||
for (int i = 0; i < sectiontextSize; ++i) {
|
||||
if (!sectionTextRef.at(i).isDigit())
|
||||
sectiontextSize = i; // which exits the loop
|
||||
}
|
||||
|
||||
const int absMax = absoluteMax(sectionIndex);
|
||||
const QLocale loc = locale();
|
||||
bool ok = true;
|
||||
int last = -1, used = -1;
|
||||
const int absMax = absoluteMax(sectionIndex);
|
||||
const int absMin = absoluteMin(sectionIndex);
|
||||
|
||||
Q_ASSERT(sectiontextSize <= sectionmaxsize);
|
||||
QStringView digitsStr = sectionTextRef.first(sectiontextSize);
|
||||
for (int digits = sectiontextSize; digits >= 1; --digits) {
|
||||
digitsStr.truncate(digits);
|
||||
int tmp = int(loc.toUInt(digitsStr, &ok));
|
||||
if (ok && sn.type == Hour12Section) {
|
||||
if (tmp > 12) {
|
||||
tmp = -1;
|
||||
ok = false;
|
||||
} else if (tmp == 12) {
|
||||
tmp = 0;
|
||||
}
|
||||
}
|
||||
if (ok && tmp <= absMax) {
|
||||
QDTPDEBUG << sectionTextRef.first(digits) << tmp << digits;
|
||||
last = tmp;
|
||||
used = digits;
|
||||
break;
|
||||
int last = -1;
|
||||
|
||||
for (; digitsStr.size(); digitsStr.chop(1)) {
|
||||
bool ok = false;
|
||||
int value = int(loc.toUInt(digitsStr, &ok));
|
||||
if (!ok || (negate ? -value < absMin : value > absMax))
|
||||
continue;
|
||||
|
||||
if (sn.type == Hour12Section) {
|
||||
if (value > 12)
|
||||
continue;
|
||||
if (value == 12)
|
||||
value = 0;
|
||||
}
|
||||
|
||||
QDTPDEBUG << digitsStr << value << digitsStr.size();
|
||||
last = value;
|
||||
used += digitsStr.size();
|
||||
break;
|
||||
}
|
||||
|
||||
if (last == -1) {
|
||||
QChar first(sectionTextRef.at(0));
|
||||
if (separators.at(sectionIndex + 1).startsWith(first))
|
||||
result = ParsedSection(Intermediate, 0, used);
|
||||
const auto &sep = separators.at(sectionIndex + 1);
|
||||
if (sep.startsWith(sectionTextRef[0])
|
||||
|| (negate && sep.startsWith(m_text.at(offset))))
|
||||
result = ParsedSection(Intermediate, 0, 0);
|
||||
else
|
||||
QDTPDEBUG << "invalid because" << sectionTextRef << "can't become a uint" << last << ok;
|
||||
QDTPDEBUG << "invalid because" << sectionTextRef << "can't become a uint"
|
||||
<< last;
|
||||
} else {
|
||||
if (negate)
|
||||
last = -last;
|
||||
const FieldInfo fi = fieldInfo(sectionIndex);
|
||||
const bool unfilled = used < sectionmaxsize;
|
||||
const bool unfilled = used - negativeYearOffset < sectionmaxsize;
|
||||
if (unfilled && fi & Fraction) { // typing 2 in a zzz field should be .200, not .002
|
||||
for (int i = used; i < sectionmaxsize; ++i)
|
||||
last *= 10;
|
||||
}
|
||||
// Even those *= 10s can't take last above absMax:
|
||||
Q_ASSERT(last <= absMax);
|
||||
const int absMin = absoluteMin(sectionIndex);
|
||||
if (last < absMin) {
|
||||
if (unfilled)
|
||||
Q_ASSERT(negate ? last >= absMin : last <= absMax);
|
||||
if (negate ? last > absMax : last < absMin) {
|
||||
if (unfilled) {
|
||||
result = ParsedSection(Intermediate, last, used);
|
||||
else
|
||||
QDTPDEBUG << "invalid because" << last << "is less than absoluteMin" << absMin;
|
||||
} else if (unfilled && (fi & (FixedWidth|Numeric)) == (FixedWidth|Numeric)) {
|
||||
} else if (negate) {
|
||||
QDTPDEBUG << "invalid because" << last << "is greater than absoluteMax"
|
||||
<< absMax;
|
||||
} else {
|
||||
QDTPDEBUG << "invalid because" << last << "is less than absoluteMin"
|
||||
<< absMin;
|
||||
}
|
||||
|
||||
} else if (unfilled && (fi & (FixedWidth | Numeric)) == (FixedWidth | Numeric)) {
|
||||
if (skipToNextSection(sectionIndex, currentValue, digitsStr)) {
|
||||
const int missingZeroes = sectionmaxsize - digitsStr.size();
|
||||
result = ParsedSection(Acceptable, last, sectionmaxsize, missingZeroes);
|
||||
|
|
|
|||
|
|
@ -1297,6 +1297,78 @@ void tst_QDate::fromStringFormat_data()
|
|||
<< QString(u8"2020🤣09🤣21") << QString(u8"yyyy🤣MM🤣dd") << QDate(2020, 9, 21);
|
||||
QTest::newRow("Unicode in quoted format string")
|
||||
<< QString(u8"🤣🤣2020👍09🤣21") << QString(u8"'🤣🤣'yyyy👍MM🤣dd") << QDate(2020, 9, 21);
|
||||
|
||||
// QTBUG-84334
|
||||
QTest::newRow("-ve year: front, nosep")
|
||||
<< QString("-20060521") << QString("yyyyMMdd") << QDate(-2006, 5, 21);
|
||||
QTest::newRow("-ve year: mid, nosep")
|
||||
<< QString("05-200621") << QString("MMyyyydd") << QDate(-2006, 5, 21);
|
||||
QTest::newRow("-ve year: back, nosep")
|
||||
<< QString("0521-2006") << QString("MMddyyyy") << QDate(-2006, 5, 21);
|
||||
// - as separator should not interfere with negative year numbers:
|
||||
QTest::newRow("-ve year: front, dash")
|
||||
<< QString("-2006-05-21") << QString("yyyy-MM-dd") << QDate(-2006, 5, 21);
|
||||
QTest::newRow("positive year: front, dash")
|
||||
<< QString("-2006-05-21") << QString("-yyyy-MM-dd") << QDate(2006, 5, 21);
|
||||
QTest::newRow("-ve year: mid, dash")
|
||||
<< QString("05--2006-21") << QString("MM-yyyy-dd") << QDate(-2006, 5, 21);
|
||||
QTest::newRow("-ve year: back, dash")
|
||||
<< QString("05-21--2006") << QString("MM-dd-yyyy") << QDate(-2006, 5, 21);
|
||||
// negative three digit year numbers should be rejected:
|
||||
QTest::newRow("-ve 3digit year: front")
|
||||
<< QString("-206-05-21") << QString("yyyy-MM-dd") << QDate();
|
||||
QTest::newRow("-ve 3digit year: mid")
|
||||
<< QString("05--206-21") << QString("MM-yyyy-dd") << QDate();
|
||||
QTest::newRow("-ve 3digit year: back")
|
||||
<< QString("05-21--206") << QString("MM-dd-yyyy") << QDate();
|
||||
// negative month numbers should be rejected:
|
||||
QTest::newRow("-ve 2digit month: mid")
|
||||
<< QString("2060--05-21") << QString("yyyy-MM-dd") << QDate();
|
||||
QTest::newRow("-ve 2digit month: front")
|
||||
<< QString("-05-2060-21") << QString("MM-yyyy-dd") << QDate();
|
||||
QTest::newRow("-ve 2digit month: back")
|
||||
<< QString("21-2060--05") << QString("dd-yyyy-MM") << QDate();
|
||||
// negative single digit month numbers should be rejected:
|
||||
QTest::newRow("-ve 1digit month: mid")
|
||||
<< QString("2060--5-21") << QString("yyyy-MM-dd") << QDate();
|
||||
QTest::newRow("-ve 1digit month: front")
|
||||
<< QString("-5-2060-21") << QString("MM-yyyy-dd") << QDate();
|
||||
QTest::newRow("-ve 1digit month: back")
|
||||
<< QString("21-2060--5") << QString("dd-yyyy-MM") << QDate();
|
||||
// negative day numbers should be rejected:
|
||||
QTest::newRow("-ve 2digit day: front")
|
||||
<< QString("-21-2060-05") << QString("dd-yyyy-MM") << QDate();
|
||||
QTest::newRow("-ve 2digit day: mid")
|
||||
<< QString("2060--21-05") << QString("yyyy-dd-MM") << QDate();
|
||||
QTest::newRow("-ve 2digit day: back")
|
||||
<< QString("05-2060--21") << QString("MM-yyyy-dd") << QDate();
|
||||
// negative single digit day numbers should be rejected:
|
||||
QTest::newRow("-ve 1digit day: front")
|
||||
<< QString("-2-2060-05") << QString("dd-yyyy-MM") << QDate();
|
||||
QTest::newRow("-ve 1digit day: mid")
|
||||
<< QString("05--2-2060") << QString("MM-dd-yyyy") << QDate();
|
||||
QTest::newRow("-ve 1digit day: back")
|
||||
<< QString("2060-05--2") << QString("yyyy-MM-dd") << QDate();
|
||||
// positive three digit year numbers should be rejected:
|
||||
QTest::newRow("3digit year, front") << QString("206-05-21") << QString("yyyy-MM-dd") << QDate();
|
||||
QTest::newRow("3digit year, mid") << QString("05-206-21") << QString("MM-yyyy-dd") << QDate();
|
||||
QTest::newRow("3digit year, back") << QString("05-21-206") << QString("MM-dd-yyyy") << QDate();
|
||||
// positive five digit year numbers should be rejected:
|
||||
QTest::newRow("5digit year, front")
|
||||
<< QString("00206-05-21") << QString("yyyy-MM-dd") << QDate();
|
||||
QTest::newRow("5digit year, mid") << QString("05-00206-21") << QString("MM-yyyy-dd") << QDate();
|
||||
QTest::newRow("5digit year, back")
|
||||
<< QString("05-21-00206") << QString("MM-dd-yyyy") << QDate();
|
||||
|
||||
QTest::newRow("dash separator, no year at end")
|
||||
<< QString("05-21-") << QString("dd-MM-yyyy") << QDate();
|
||||
QTest::newRow("slash separator, no year at end")
|
||||
<< QString("11/05/") << QString("d/MM/yyyy") << QDate();
|
||||
|
||||
// QTBUG-84349
|
||||
QTest::newRow("+ sign in year field") << QString("+0200322") << QString("yyyyMMdd") << QDate();
|
||||
QTest::newRow("+ sign in month field") << QString("2020+322") << QString("yyyyMMdd") << QDate();
|
||||
QTest::newRow("+ sign in day field") << QString("202003+1") << QString("yyyyMMdd") << QDate();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1345,7 +1417,7 @@ void tst_QDate::toStringDateFormat_data()
|
|||
QTest::newRow("data2") << QDate(111,1,1) << Qt::ISODate << QString("0111-01-01");
|
||||
QTest::newRow("data3") << QDate(1974,12,1) << Qt::ISODate << QString("1974-12-01");
|
||||
QTest::newRow("year < 0") << QDate(-1,1,1) << Qt::ISODate << QString();
|
||||
QTest::newRow("year > 9999") << QDate(-1,1,1) << Qt::ISODate << QString();
|
||||
QTest::newRow("year > 9999") << QDate(10000, 1, 1) << Qt::ISODate << QString();
|
||||
QTest::newRow("RFC2822Date") << QDate(1974,12,1) << Qt::RFC2822Date << QString("01 Dec 1974");
|
||||
QTest::newRow("ISODateWithMs") << QDate(1974,12,1) << Qt::ISODateWithMs << QString("1974-12-01");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2797,6 +2797,11 @@ void tst_QDateTime::fromStringStringFormat_data()
|
|||
QTest::newRow("unicode handling") << QString(u8"2005🤣06🤣28T07🤣57🤣30.001Z")
|
||||
<< QString(u8"yyyy🤣MM🤣ddThh🤣mm🤣ss.zt")
|
||||
<< QDateTime(QDate(2005, 06, 28), QTime(07, 57, 30, 1), Qt::UTC);
|
||||
|
||||
// QTBUG-84349
|
||||
QTest::newRow("QTBUG-84349: positive sign in month")
|
||||
<< QStringLiteral("9922+221102233Z") << QStringLiteral("yyyyMMddHHmmsst")
|
||||
<< QDateTime();
|
||||
}
|
||||
|
||||
void tst_QDateTime::fromStringStringFormat()
|
||||
|
|
|
|||
|
|
@ -95,6 +95,32 @@ void tst_QDateTimeParser::parseSection_data()
|
|||
QTest::newRow("short-year-middle")
|
||||
<< "MM-yyyy-dd" << "12-200-15" << 1 << 3
|
||||
<< ParsedSection(State::Intermediate, 200, 3, 0);
|
||||
|
||||
QTest::newRow("negative-year-middle-5")
|
||||
<< "MM-yyyy-dd" << "12--2000-15" << 1 << 3
|
||||
<< ParsedSection(State::Acceptable, -2000, 5, 0);
|
||||
|
||||
QTest::newRow("short-negative-year-middle-4")
|
||||
<< "MM-yyyy-dd" << "12--200-15" << 1 << 3
|
||||
<< ParsedSection(State::Intermediate, -200, 4, 0);
|
||||
|
||||
QTest::newRow("short-negative-year-middle-3")
|
||||
<< "MM-yyyy-dd" << "12--20-15" << 1 << 3
|
||||
<< ParsedSection(State::Intermediate, -20, 3, 0);
|
||||
|
||||
QTest::newRow("short-negative-year-middle-2")
|
||||
<< "MM-yyyy-dd" << "12--2-15" << 1 << 3
|
||||
<< ParsedSection(State::Intermediate, -2, 2, 0);
|
||||
|
||||
QTest::newRow("short-negative-year-middle-1")
|
||||
<< "MM-yyyy-dd" << "12---15" << 1 << 3
|
||||
<< ParsedSection(State::Intermediate, 0, 1, 0);
|
||||
|
||||
// Here the -15 will be understood as year, with separator and day omitted,
|
||||
// although it could equally be read as month and day with missing year.
|
||||
QTest::newRow("short-negative-year-middle-0")
|
||||
<< "MM-yyyy-dd" << "12--15" << 1 << 3
|
||||
<< ParsedSection(State::Intermediate, -15, 3, 0);
|
||||
}
|
||||
|
||||
void tst_QDateTimeParser::parseSection()
|
||||
|
|
|
|||
Loading…
Reference in New Issue