diff options
Diffstat (limited to 'src/corelib/tools/qdatetime.cpp')
-rw-r--r-- | src/corelib/tools/qdatetime.cpp | 298 |
1 files changed, 280 insertions, 18 deletions
diff --git a/src/corelib/tools/qdatetime.cpp b/src/corelib/tools/qdatetime.cpp index 511dbf0db8..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/ ** @@ -259,7 +259,7 @@ static QString toOffsetString(Qt::DateFormat format, int offset) #if QT_CONFIG(datestring) // Parse offset in [+-]HH[[:]mm] format -static int fromOffsetString(const QStringRef &offsetString, bool *valid) Q_DECL_NOTHROW +static int fromOffsetString(const QStringRef &offsetString, bool *valid) noexcept { *valid = false; @@ -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 @@ -1467,9 +1729,9 @@ bool QDate::isLeapYear(int y) /*! \fn QTime::QTime() - Constructs a null time object. A null time can be a QTime(0, 0, 0, 0) - (i.e., midnight) object, except that isNull() returns \c true and isValid() - returns \c false. + 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). For + the start of a day, see QDate::startOfDay(). \sa isNull(), isValid() */ @@ -2393,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; } @@ -3135,7 +3397,7 @@ inline qint64 QDateTimePrivate::zoneMSecsToEpochMSecs(qint64 zoneMSecs, const QT \sa isValid() */ -QDateTime::QDateTime() Q_DECL_NOEXCEPT_EXPR(Data::CanBeSmall) +QDateTime::QDateTime() noexcept(Data::CanBeSmall) { } @@ -3146,7 +3408,7 @@ QDateTime::QDateTime() Q_DECL_NOEXCEPT_EXPR(Data::CanBeSmall) */ QDateTime::QDateTime(const QDate &date) - : d(QDateTimePrivate::create(date, QTime(0, 0, 0), Qt::LocalTime, 0)) + : d(QDateTimePrivate::create(date, QTime(0, 0), Qt::LocalTime, 0)) { } @@ -3214,7 +3476,7 @@ QDateTime::QDateTime(const QDate &date, const QTime &time, const QTimeZone &time /*! Constructs a copy of the \a other datetime. */ -QDateTime::QDateTime(const QDateTime &other) Q_DECL_NOTHROW +QDateTime::QDateTime(const QDateTime &other) noexcept : d(other.d) { } @@ -3224,7 +3486,7 @@ QDateTime::QDateTime(const QDateTime &other) Q_DECL_NOTHROW Moves the content of the temporary \a other datetime to this object and leaves \a other in an unspecified (but proper) state. */ -QDateTime::QDateTime(QDateTime &&other) Q_DECL_NOTHROW +QDateTime::QDateTime(QDateTime &&other) noexcept : d(std::move(other.d)) { } @@ -3241,7 +3503,7 @@ QDateTime::~QDateTime() copy. */ -QDateTime &QDateTime::operator=(const QDateTime &other) Q_DECL_NOTHROW +QDateTime &QDateTime::operator=(const QDateTime &other) noexcept { d = other.d; return *this; @@ -4469,7 +4731,7 @@ QDateTime QDateTime::currentDateTimeUtc() return QDateTime(d, t, Qt::UTC); } -qint64 QDateTime::currentMSecsSinceEpoch() Q_DECL_NOTHROW +qint64 QDateTime::currentMSecsSinceEpoch() noexcept { SYSTEMTIME st; memset(&st, 0, sizeof(SYSTEMTIME)); @@ -4480,7 +4742,7 @@ qint64 QDateTime::currentMSecsSinceEpoch() Q_DECL_NOTHROW - julianDayFromDate(1970, 1, 1)) * Q_INT64_C(86400000); } -qint64 QDateTime::currentSecsSinceEpoch() Q_DECL_NOTHROW +qint64 QDateTime::currentSecsSinceEpoch() noexcept { SYSTEMTIME st; memset(&st, 0, sizeof(SYSTEMTIME)); @@ -4512,7 +4774,7 @@ QDateTime QDateTime::currentDateTimeUtc() return fromMSecsSinceEpoch(currentMSecsSinceEpoch(), Qt::UTC); } -qint64 QDateTime::currentMSecsSinceEpoch() Q_DECL_NOTHROW +qint64 QDateTime::currentMSecsSinceEpoch() noexcept { // posix compliant system // we have milliseconds @@ -4521,7 +4783,7 @@ qint64 QDateTime::currentMSecsSinceEpoch() Q_DECL_NOTHROW return qint64(tv.tv_sec) * Q_INT64_C(1000) + tv.tv_usec / 1000; } -qint64 QDateTime::currentSecsSinceEpoch() Q_DECL_NOTHROW +qint64 QDateTime::currentSecsSinceEpoch() noexcept { struct timeval tv; gettimeofday(&tv, 0); @@ -5418,7 +5680,7 @@ uint qHash(const QDateTime &key, uint seed) Returns the hash value for the \a key, using \a seed to seed the calculation. */ -uint qHash(const QDate &key, uint seed) Q_DECL_NOTHROW +uint qHash(const QDate &key, uint seed) noexcept { return qHash(key.toJulianDay(), seed); } @@ -5429,7 +5691,7 @@ uint qHash(const QDate &key, uint seed) Q_DECL_NOTHROW Returns the hash value for the \a key, using \a seed to seed the calculation. */ -uint qHash(const QTime &key, uint seed) Q_DECL_NOTHROW +uint qHash(const QTime &key, uint seed) noexcept { return qHash(key.msecsSinceStartOfDay(), seed); } |