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:
parent
6c45b1817f
commit
7605451604
@ -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
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user