From c00ee2f31013e99c79b820a0db57003c110a5510 Mon Sep 17 00:00:00 2001 From: Edward Welbourne Date: Tue, 1 Jun 2021 18:27:48 +0200 Subject: [PATCH] Use UTC when parsing only a date or only a time, not a date-time This should reduce the amount of fall-out from DST complications. Also document the assumptions of QDateTimeParser's two fromString() methods (and fix the punctuation on the QDateTime parameter). Adjusted some tests to match. Since only QDateTime-returning methods will show the difference, and it's at least somewhat odd to be using those on QDateEdit or QTimeEdit, this should have little impact on API users. [ChangeLog][QtCore][Behavior Change] QDateEdit and QTimeEdit now operate in UTC, to avoid spurious complications arising from time-zone transitions (e.g. DST) causing the implicit other half to combine with the part being edited to make an invalid result. Returns from their dateTime() and other methods returning QDateTime (max/min) shall thus be in UTC where previously they were in local time. QDateTimeEdit continues using local time. The default can be over-ridden by setTimeSpec(), as ever. Change-Id: I44fece004c12342fe536bbe3048217d236fd97b2 Reviewed-by: Qt CI Bot Reviewed-by: Thiago Macieira --- src/corelib/time/qdatetimeparser.cpp | 15 ++++++++++----- src/widgets/widgets/qdatetimeedit.cpp | 13 ++++++++----- src/widgets/widgets/qdatetimeedit_p.h | 4 ++-- .../widgets/qdatetimeedit/tst_qdatetimeedit.cpp | 4 ++-- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/corelib/time/qdatetimeparser.cpp b/src/corelib/time/qdatetimeparser.cpp index 9117e743750..e699bdeb240 100644 --- a/src/corelib/time/qdatetimeparser.cpp +++ b/src/corelib/time/qdatetimeparser.cpp @@ -2198,21 +2198,25 @@ QString QDateTimeParser::stateName(State s) const } } +// Only called when we want only one of date or time; use UTC to avoid bogus DST issues. bool QDateTimeParser::fromString(const QString &t, QDate *date, QTime *time) const { - QDateTime datetime; - if (!fromString(t, &datetime)) + QDateTime val(QDate(1900, 1, 1).startOfDay(Qt::UTC)); + const StateNode tmp = parse(t, -1, val, false); + if (tmp.state != Acceptable || tmp.conflicts) return false; if (time) { - const QTime t = datetime.time(); + Q_ASSERT(!date); + const QTime t = tmp.value.time(); if (!t.isValid()) return false; *time = t; } if (date) { - const QDate d = datetime.date(); + Q_ASSERT(!time); + const QDate d = tmp.value.date(); if (!d.isValid()) return false; *date = d; @@ -2220,7 +2224,8 @@ bool QDateTimeParser::fromString(const QString &t, QDate *date, QTime *time) con return true; } -bool QDateTimeParser::fromString(const QString &t, QDateTime* datetime) const +// Only called when we want both date and time; default to local time. +bool QDateTimeParser::fromString(const QString &t, QDateTime *datetime) const { QDateTime val(QDate(1900, 1, 1).startOfDay()); const StateNode tmp = parse(t, -1, val, false); diff --git a/src/widgets/widgets/qdatetimeedit.cpp b/src/widgets/widgets/qdatetimeedit.cpp index b150827a4d0..81b70b5f903 100644 --- a/src/widgets/widgets/qdatetimeedit.cpp +++ b/src/widgets/widgets/qdatetimeedit.cpp @@ -223,7 +223,9 @@ QDateTimeEdit::QDateTimeEdit(QTime time, QWidget *parent) \internal */ QDateTimeEdit::QDateTimeEdit(const QVariant &var, QMetaType::Type parserType, QWidget *parent) - : QAbstractSpinBox(*new QDateTimeEditPrivate, parent) + : QAbstractSpinBox(*new QDateTimeEditPrivate( + parserType == QMetaType::QDateTime ? Qt::LocalTime : Qt::UTC), + parent) { Q_D(QDateTimeEdit); d->parserType = parserType; @@ -1734,16 +1736,17 @@ QDateEdit::~QDateEdit() */ -QDateTimeEditPrivate::QDateTimeEditPrivate() - : QDateTimeParser(QMetaType::QDateTime, QDateTimeParser::DateTimeEdit, QCalendar()) +QDateTimeEditPrivate::QDateTimeEditPrivate(Qt::TimeSpec timeSpec) + : QDateTimeParser(QMetaType::QDateTime, QDateTimeParser::DateTimeEdit, QCalendar()), + spec(timeSpec) { fixday = true; type = QMetaType::QDateTime; currentSectionIndex = FirstSectionIndex; first.pos = 0; - minimum = QDATETIMEEDIT_COMPAT_DATE_MIN.startOfDay(); - maximum = QDATETIMEEDIT_DATE_MAX.endOfDay(); + minimum = QDATETIMEEDIT_COMPAT_DATE_MIN.startOfDay(spec); + maximum = QDATETIMEEDIT_DATE_MAX.endOfDay(spec); readLocaleSettings(); } diff --git a/src/widgets/widgets/qdatetimeedit_p.h b/src/widgets/widgets/qdatetimeedit_p.h index 5830caaba3e..550653d731b 100644 --- a/src/widgets/widgets/qdatetimeedit_p.h +++ b/src/widgets/widgets/qdatetimeedit_p.h @@ -70,7 +70,7 @@ class Q_AUTOTEST_EXPORT QDateTimeEditPrivate : public QAbstractSpinBoxPrivate, p { Q_DECLARE_PUBLIC(QDateTimeEdit) public: - QDateTimeEditPrivate(); + QDateTimeEditPrivate(Qt::TimeSpec timeSpec = Qt::LocalTime); void init(const QVariant &var); void readLocaleSettings(); @@ -153,7 +153,7 @@ public: bool focusOnButton = false; #endif - Qt::TimeSpec spec = Qt::LocalTime; + Qt::TimeSpec spec; }; diff --git a/tests/auto/widgets/widgets/qdatetimeedit/tst_qdatetimeedit.cpp b/tests/auto/widgets/widgets/qdatetimeedit/tst_qdatetimeedit.cpp index ad16fcc79fd..9211800eb34 100644 --- a/tests/auto/widgets/widgets/qdatetimeedit/tst_qdatetimeedit.cpp +++ b/tests/auto/widgets/widgets/qdatetimeedit/tst_qdatetimeedit.cpp @@ -1358,7 +1358,7 @@ void tst_QDateTimeEdit::editingRanged_data() << QDate(2010, 12, 30) << QTime() << QDate(2011, 1, 2) << QTime() << QString::fromLatin1("01012011") - << QDateTime(QDate(2011, 1, 1), QTime()); + << QDateTime(QDate(2011, 1, 1), QTime(), Qt::UTC); } void tst_QDateTimeEdit::editingRanged() @@ -3141,9 +3141,9 @@ void tst_QDateTimeEdit::hour12Test() void tst_QDateTimeEdit::yyTest() { testWidget->setDisplayFormat("dd-MMM-yy"); - testWidget->setTime(QTime(0, 0, 0)); testWidget->setDateRange(QDate(2005, 1, 1), QDate(2010, 12, 31)); testWidget->setDate(testWidget->minimumDate()); + testWidget->setTime(QTime(12, 0, 0)); // Mid-day to avoid DST artefacts. testWidget->setCurrentSection(QDateTimeEdit::YearSection); QString jan = QLocale::system().monthName(1, QLocale::ShortFormat);