diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/corelib/doc/snippets/code/src_corelib_tools_qdatetime.cpp | 2 | ||||
-rw-r--r-- | src/corelib/tools/qdatetime.cpp | 271 | ||||
-rw-r--r-- | src/corelib/tools/qdatetime.h | 10 | ||||
-rw-r--r-- | src/corelib/tools/qdatetimeparser.cpp | 10 | ||||
-rw-r--r-- | src/corelib/tools/qdatetimeparser_p.h | 7 | ||||
-rw-r--r-- | src/widgets/widgets/qabstractspinbox.cpp | 2 | ||||
-rw-r--r-- | src/widgets/widgets/qdatetimeedit.cpp | 19 |
7 files changed, 294 insertions, 27 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 |