summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/corelib/doc/snippets/code/src_corelib_tools_qdatetime.cpp2
-rw-r--r--src/corelib/tools/qdatetime.cpp271
-rw-r--r--src/corelib/tools/qdatetime.h10
-rw-r--r--src/corelib/tools/qdatetimeparser.cpp10
-rw-r--r--src/corelib/tools/qdatetimeparser_p.h7
-rw-r--r--src/widgets/widgets/qabstractspinbox.cpp2
-rw-r--r--src/widgets/widgets/qdatetimeedit.cpp19
-rw-r--r--tests/auto/corelib/tools/qdate/qdate.pro2
-rw-r--r--tests/auto/corelib/tools/qdate/tst_qdate.cpp168
9 files changed, 462 insertions, 29 deletions
diff --git a/src/corelib/doc/snippets/code/src_corelib_tools_qdatetime.cpp b/src/corelib/doc/snippets/code/src_corelib_tools_qdatetime.cpp
index 3ecb67a48f..a477e91548 100644
--- a/src/corelib/doc/snippets/code/src_corelib_tools_qdatetime.cpp
+++ b/src/corelib/doc/snippets/code/src_corelib_tools_qdatetime.cpp
@@ -128,7 +128,7 @@ qDebug("Time elapsed: %d ms", t.elapsed());
//! [11]
QDateTime now = QDateTime::currentDateTime();
-QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0));
+QDateTime xmas(QDate(now.date().year(), 12, 25).startOfDay());
qDebug("There are %d seconds to Christmas", now.secsTo(xmas));
//! [11]
diff --git a/src/corelib/tools/qdatetime.cpp b/src/corelib/tools/qdatetime.cpp
index 6fa735dab7..cc98f80feb 100644
--- a/src/corelib/tools/qdatetime.cpp
+++ b/src/corelib/tools/qdatetime.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2016 Intel Corporation.
** Contact: https://www.qt.io/licensing/
**
@@ -617,6 +617,268 @@ int QDate::weekNumber(int *yearNumber) const
return week;
}
+static bool inDateTimeRange(qint64 jd, bool start)
+{
+ using Bounds = std::numeric_limits<qint64>;
+ if (jd < Bounds::min() + JULIAN_DAY_FOR_EPOCH)
+ return false;
+ jd -= JULIAN_DAY_FOR_EPOCH;
+ const qint64 maxDay = Bounds::max() / MSECS_PER_DAY;
+ const qint64 minDay = Bounds::min() / MSECS_PER_DAY - 1;
+ // (Divisions rounded towards zero, as MSECS_PER_DAY has factors other than two.)
+ // Range includes start of last day and end of first:
+ if (start)
+ return jd > minDay && jd <= maxDay;
+ return jd >= minDay && jd < maxDay;
+}
+
+static QDateTime toEarliest(const QDate &day, const QDateTime &form)
+{
+ const Qt::TimeSpec spec = form.timeSpec();
+ const int offset = (spec == Qt::OffsetFromUTC) ? form.offsetFromUtc() : 0;
+#if QT_CONFIG(timezone)
+ QTimeZone zone;
+ if (spec == Qt::TimeZone)
+ zone = form.timeZone();
+#endif
+ auto moment = [=](QTime time) {
+ switch (spec) {
+ case Qt::OffsetFromUTC: return QDateTime(day, time, spec, offset);
+#if QT_CONFIG(timezone)
+ case Qt::TimeZone: return QDateTime(day, time, zone);
+#endif
+ default: return QDateTime(day, time, spec);
+ }
+ };
+ // Longest routine time-zone transition is 2 hours:
+ QDateTime when = moment(QTime(2, 0));
+ if (!when.isValid()) {
+ // Noon should be safe ...
+ when = moment(QTime(12, 0));
+ if (!when.isValid()) {
+ // ... unless it's a 24-hour jump (moving the date-line)
+ when = moment(QTime(23, 59, 59, 999));
+ if (!when.isValid())
+ return QDateTime();
+ }
+ }
+ int high = when.time().msecsSinceStartOfDay() / 60000;
+ int low = 0;
+ // Binary chop to the right minute
+ while (high > low + 1) {
+ int mid = (high + low) / 2;
+ QDateTime probe = moment(QTime(mid / 60, mid % 60));
+ if (probe.isValid() && probe.date() == day) {
+ high = mid;
+ when = probe;
+ } else {
+ low = mid;
+ }
+ }
+ return when;
+}
+
+/*!
+ \since 5.14
+ \fn QDateTime QDate::startOfDay(Qt::TimeSpec spec, int offsetSeconds) const
+ \fn QDateTime QDate::startOfDay(const QTimeZone &zone) const
+
+ Returns the start-moment of the day. Usually, this shall be midnight at the
+ start of the day: however, if a time-zone transition causes the given date
+ to skip over that midnight (e.g. a DST spring-forward skipping from the end
+ of the previous day to 01:00 of the new day), the actual earliest time in
+ the day is returned. This can only arise when the start-moment is specified
+ in terms of a time-zone (by passing its QTimeZone as \a zone) or in terms of
+ local time (by passing Qt::LocalTime as \a spec; this is its default).
+
+ The \a offsetSeconds is ignored unless \a spec is Qt::OffsetFromUTC, when it
+ gives the implied zone's offset from UTC. As UTC and such zones have no
+ transitions, the start of the day is QTime(0, 0) in these cases.
+
+ In the rare case of a date that was entirely skipped (this happens when a
+ zone east of the international date-line switches to being west of it), the
+ return shall be invalid. Passing Qt::TimeZone as \a spec (instead of
+ passing a QTimeZone) or passing an invalid time-zone as \a zone will also
+ produce an invalid result, as shall dates that start outside the range
+ representable by QDateTime.
+
+ \sa endOfDay()
+*/
+QDateTime QDate::startOfDay(Qt::TimeSpec spec, int offsetSeconds) const
+{
+ if (!inDateTimeRange(jd, true))
+ return QDateTime();
+
+ switch (spec) {
+ case Qt::TimeZone: // should pass a QTimeZone instead of Qt::TimeZone
+ qWarning() << "Called QDate::startOfDay(Qt::TimeZone) on" << *this;
+ return QDateTime();
+ case Qt::OffsetFromUTC:
+ case Qt::UTC:
+ return QDateTime(*this, QTime(0, 0), spec, offsetSeconds);
+
+ case Qt::LocalTime:
+ if (offsetSeconds)
+ qWarning("Ignoring offset (%d seconds) passed with Qt::LocalTime", offsetSeconds);
+ break;
+ }
+ QDateTime when(*this, QTime(0, 0), spec);
+ if (!when.isValid())
+ when = toEarliest(*this, when);
+
+ return when.isValid() ? when : QDateTime();
+}
+
+#if QT_CONFIG(timezone)
+/*!
+ \overload
+ \since 5.14
+*/
+QDateTime QDate::startOfDay(const QTimeZone &zone) const
+{
+ if (!inDateTimeRange(jd, true) || !zone.isValid())
+ return QDateTime();
+
+ QDateTime when(*this, QTime(0, 0), zone);
+ if (when.isValid())
+ return when;
+
+ // The start of the day must have fallen in a spring-forward's gap; find the spring-forward:
+ if (zone.hasTransitions()) {
+ QTimeZone::OffsetData tran = zone.previousTransition(QDateTime(*this, QTime(23, 59, 59, 999), zone));
+ const QDateTime &at = tran.atUtc.toTimeZone(zone);
+ if (at.isValid() && at.date() == *this)
+ return at;
+ }
+
+ when = toEarliest(*this, when);
+ return when.isValid() ? when : QDateTime();
+}
+#endif // timezone
+
+static QDateTime toLatest(const QDate &day, const QDateTime &form)
+{
+ const Qt::TimeSpec spec = form.timeSpec();
+ const int offset = (spec == Qt::OffsetFromUTC) ? form.offsetFromUtc() : 0;
+#if QT_CONFIG(timezone)
+ QTimeZone zone;
+ if (spec == Qt::TimeZone)
+ zone = form.timeZone();
+#endif
+ auto moment = [=](QTime time) {
+ switch (spec) {
+ case Qt::OffsetFromUTC: return QDateTime(day, time, spec, offset);
+#if QT_CONFIG(timezone)
+ case Qt::TimeZone: return QDateTime(day, time, zone);
+#endif
+ default: return QDateTime(day, time, spec);
+ }
+ };
+ // Longest routine time-zone transition is 2 hours:
+ QDateTime when = moment(QTime(21, 59, 59, 999));
+ if (!when.isValid()) {
+ // Noon should be safe ...
+ when = moment(QTime(12, 0));
+ if (!when.isValid()) {
+ // ... unless it's a 24-hour jump (moving the date-line)
+ when = moment(QTime(0, 0));
+ if (!when.isValid())
+ return QDateTime();
+ }
+ }
+ int high = 24 * 60;
+ int low = when.time().msecsSinceStartOfDay() / 60000;
+ // Binary chop to the right minute
+ while (high > low + 1) {
+ int mid = (high + low) / 2;
+ QDateTime probe = moment(QTime(mid / 60, mid % 60, 59, 999));
+ if (probe.isValid() && probe.date() == day) {
+ low = mid;
+ when = probe;
+ } else {
+ high = mid;
+ }
+ }
+ return when;
+}
+
+/*!
+ \since 5.14
+ \fn QDateTime QDate::endOfDay(Qt::TimeSpec spec, int offsetSeconds) const
+ \fn QDateTime QDate::endOfDay(const QTimeZone &zone) const
+
+ Returns the end-moment of the day. Usually, this is one millisecond before
+ the midnight at the end of the day: however, if a time-zone transition
+ causes the given date to skip over that midnight (e.g. a DST spring-forward
+ skipping from just before 23:00 to the start of the next day), the actual
+ latest time in the day is returned. This can only arise when the
+ start-moment is specified in terms of a time-zone (by passing its QTimeZone
+ as \a zone) or in terms of local time (by passing Qt::LocalTime as \a spec;
+ this is its default).
+
+ The \a offsetSeconds is ignored unless \a spec is Qt::OffsetFromUTC, when it
+ gives the implied zone's offset from UTC. As UTC and such zones have no
+ transitions, the end of the day is QTime(23, 59, 59, 999) in these cases.
+
+ In the rare case of a date that was entirely skipped (this happens when a
+ zone east of the international date-line switches to being west of it), the
+ return shall be invalid. Passing Qt::TimeZone as \a spec (instead of
+ passing a QTimeZone) will also produce an invalid result, as shall dates
+ that end outside the range representable by QDateTime.
+
+ \sa startOfDay()
+*/
+QDateTime QDate::endOfDay(Qt::TimeSpec spec, int offsetSeconds) const
+{
+ if (!inDateTimeRange(jd, false))
+ return QDateTime();
+
+ switch (spec) {
+ case Qt::TimeZone: // should pass a QTimeZone instead of Qt::TimeZone
+ qWarning() << "Called QDate::endOfDay(Qt::TimeZone) on" << *this;
+ return QDateTime();
+ case Qt::UTC:
+ case Qt::OffsetFromUTC:
+ return QDateTime(*this, QTime(23, 59, 59, 999), spec, offsetSeconds);
+
+ case Qt::LocalTime:
+ if (offsetSeconds)
+ qWarning("Ignoring offset (%d seconds) passed with Qt::LocalTime", offsetSeconds);
+ break;
+ }
+ QDateTime when(*this, QTime(23, 59, 59, 999), spec);
+ if (!when.isValid())
+ when = toLatest(*this, when);
+ return when.isValid() ? when : QDateTime();
+}
+
+#if QT_CONFIG(timezone)
+/*!
+ \overload
+ \since 5.14
+*/
+QDateTime QDate::endOfDay(const QTimeZone &zone) const
+{
+ if (!inDateTimeRange(jd, false) || !zone.isValid())
+ return QDateTime();
+
+ QDateTime when(*this, QTime(23, 59, 59, 999), zone);
+ if (when.isValid())
+ return when;
+
+ // The end of the day must have fallen in a spring-forward's gap; find the spring-forward:
+ if (zone.hasTransitions()) {
+ QTimeZone::OffsetData tran = zone.nextTransition(QDateTime(*this, QTime(0, 0), zone));
+ const QDateTime &at = tran.atUtc.toTimeZone(zone);
+ if (at.isValid() && at.date() == *this)
+ return at;
+ }
+
+ when = toLatest(*this, when);
+ return when.isValid() ? when : QDateTime();
+}
+#endif // timezone
+
#if QT_DEPRECATED_SINCE(5, 11) && QT_CONFIG(textdate)
/*!
\since 4.5
@@ -1468,7 +1730,8 @@ bool QDate::isLeapYear(int y)
\fn QTime::QTime()
Constructs a null time object. For a null time, isNull() returns \c true and
- isValid() returns \c false. If you need a zero time, use QTime(0, 0).
+ isValid() returns \c false. If you need a zero time, use QTime(0, 0). For
+ the start of a day, see QDate::startOfDay().
\sa isNull(), isValid()
*/
@@ -2392,8 +2655,8 @@ static void msecsToTime(qint64 msecs, QDate *date, QTime *time)
qint64 jd = JULIAN_DAY_FOR_EPOCH;
qint64 ds = 0;
- if (qAbs(msecs) >= MSECS_PER_DAY) {
- jd += (msecs / MSECS_PER_DAY);
+ if (msecs >= MSECS_PER_DAY || msecs <= -MSECS_PER_DAY) {
+ jd += msecs / MSECS_PER_DAY;
msecs %= MSECS_PER_DAY;
}
diff --git a/src/corelib/tools/qdatetime.h b/src/corelib/tools/qdatetime.h
index 51d5dd9759..8873651f17 100644
--- a/src/corelib/tools/qdatetime.h
+++ b/src/corelib/tools/qdatetime.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2016 Intel Corporation.
** Contact: https://www.qt.io/licensing/
**
@@ -55,6 +55,7 @@ Q_FORWARD_DECLARE_OBJC_CLASS(NSDate);
QT_BEGIN_NAMESPACE
class QTimeZone;
+class QDateTime;
class Q_CORE_EXPORT QDate
{
@@ -81,6 +82,13 @@ public:
int daysInYear() const;
int weekNumber(int *yearNum = nullptr) const;
+ QDateTime startOfDay(Qt::TimeSpec spec = Qt::LocalTime, int offsetSeconds = 0) const;
+ QDateTime endOfDay(Qt::TimeSpec spec = Qt::LocalTime, int offsetSeconds = 0) const;
+#if QT_CONFIG(timezone)
+ QDateTime startOfDay(const QTimeZone &zone) const;
+ QDateTime endOfDay(const QTimeZone &zone) const;
+#endif
+
#if QT_DEPRECATED_SINCE(5, 10) && QT_CONFIG(textdate)
QT_DEPRECATED_X("Use QLocale::monthName or QLocale::standaloneMonthName")
static QString shortMonthName(int month, MonthNameType type = DateFormat);
diff --git a/src/corelib/tools/qdatetimeparser.cpp b/src/corelib/tools/qdatetimeparser.cpp
index 5d1704daeb..e1dc596d2d 100644
--- a/src/corelib/tools/qdatetimeparser.cpp
+++ b/src/corelib/tools/qdatetimeparser.cpp
@@ -1982,7 +1982,7 @@ 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), QDATETIMEEDIT_TIME_MIN);
+ QDateTime val(QDate(1900, 1, 1).startOfDay());
const StateNode tmp = parse(t, -1, val, false);
if (tmp.state != Acceptable || tmp.conflicts) {
return false;
@@ -2010,20 +2010,20 @@ QDateTime QDateTimeParser::getMinimum() const
{
// Cache the most common case
if (spec == Qt::LocalTime) {
- static const QDateTime localTimeMin(QDATETIMEEDIT_DATE_MIN, QDATETIMEEDIT_TIME_MIN, Qt::LocalTime);
+ static const QDateTime localTimeMin(QDATETIMEEDIT_DATE_MIN.startOfDay(Qt::LocalTime));
return localTimeMin;
}
- return QDateTime(QDATETIMEEDIT_DATE_MIN, QDATETIMEEDIT_TIME_MIN, spec);
+ return QDateTime(QDATETIMEEDIT_DATE_MIN.startOfDay(spec));
}
QDateTime QDateTimeParser::getMaximum() const
{
// Cache the most common case
if (spec == Qt::LocalTime) {
- static const QDateTime localTimeMax(QDATETIMEEDIT_DATE_MAX, QDATETIMEEDIT_TIME_MAX, Qt::LocalTime);
+ static const QDateTime localTimeMax(QDATETIMEEDIT_DATE_MAX.endOfDay(Qt::LocalTime));
return localTimeMax;
}
- return QDateTime(QDATETIMEEDIT_DATE_MAX, QDATETIMEEDIT_TIME_MAX, spec);
+ return QDateTime(QDATETIMEEDIT_DATE_MAX.endOfDay(spec));
}
QString QDateTimeParser::getAmPmText(AmPm ap, Case cs) const
diff --git a/src/corelib/tools/qdatetimeparser_p.h b/src/corelib/tools/qdatetimeparser_p.h
index e244fed09a..d9e39f0795 100644
--- a/src/corelib/tools/qdatetimeparser_p.h
+++ b/src/corelib/tools/qdatetimeparser_p.h
@@ -65,14 +65,11 @@
QT_REQUIRE_CONFIG(datetimeparser);
-#define QDATETIMEEDIT_TIME_MIN QTime(0, 0, 0, 0)
-#define QDATETIMEEDIT_TIME_MAX QTime(23, 59, 59, 999)
+#define QDATETIMEEDIT_TIME_MIN QTime(0, 0) // Prefer QDate::startOfDay()
+#define QDATETIMEEDIT_TIME_MAX QTime(23, 59, 59, 999) // Prefer QDate::endOfDay()
#define QDATETIMEEDIT_DATE_MIN QDate(100, 1, 1)
#define QDATETIMEEDIT_COMPAT_DATE_MIN QDate(1752, 9, 14)
#define QDATETIMEEDIT_DATE_MAX QDate(9999, 12, 31)
-#define QDATETIMEEDIT_DATETIME_MIN QDateTime(QDATETIMEEDIT_DATE_MIN, QDATETIMEEDIT_TIME_MIN)
-#define QDATETIMEEDIT_COMPAT_DATETIME_MIN QDateTime(QDATETIMEEDIT_COMPAT_DATE_MIN, QDATETIMEEDIT_TIME_MIN)
-#define QDATETIMEEDIT_DATETIME_MAX QDateTime(QDATETIMEEDIT_DATE_MAX, QDATETIMEEDIT_TIME_MAX)
#define QDATETIMEEDIT_DATE_INITIAL QDate(2000, 1, 1)
QT_BEGIN_NAMESPACE
diff --git a/src/widgets/widgets/qabstractspinbox.cpp b/src/widgets/widgets/qabstractspinbox.cpp
index c617525c45..56697c5e8f 100644
--- a/src/widgets/widgets/qabstractspinbox.cpp
+++ b/src/widgets/widgets/qabstractspinbox.cpp
@@ -2097,7 +2097,7 @@ QVariant operator*(const QVariant &arg1, double multiplier)
days -= daysInt;
qint64 msecs = qint64(arg1.toDateTime().time().msecsSinceStartOfDay() * multiplier
+ days * (24 * 3600 * 1000));
- ret = QDateTime(QDATETIMEEDIT_DATE_MIN.addDays(daysInt), QTime::fromMSecsSinceStartOfDay(msecs));
+ ret = QDATETIMEEDIT_DATE_MIN.addDays(daysInt).startOfDay().addMSecs(msecs);
break;
}
#endif // datetimeparser
diff --git a/src/widgets/widgets/qdatetimeedit.cpp b/src/widgets/widgets/qdatetimeedit.cpp
index b874e4e3a9..3f41fdeb59 100644
--- a/src/widgets/widgets/qdatetimeedit.cpp
+++ b/src/widgets/widgets/qdatetimeedit.cpp
@@ -153,7 +153,7 @@ QDateTimeEdit::QDateTimeEdit(QWidget *parent)
: QAbstractSpinBox(*new QDateTimeEditPrivate, parent)
{
Q_D(QDateTimeEdit);
- d->init(QDateTime(QDATETIMEEDIT_DATE_INITIAL, QDATETIMEEDIT_TIME_MIN));
+ d->init(QDATETIMEEDIT_DATE_INITIAL.startOfDay());
}
/*!
@@ -165,8 +165,7 @@ QDateTimeEdit::QDateTimeEdit(const QDateTime &datetime, QWidget *parent)
: QAbstractSpinBox(*new QDateTimeEditPrivate, parent)
{
Q_D(QDateTimeEdit);
- d->init(datetime.isValid() ? datetime : QDateTime(QDATETIMEEDIT_DATE_INITIAL,
- QDATETIMEEDIT_TIME_MIN));
+ d->init(datetime.isValid() ? datetime : QDATETIMEEDIT_DATE_INITIAL.startOfDay());
}
/*!
@@ -342,7 +341,7 @@ QDateTime QDateTimeEdit::minimumDateTime() const
void QDateTimeEdit::clearMinimumDateTime()
{
- setMinimumDateTime(QDateTime(QDATETIMEEDIT_COMPAT_DATE_MIN, QDATETIMEEDIT_TIME_MIN));
+ setMinimumDateTime(QDATETIMEEDIT_COMPAT_DATE_MIN.startOfDay());
}
void QDateTimeEdit::setMinimumDateTime(const QDateTime &dt)
@@ -385,7 +384,7 @@ QDateTime QDateTimeEdit::maximumDateTime() const
void QDateTimeEdit::clearMaximumDateTime()
{
- setMaximumDateTime(QDATETIMEEDIT_DATETIME_MAX);
+ setMaximumDateTime(QDATETIMEEDIT_DATE_MAX.endOfDay());
}
void QDateTimeEdit::setMaximumDateTime(const QDateTime &dt)
@@ -1658,8 +1657,8 @@ QDateTimeEditPrivate::QDateTimeEditPrivate()
first.pos = 0;
sections = 0;
calendarPopup = false;
- minimum = QDATETIMEEDIT_COMPAT_DATETIME_MIN;
- maximum = QDATETIMEEDIT_DATETIME_MAX;
+ minimum = QDATETIMEEDIT_COMPAT_DATE_MIN.startOfDay();
+ maximum = QDATETIMEEDIT_DATE_MAX.endOfDay();
arrowState = QStyle::State_None;
monthCalendar = 0;
readLocaleSettings();
@@ -1683,8 +1682,8 @@ void QDateTimeEditPrivate::updateTimeSpec()
const bool dateShown = (sections & QDateTimeEdit::DateSections_Mask);
if (!dateShown) {
if (minimum.toTime() >= maximum.toTime()){
- minimum = QDateTime(value.toDate(), QDATETIMEEDIT_TIME_MIN, spec);
- maximum = QDateTime(value.toDate(), QDATETIMEEDIT_TIME_MAX, spec);
+ minimum = value.toDate().startOfDay(spec);
+ maximum = value.toDate().endOfDay(spec);
}
}
}
@@ -2382,7 +2381,7 @@ void QDateTimeEditPrivate::init(const QVariant &var)
Q_Q(QDateTimeEdit);
switch (var.type()) {
case QVariant::Date:
- value = QDateTime(var.toDate(), QDATETIMEEDIT_TIME_MIN);
+ value = var.toDate().startOfDay();
updateTimeSpec();
q->setDisplayFormat(defaultDateFormat);
if (sectionNodes.isEmpty()) // ### safeguard for broken locale
diff --git a/tests/auto/corelib/tools/qdate/qdate.pro b/tests/auto/corelib/tools/qdate/qdate.pro
index dd7c6cb888..925c3b4c78 100644
--- a/tests/auto/corelib/tools/qdate/qdate.pro
+++ b/tests/auto/corelib/tools/qdate/qdate.pro
@@ -1,4 +1,4 @@
CONFIG += testcase
TARGET = tst_qdate
-QT = core testlib
+QT = core-private testlib
SOURCES = tst_qdate.cpp
diff --git a/tests/auto/corelib/tools/qdate/tst_qdate.cpp b/tests/auto/corelib/tools/qdate/tst_qdate.cpp
index c17af8741b..0ef494b229 100644
--- a/tests/auto/corelib/tools/qdate/tst_qdate.cpp
+++ b/tests/auto/corelib/tools/qdate/tst_qdate.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2016 Intel Corporation.
** Contact: https://www.qt.io/licensing/
**
@@ -27,6 +27,7 @@
**
****************************************************************************/
+#include <private/qglobal_p.h> // for the icu feature test
#include <QtTest/QtTest>
#include <qdatetime.h>
#include <qlocale.h>
@@ -54,6 +55,13 @@ private slots:
void weekNumber_invalid();
void weekNumber_data();
void weekNumber();
+#if QT_CONFIG(timezone)
+ void startOfDay_endOfDay_data();
+ void startOfDay_endOfDay();
+#endif
+ void startOfDay_endOfDay_fixed_data();
+ void startOfDay_endOfDay_fixed();
+ void startOfDay_endOfDay_bounds();
void julianDaysLimits();
void addDays_data();
void addDays();
@@ -458,6 +466,164 @@ void tst_QDate::weekNumber_invalid()
QCOMPARE( dt.weekNumber( &yearNumber ), 0 );
}
+#if QT_CONFIG(timezone)
+void tst_QDate::startOfDay_endOfDay_data()
+{
+ QTest::addColumn<QDate>("date"); // Typically a spring-forward.
+ // A zone in which that date's start and end are worth checking:
+ QTest::addColumn<QByteArray>("zoneName");
+ // The start and end times in that zone:
+ QTest::addColumn<QTime>("start");
+ QTest::addColumn<QTime>("end");
+
+ const QTime initial(0, 0), final(23, 59, 59, 999), invalid(QDateTime().time());
+
+ QTest::newRow("epoch")
+ << QDate(1970, 1, 1) << QByteArray("UTC")
+ << initial << final;
+ QTest::newRow("Brazil")
+ << QDate(2008, 10, 19) << QByteArray("America/Sao_Paulo")
+ << QTime(1, 0) << final;
+#if QT_CONFIG(icu) || !defined(Q_OS_WIN) // MS's TZ APIs lack data
+ QTest::newRow("Sofia")
+ << QDate(1994, 3, 27) << QByteArray("Europe/Sofia")
+ << QTime(1, 0) << final;
+#endif
+ QTest::newRow("Kiritimati")
+ << QDate(1994, 12, 31) << QByteArray("Pacific/Kiritimati")
+ << invalid << invalid;
+ QTest::newRow("Samoa")
+ << QDate(2011, 12, 30) << QByteArray("Pacific/Apia")
+ << invalid << invalid;
+ // TODO: find other zones with transitions at/crossing midnight.
+}
+
+void tst_QDate::startOfDay_endOfDay()
+{
+ QFETCH(QDate, date);
+ QFETCH(QByteArray, zoneName);
+ QFETCH(QTime, start);
+ QFETCH(QTime, end);
+ const QTimeZone zone(zoneName);
+ const bool isSystem = QTimeZone::systemTimeZone() == zone;
+ QDateTime front(date.startOfDay(zone)), back(date.endOfDay(zone));
+ if (end.isValid())
+ QCOMPARE(date.addDays(1).startOfDay(zone).addMSecs(-1), back);
+ if (start.isValid())
+ QCOMPARE(date.addDays(-1).endOfDay(zone).addMSecs(1), front);
+ do { // Avoids duplicating these tests for local-time when it *is* zone:
+ if (start.isValid()) {
+ QCOMPARE(front.date(), date);
+ QCOMPARE(front.time(), start);
+ }
+ if (end.isValid()) {
+ QCOMPARE(back.date(), date);
+ QCOMPARE(back.time(), end);
+ }
+ if (front.timeSpec() == Qt::LocalTime)
+ break;
+ front = date.startOfDay(Qt::LocalTime);
+ back = date.endOfDay(Qt::LocalTime);
+ } while (isSystem);
+ if (end.isValid())
+ QCOMPARE(date.addDays(1).startOfDay(Qt::LocalTime).addMSecs(-1), back);
+ if (start.isValid())
+ QCOMPARE(date.addDays(-1).endOfDay(Qt::LocalTime).addMSecs(1), front);
+ if (!isSystem) {
+ // These might fail if system zone coincides with zone; but only if it
+ // did something similarly unusual on the date picked for this test.
+ if (start.isValid()) {
+ QCOMPARE(front.date(), date);
+ QCOMPARE(front.time(), QTime(0, 0));
+ }
+ if (end.isValid()) {
+ QCOMPARE(back.date(), date);
+ QCOMPARE(back.time(), QTime(23, 59, 59, 999));
+ }
+ }
+}
+#endif // timezone
+
+void tst_QDate::startOfDay_endOfDay_fixed_data()
+{
+ const qint64 kilo(1000);
+ using Bounds = std::numeric_limits<qint64>;
+ const QDateTime
+ first(QDateTime::fromMSecsSinceEpoch(Bounds::min() + 1, Qt::UTC)),
+ start32sign(QDateTime::fromMSecsSinceEpoch(-0x80000000L * kilo, Qt::UTC)),
+ end32sign(QDateTime::fromMSecsSinceEpoch(0x80000000L * kilo, Qt::UTC)),
+ end32unsign(QDateTime::fromMSecsSinceEpoch(0x100000000L * kilo, Qt::UTC)),
+ last(QDateTime::fromMSecsSinceEpoch(Bounds::max(), Qt::UTC));
+
+ const struct {
+ const char *name;
+ QDate date;
+ } data[] = {
+ { "epoch", QDate(1970, 1, 1) },
+ { "y2k-leap-day", QDate(2000, 2, 29) },
+ // Just outside the start and end of 32-bit time_t:
+ { "pre-sign32", QDate(start32sign.date().year(), 1, 1) },
+ { "post-sign32", QDate(end32sign.date().year(), 12, 31) },
+ { "post-uint32", QDate(end32unsign.date().year(), 12, 31) },
+ // Just inside the start and end of QDateTime's range:
+ { "first-full", first.date().addDays(1) },
+ { "last-full", last.date().addDays(-1) }
+ };
+
+ QTest::addColumn<QDate>("date");
+ for (const auto &r : data)
+ QTest::newRow(r.name) << r.date;
+}
+
+void tst_QDate::startOfDay_endOfDay_fixed()
+{
+ const QTime early(0, 0), late(23, 59, 59, 999);
+ QFETCH(QDate, date);
+
+ QDateTime start(date.startOfDay(Qt::UTC));
+ QDateTime end(date.endOfDay(Qt::UTC));
+ QCOMPARE(start.date(), date);
+ QCOMPARE(end.date(), date);
+ QCOMPARE(start.time(), early);
+ QCOMPARE(end.time(), late);
+ QCOMPARE(date.addDays(1).startOfDay(Qt::UTC).addMSecs(-1), end);
+ QCOMPARE(date.addDays(-1).endOfDay(Qt::UTC).addMSecs(1), start);
+ for (int offset = -60 * 16; offset <= 60 * 16; offset += 65) {
+ start = date.startOfDay(Qt::OffsetFromUTC, offset);
+ end = date.endOfDay(Qt::OffsetFromUTC, offset);
+ QCOMPARE(start.date(), date);
+ QCOMPARE(end.date(), date);
+ QCOMPARE(start.time(), early);
+ QCOMPARE(end.time(), late);
+ QCOMPARE(date.addDays(1).startOfDay(Qt::OffsetFromUTC, offset).addMSecs(-1), end);
+ QCOMPARE(date.addDays(-1).endOfDay(Qt::OffsetFromUTC, offset).addMSecs(1), start);
+ }
+}
+
+void tst_QDate::startOfDay_endOfDay_bounds()
+{
+ // Check the days in which QDateTime's range starts and ends:
+ using Bounds = std::numeric_limits<qint64>;
+ const QDateTime
+ first(QDateTime::fromMSecsSinceEpoch(Bounds::min(), Qt::UTC)),
+ last(QDateTime::fromMSecsSinceEpoch(Bounds::max(), Qt::UTC)),
+ epoch(QDateTime::fromMSecsSinceEpoch(0, Qt::UTC));
+ // First, check these *are* the start and end of QDateTime's range:
+ QVERIFY(first.isValid());
+ QVERIFY(last.isValid());
+ QVERIFY(first < epoch);
+ QVERIFY(last > epoch);
+ // QDateTime's addMSecs doesn't check against {und,ov}erflow ...
+ QVERIFY(!first.addMSecs(-1).isValid() || first.addMSecs(-1) > first);
+ QVERIFY(!last.addMSecs(1).isValid() || last.addMSecs(1) < last);
+
+ // Now test start/end methods with them:
+ QCOMPARE(first.date().endOfDay(Qt::UTC).time(), QTime(23, 59, 59, 999));
+ QCOMPARE(last.date().startOfDay(Qt::UTC).time(), QTime(0, 0));
+ QVERIFY(!first.date().startOfDay(Qt::UTC).isValid());
+ QVERIFY(!last.date().endOfDay(Qt::UTC).isValid());
+}
+
void tst_QDate::julianDaysLimits()
{
qint64 min = std::numeric_limits<qint64>::min();