Handle specified time-spec in date-time parsing

When a date-time was parsed from a string, the result was equal (as a
date-time) to the correct value, but had (at least in some cases) the
wrong spec, where it should have had a spec reflecting the zone
specifier parsed.

The time-spec imposed for the benefit of QDateTimeEdit is now moved
from QDateTimeParser to QDateTimeEditPrivate, which takes over
responsibility for imposing it. QDateTimeParser assumes Qt::LocalTime
in member functions (where applicable) and uses the time-spec parsed
from the string when constructing the date-time.

QDateTime::fromString() and QLocale::toDateTime() are updated to
use the full QDateTime returned by QDateTimeParser.

Fixes: QTBUG-83075
Done-With: Edward Welbourne <edward.welbourne@qt.io>
Change-Id: I8b79add2c7fc13a200e1252d48dbfa70b36757bf
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Andrei Golubev 2020-03-25 15:48:52 +01:00
parent 6c45b1817f
commit 7605451604
7 changed files with 73 additions and 39 deletions

View File

@ -2559,19 +2559,18 @@ QDateTime QLocale::toDateTime(const QString &string, const QString &format) cons
QDateTime QLocale::toDateTime(const QString &string, const QString &format, QCalendar cal) const
{
#if QT_CONFIG(datetimeparser)
QTime time;
QDate date;
QDateTime datetime;
QDateTimeParser dt(QMetaType::QDateTime, QDateTimeParser::FromString, cal);
dt.setDefaultLocale(*this);
if (dt.parseFormat(format) && dt.fromString(string, &date, &time))
return QDateTime(date, time);
if (dt.parseFormat(format) && dt.fromString(string, &datetime))
return datetime;
#else
Q_UNUSED(string);
Q_UNUSED(format);
Q_UNUSED(cal);
#endif
return QDateTime(QDate(), QTime(-1, -1, -1));
return QDateTime();
}
#endif // datestring

View File

@ -5537,13 +5537,12 @@ QT_WARNING_POP
QDateTime QDateTime::fromString(const QString &string, const QString &format, QCalendar cal)
{
#if QT_CONFIG(datetimeparser)
QTime time;
QDate date;
QDateTime datetime;
QDateTimeParser dt(QMetaType::QDateTime, QDateTimeParser::FromString, cal);
// dt.setDefaultLocale(QLocale::c()); ### Qt 6
if (dt.parseFormat(format) && dt.fromString(string, &date, &time))
return QDateTime(date, time);
if (dt.parseFormat(format) && dt.fromString(string, &datetime))
return datetime;
#else
Q_UNUSED(string);
Q_UNUSED(format);

View File

@ -1207,12 +1207,16 @@ QDateTimeParser::scanString(const QDateTime &defaultValue,
Q_ASSERT(!zoneName.isEmpty()); // sect.used > 0
const QByteArray latinZone(zoneName == QLatin1String("Z")
? QByteArray("UTC") : zoneName.toLatin1());
timeZone = QTimeZone(latinZone);
tspec = timeZone.isValid()
? (QTimeZone::isTimeZoneIdAvailable(latinZone)
? Qt::TimeZone
: Qt::OffsetFromUTC)
: (Q_ASSERT(startsWithLocalTimeZone(zoneName)), Qt::LocalTime);
if (latinZone.startsWith("UTC") &&
(latinZone.size() == 3 || latinZone.at(3) == '+' || latinZone.at(3) == '-' )) {
timeZone = QTimeZone(sect.value);
tspec = sect.value ? Qt::OffsetFromUTC : Qt::UTC;
} else {
timeZone = QTimeZone(latinZone);
tspec = timeZone.isValid()
? Qt::TimeZone
: (Q_ASSERT(startsWithLocalTimeZone(zoneName)), Qt::LocalTime);
}
#else
tspec = Qt::LocalTime;
#endif
@ -1537,12 +1541,10 @@ QDateTimeParser::parse(QString input, int position, const QDateTime &defaultValu
}
}
text = scan.input = input;
// Set spec *after* all checking, so validity is a property of the string:
scan.value = scan.value.toTimeSpec(spec);
/*
However, even with a valid string we might have ended up with an invalid datetime:
the non-existent hour during dst changes, for instance.
We might have ended up with an invalid datetime: the non-existent hour
during dst changes, for instance.
*/
if (!scan.value.isValid() && scan.state == Acceptable)
scan.state = Intermediate;
@ -2018,13 +2020,12 @@ QString QDateTimeParser::stateName(State s) const
#if QT_CONFIG(datestring)
bool QDateTimeParser::fromString(const QString &t, QDate *date, QTime *time) const
{
QDateTime val(QDate(1900, 1, 1).startOfDay());
const StateNode tmp = parse(t, -1, val, false);
if (tmp.state != Acceptable || tmp.conflicts) {
QDateTime datetime;
if (!fromString(t, &datetime))
return false;
}
if (time) {
const QTime t = tmp.value.time();
const QTime t = datetime.time();
if (!t.isValid()) {
return false;
}
@ -2032,7 +2033,7 @@ bool QDateTimeParser::fromString(const QString &t, QDate *date, QTime *time) con
}
if (date) {
const QDate d = tmp.value.date();
const QDate d = datetime.date();
if (!d.isValid()) {
return false;
}
@ -2040,26 +2041,43 @@ bool QDateTimeParser::fromString(const QString &t, QDate *date, QTime *time) con
}
return true;
}
bool QDateTimeParser::fromString(const QString &t, QDateTime* datetime) const
{
QDateTime val(QDate(1900, 1, 1).startOfDay());
const StateNode tmp = parse(t, -1, val, false);
if (tmp.state != Acceptable || tmp.conflicts)
return false;
if (datetime) {
if (!tmp.value.isValid())
return false;
*datetime = tmp.value;
}
return true;
}
#endif // datestring
QDateTime QDateTimeParser::getMinimum() const
{
// Cache the most common case
if (spec == Qt::LocalTime) {
static const QDateTime localTimeMin(QDATETIMEEDIT_DATE_MIN.startOfDay(Qt::LocalTime));
return localTimeMin;
}
return QDateTime(QDATETIMEEDIT_DATE_MIN.startOfDay(spec));
// NB: QDateTimeParser always uses Qt::LocalTime time spec by default. If
// any subclass needs a changing time spec, it must override this
// method. At the time of writing, this is done by QDateTimeEditPrivate.
// Cache the only case
static const QDateTime localTimeMin(QDATETIMEEDIT_DATE_MIN.startOfDay(Qt::LocalTime));
return localTimeMin;
}
QDateTime QDateTimeParser::getMaximum() const
{
// Cache the most common case
if (spec == Qt::LocalTime) {
static const QDateTime localTimeMax(QDATETIMEEDIT_DATE_MAX.endOfDay(Qt::LocalTime));
return localTimeMax;
}
return QDateTime(QDATETIMEEDIT_DATE_MAX.endOfDay(spec));
// NB: QDateTimeParser always uses Qt::LocalTime time spec by default. If
// any subclass needs a changing time spec, it must override this
// method. At the time of writing, this is done by QDateTimeEditPrivate.
// Cache the only case
static const QDateTime localTimeMax(QDATETIMEEDIT_DATE_MAX.endOfDay(Qt::LocalTime));
return localTimeMax;
}
QString QDateTimeParser::getAmPmText(AmPm ap, Case cs) const

View File

@ -85,7 +85,7 @@ public:
};
QDateTimeParser(QMetaType::Type t, Context ctx, const QCalendar &cal = QCalendar())
: currentSectionIndex(-1), cachedDay(-1), parserType(t),
fixday(false), spec(Qt::LocalTime), context(ctx), calendar(cal)
fixday(false), context(ctx), calendar(cal)
{
defaultLocale = QLocale::system();
first.type = FirstSection;
@ -181,6 +181,7 @@ public:
#if QT_CONFIG(datestring)
StateNode parse(QString input, int position, const QDateTime &defaultValue, bool fixup) const;
bool fromString(const QString &text, QDate *date, QTime *time) const;
bool fromString(const QString &text, QDateTime* datetime) const;
#endif
bool parseFormat(const QString &format);
@ -297,7 +298,6 @@ protected: // for the benefit of QDateTimeEditPrivate
QLocale defaultLocale;
QMetaType::Type parserType;
bool fixday;
Qt::TimeSpec spec; // spec if used by QDateTimeEdit
Context context;
QCalendar calendar;
};

View File

@ -1976,7 +1976,14 @@ QDateTime QDateTimeEditPrivate::validateAndInterpret(QString &input, int &positi
return minimum.toDateTime();
}
}
StateNode tmp = parse(input, position, value.toDateTime(), fixup);
// Impose this widget's spec:
tmp.value = tmp.value.toTimeSpec(spec);
// ... but that might turn a valid datetime into an invalid one:
if (!tmp.value.isValid() && tmp.state == Acceptable)
tmp.state = Intermediate;
input = tmp.input;
position += tmp.padded;
state = QValidator::State(int(tmp.state));

View File

@ -98,12 +98,16 @@ public:
{
if (keyboardTracking)
return minimum.toDateTime();
if (spec != Qt::LocalTime)
return QDateTime(QDATETIMEEDIT_DATE_MIN.startOfDay(spec));
return QDateTimeParser::getMinimum();
}
QDateTime getMaximum() const override
{
if (keyboardTracking)
return maximum.toDateTime();
if (spec != Qt::LocalTime)
return QDateTime(QDATETIMEEDIT_DATE_MIN.startOfDay(spec));
return QDateTimeParser::getMaximum();
}
QLocale locale() const override { return q_func()->locale(); }
@ -148,6 +152,8 @@ public:
#ifdef QT_KEYPAD_NAVIGATION
bool focusOnButton;
#endif
Qt::TimeSpec spec = Qt::LocalTime;
};

View File

@ -2554,6 +2554,11 @@ void tst_QDateTime::fromStringStringFormat()
QDateTime dt = QDateTime::fromString(string, format);
if (expected.isValid()) {
QCOMPARE(dt.timeSpec(), expected.timeSpec());
if (expected.timeSpec() == Qt::TimeZone)
QCOMPARE(dt.timeZone(), expected.timeZone());
}
QCOMPARE(dt, expected);
}