Add startOfDay() and endOfDay() methods to QDate
These methods give the first and last QDateTime values in the given day, for a given time-zone or time-spec. These are usually the relevant midnight, or the millisecond before, except when time-zone transitions (typically DST changes) skip it, when care is needed to select the right moment. Adapted some code to make use of the new API, eliminating some old cruft from qdatetimeparser_p.h in the process. [ChangeLog][QtCore][QDate] Added startOfDay() and endOfDay() methods to provide a QDateTime at the start and end of a given date, taking account of any time skipped by transitions, e.g. a DST spring-forward, which can lead to a day starting at 01:00 or ending just before 23:00. Task-number: QTBUG-64485 Change-Id: I3dd7a34bedfbec8f8af00c43d13f50f99346ecd0 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
3abfa4dfff
commit
80853afd73
@ -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]
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -1,4 +1,4 @@
|
||||
CONFIG += testcase
|
||||
TARGET = tst_qdate
|
||||
QT = core testlib
|
||||
QT = core-private testlib
|
||||
SOURCES = 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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user