From b4a875544ba8f2d11e183d67f45891d6149203ed Mon Sep 17 00:00:00 2001 From: Edward Welbourne Date: Thu, 18 Feb 2021 17:24:18 +0100 Subject: [PATCH 1/5] Extend time_t-based handling all the way to the end of time_t At least some modern 64-bit systems have widened time_t to 64 bits fixing the "Unix time" problem. (This is even the default on MS-Win, although the system functions artificially limit the accepted range to 1970 through 3000.) Even the 32-bit range extends into January 2038 but the code was artificially cutting this off at the end of 2037. This is a preparation for using the same also all the way back to the start of time_t. In the process, simplify and tidy up the logic of the existing code, update the docs (this includes correcting some misinformation) and revise some tests. Fixes: QTBUG-73225 Change-Id: Ib8001b5a982386c747eda3dea2b5a26eedd499ad Reviewed-by: Qt CI Bot Reviewed-by: Thiago Macieira --- src/corelib/time/qdatetime.cpp | 207 +++++++++--------- .../corelib/time/qdatetime/tst_qdatetime.cpp | 14 +- 2 files changed, 112 insertions(+), 109 deletions(-) diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp index 701df4a06b..a3516ddb67 100644 --- a/src/corelib/time/qdatetime.cpp +++ b/src/corelib/time/qdatetime.cpp @@ -84,7 +84,7 @@ enum : qint64 { SECS_PER_MIN = 60, MSECS_PER_MIN = 60000, MSECS_PER_SEC = 1000, - TIME_T_MAX = 2145916799, // int maximum 2037-12-31T23:59:59 UTC + TIME_T_MAX = std::numeric_limits::max(), JULIAN_DAY_FOR_EPOCH = 2440588 // result of julianDayFromDate(1970, 1, 1) }; @@ -2430,15 +2430,15 @@ int QDateTimeParser::startsWithLocalTimeZone(QStringView name) } #endif // datetimeparser -// Calls the platform variant of mktime for the given date, time and daylightStatus, -// and updates the date, time, daylightStatus and abbreviation with the returned values -// If the date falls outside the 1970 to 2037 range supported by mktime / time_t -// then null date/time will be returned, you should adjust the date first if -// you need a guaranteed result. +// Calls the platform variant of mktime for the given date, time and +// daylightStatus, and updates the date, time, daylightStatus and abbreviation +// with the returned values. If the date falls outside the time_t range +// supported by mktime, then date/time will not be updated and *ok is set false. static qint64 qt_mktime(QDate *date, QTime *time, QDateTimePrivate::DaylightStatus *daylightStatus, - QString *abbreviation, bool *ok = nullptr) + QString *abbreviation, bool *ok) { - const qint64 msec = time->msec(); + Q_ASSERT(ok); + qint64 msec = time->msec(); int yy, mm, dd; date->getDate(&yy, &mm, &dd); @@ -2505,14 +2505,21 @@ static qint64 qt_mktime(QDate *date, QTime *time, QDateTimePrivate::DaylightStat *daylightStatus = QDateTimePrivate::UnknownDaylightTime; if (abbreviation) *abbreviation = QString(); - if (ok) - *ok = false; + *ok = false; return 0; } - if (ok) - *ok = true; + if (secsSinceEpoch < 0 && msec > 0) { + secsSinceEpoch++; + msec -= MSECS_PER_SEC; + } + qint64 millis; + const bool overflow = + mul_overflow(qint64(secsSinceEpoch), + std::integral_constant(), &millis) + || add_overflow(millis, msec, &msec); + *ok = !overflow; - return qint64(secsSinceEpoch) * MSECS_PER_SEC + msec; + return msec; } // Calls the platform variant of localtime for the given msecs, and updates @@ -2602,6 +2609,34 @@ static qint64 timeToMSecs(QDate date, QTime time) + time.msecsSinceStartOfDay(); } +/*! + \internal + Tests whether system functions can handle a given time. + + On MS-systems (where time_t is 64-bit by default), the system functions only + work for dates up to the end of year 3000 (for mktime(); for _localtime64_s + it's 18 days later, but we ignore that here). On Unix the supported range + is as many seconds after the epoch as time_t can represent. + + This second-range is then mapped to a millisecond range; if \a slack is + passed, the range is extended by this many milliseconds at each end. The + function returns true precisely if \a millis is within the resulting range. +*/ +static inline bool millisInSystemRange(qint64 millis, qint64 slack = 0) +{ +#ifdef Q_OS_WIN + const qint64 msecsMax = Q_INT64_C(32535215999999); + return millis <= msecsMax + slack; +#else + if constexpr (std::numeric_limits::max() / MSECS_PER_SEC > TIME_T_MAX) { + const qint64 msecsMax = TIME_T_MAX * MSECS_PER_SEC; + return millis <= msecsMax + slack; + } else { + return true; + } +#endif +} + // Convert an MSecs Since Epoch into Local Time static bool epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTime *localTime, QDateTimePrivate::DaylightStatus *daylightStatus = nullptr) @@ -2614,9 +2649,11 @@ static bool epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTime *localTi if (daylightStatus) *daylightStatus = QDateTimePrivate::StandardTime; return true; - } else if (msecs > TIME_T_MAX * MSECS_PER_SEC) { - // Docs state any LocalTime after 2037-12-31 *will* have any DST applied - // but this may fall outside the supported time_t range, so need to fake it. + } + + if (!millisInSystemRange(msecs)) { + // Docs state any LocalTime after 2038-01-18 *will* have any DST applied. + // When this falls outside the supported range, we need to fake it. // Use existing method to fake the conversion, but this is deeply flawed as it may // apply the conversion from the wrong day number, e.g. if rule is last Sunday of month // TODO Use QTimeZone when available to apply the future rule correctly @@ -2633,10 +2670,10 @@ static bool epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTime *localTi bool res = qt_localtime(fakeMsecs, localDate, localTime, daylightStatus); *localDate = localDate->addDays(fakeDate.daysTo(utcDate)); return res; - } else { - // Falls inside time_t suported range so can use localtime - return qt_localtime(msecs, localDate, localTime, daylightStatus); } + + // Falls inside time_t supported range so can use localtime + return qt_localtime(msecs, localDate, localTime, daylightStatus); } // Convert a LocalTime expressed in local msecs encoding and the corresponding @@ -2651,31 +2688,31 @@ static qint64 localMSecsToEpochMSecs(qint64 localMsecs, QTime tm; msecsToTime(localMsecs, &dt, &tm); - const qint64 msecsMax = TIME_T_MAX * MSECS_PER_SEC; + // First, if localMsecs is within +/- 1 day of viable range, try mktime() in + // case it does fall in the range and gets proper DST conversion: + if (localMsecs >= -MSECS_PER_DAY && millisInSystemRange(localMsecs, MSECS_PER_DAY)) { + bool valid; + const qint64 utcMsecs = qt_mktime(&dt, &tm, daylightStatus, abbreviation, &valid); + if (valid && utcMsecs >= 0 && millisInSystemRange(utcMsecs)) { + // mktime worked and falls in valid range, so use it + if (localDate) + *localDate = dt; + if (localTime) + *localTime = tm; + return utcMsecs; + } + // Restore dt and tm, after qt_mktime() stomped them: + msecsToTime(localMsecs, &dt, &tm); + } else { + // If we don't call mktime then we need to call tzset to set up local zone data: + qTzSet(); + } if (localMsecs <= MSECS_PER_DAY) { - + // Would have been caught above if after UTC epoch, so is before. // Docs state any LocalTime before 1970-01-01 will *not* have any DST applied - - // First, if localMsecs is within +/- 1 day of minimum time_t try mktime in case it does - // fall after minimum and needs proper DST conversion - if (localMsecs >= -MSECS_PER_DAY) { - bool valid; - qint64 utcMsecs = qt_mktime(&dt, &tm, daylightStatus, abbreviation, &valid); - if (valid && utcMsecs >= 0) { - // mktime worked and falls in valid range, so use it - if (localDate) - *localDate = dt; - if (localTime) - *localTime = tm; - return utcMsecs; - } - } else { - // If we don't call mktime then need to call tzset to get offset - qTzSet(); - } // Time is clearly before 1970-01-01 so just use standard offset to convert - qint64 utcMsecs = localMsecs + qt_timezone() * MSECS_PER_SEC; + const qint64 utcMsecs = localMsecs + qt_timezone() * MSECS_PER_SEC; if (localDate || localTime) msecsToTime(localMsecs, localDate, localTime); if (daylightStatus) @@ -2683,59 +2720,30 @@ static qint64 localMSecsToEpochMSecs(qint64 localMsecs, if (abbreviation) *abbreviation = qt_tzname(QDateTimePrivate::StandardTime); return utcMsecs; - - } else if (localMsecs >= msecsMax - MSECS_PER_DAY) { - - // Docs state any LocalTime after 2037-12-31 *will* have any DST applied - // but this may fall outside the supported time_t range, so need to fake it. - - // First, if localMsecs is within +/- 1 day of maximum time_t try mktime in case it does - // fall before maximum and can use proper DST conversion - if (localMsecs <= msecsMax + MSECS_PER_DAY) { - bool valid; - qint64 utcMsecs = qt_mktime(&dt, &tm, daylightStatus, abbreviation, &valid); - if (valid && utcMsecs <= msecsMax) { - // mktime worked and falls in valid range, so use it - if (localDate) - *localDate = dt; - if (localTime) - *localTime = tm; - return utcMsecs; - } - } - // Use existing method to fake the conversion, but this is deeply flawed as it may - // apply the conversion from the wrong day number, e.g. if rule is last Sunday of month - // TODO Use QTimeZone when available to apply the future rule correctly - int year, month, day; - dt.getDate(&year, &month, &day); - // 2037 is not a leap year, so make sure date isn't Feb 29 - if (month == 2 && day == 29) - --day; - QDate fakeDate(2037, month, day); - qint64 fakeDiff = fakeDate.daysTo(dt); - qint64 utcMsecs = qt_mktime(&fakeDate, &tm, daylightStatus, abbreviation); - if (localDate) - *localDate = fakeDate.addDays(fakeDiff); - if (localTime) - *localTime = tm; - QDate utcDate; - QTime utcTime; - msecsToTime(utcMsecs, &utcDate, &utcTime); - utcDate = utcDate.addDays(fakeDiff); - utcMsecs = timeToMSecs(utcDate, utcTime); - return utcMsecs; - - } else { - - // Clearly falls inside 1970-2037 suported range so can use mktime - qint64 utcMsecs = qt_mktime(&dt, &tm, daylightStatus, abbreviation); - if (localDate) - *localDate = dt; - if (localTime) - *localTime = tm; - return utcMsecs; - } + + // Otherwise, after the end of the system range. + // Use existing method to fake the conversion, but this is deeply flawed as it may + // apply the conversion from the wrong day number, e.g. if rule is last Sunday of month + // TODO Use QTimeZone when available to apply the future rule correctly + int year, month, day; + dt.getDate(&year, &month, &day); + // 2037 is not a leap year, so make sure date isn't Feb 29 + if (month == 2 && day == 29) + --day; + bool ok; + QDate fakeDate(2037, month, day); + const qint64 fakeDiff = fakeDate.daysTo(dt); + const qint64 utcMsecs = qt_mktime(&fakeDate, &tm, daylightStatus, abbreviation, &ok); + Q_ASSERT(ok); + if (localDate) + *localDate = fakeDate.addDays(fakeDiff); + if (localTime) + *localTime = tm; + QDate utcDate; + QTime utcTime; + msecsToTime(utcMsecs, &utcDate, &utcTime); + return timeToMSecs(utcDate.addDays(fakeDiff), utcTime); } static inline bool specCanBeSmall(Qt::TimeSpec spec) @@ -3350,14 +3358,13 @@ inline qint64 QDateTimePrivate::zoneMSecsToEpochMSecs(qint64 zoneMSecs, const QT result. For example, adding one minute to 01:59:59 will get 03:00:00. The range of valid dates taking DST into account is 1970-01-01 to the - present, and rules are in place for handling DST correctly until 2037-12-31, - but these could change. For dates after 2037, QDateTime makes a \e{best - guess} using the rules for year 2037, but we can't guarantee accuracy; - indeed, for \e{any} future date, the time-zone may change its rules before - that date comes around. For dates before 1970, QDateTime doesn't take DST - changes into account, even if the system's time zone database provides that - information, although it does take into account changes to the time-zone's - standard offset, where this information is available. + present, and rules are in place for handling DST correctly until 2038-01-18 + (or the end of the \c time_t range, if this is later). For dates after the + end of this range, QDateTime makes a \e{best guess} using the rules for year + 2037, but we can't guarantee accuracy; indeed, for \e{any} future date, the + time-zone may change its rules before that date comes around. For dates + before 1970, QDateTime uses the current abbreviation and offset of local + time's standad time. \section2 Offsets From UTC diff --git a/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp b/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp index c2f4e82896..166ce260a5 100644 --- a/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp +++ b/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp @@ -615,7 +615,7 @@ void tst_QDateTime::setMSecsSinceEpoch_data() << Q_INT64_C(-123456789) << QDateTime(QDate(1969, 12, 30), QTime(13, 42, 23, 211), Qt::UTC) << QDateTime(QDate(1969, 12, 30), QTime(14, 42, 23, 211), Qt::LocalTime); - QTest::newRow("non-time_t") + QTest::newRow("post-32-bit-time_t") << (Q_INT64_C(1000) << 32) << QDateTime(QDate(2106, 2, 7), QTime(6, 28, 16), Qt::UTC) << QDateTime(QDate(2106, 2, 7), QTime(7, 28, 16)); @@ -713,10 +713,7 @@ void tst_QDateTime::setMSecsSinceEpoch() } QCOMPARE(dt.toMSecsSinceEpoch(), msecs); - - if (quint64(msecs / 1000) < 0xFFFFFFFF) { - QCOMPARE(qint64(dt.toSecsSinceEpoch()), msecs / 1000); - } + QCOMPARE(qint64(dt.toSecsSinceEpoch()), msecs / 1000); QDateTime reference(QDate(1970, 1, 1), QTime(0, 0), Qt::UTC); QCOMPARE(dt, reference.addMSecs(msecs)); @@ -766,11 +763,10 @@ void tst_QDateTime::fromMSecsSinceEpoch() QCOMPARE(dtUtc.toMSecsSinceEpoch(), msecs); QCOMPARE(dtOffset.toMSecsSinceEpoch(), msecs); - if (quint64(msecs / 1000) < 0xFFFFFFFF) { + if (!localOverflow) QCOMPARE(qint64(dtLocal.toSecsSinceEpoch()), msecs / 1000); - QCOMPARE(qint64(dtUtc.toSecsSinceEpoch()), msecs / 1000); - QCOMPARE(qint64(dtOffset.toSecsSinceEpoch()), msecs / 1000); - } + QCOMPARE(qint64(dtUtc.toSecsSinceEpoch()), msecs / 1000); + QCOMPARE(qint64(dtOffset.toSecsSinceEpoch()), msecs / 1000); QDateTime reference(QDate(1970, 1, 1), QTime(0, 0), Qt::UTC); if (!localOverflow) From 530e0bd469e6859269c2d1a792b8ce819fbff389 Mon Sep 17 00:00:00 2001 From: Edward Welbourne Date: Thu, 18 Feb 2021 18:33:18 +0100 Subject: [PATCH 2/5] Use QTimeZone to determine offsets outside the system-function range MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow up on some comments saying "TODO Use QTimeZone when available" in converting times, outside the range supported by the system's time_t functions, between local or zone time and UTC. Since this required two formerly static functions in qdatetime.cpp to access QTimeZone's d-ptr, turn those into methods of QTZ's friend QDTPrivate. Change-Id: I27fe03d8eff9f4e98661263b1a1d4d830f4e7459 Reviewed-by: Qt CI Bot Reviewed-by: MÃ¥rten Nordheim --- src/corelib/time/qdatetime.cpp | 75 ++++++++++++++++++++++++---------- src/corelib/time/qdatetime_p.h | 7 ++++ 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp index a3516ddb67..28816ccef6 100644 --- a/src/corelib/time/qdatetime.cpp +++ b/src/corelib/time/qdatetime.cpp @@ -2638,8 +2638,8 @@ static inline bool millisInSystemRange(qint64 millis, qint64 slack = 0) } // Convert an MSecs Since Epoch into Local Time -static bool epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTime *localTime, - QDateTimePrivate::DaylightStatus *daylightStatus = nullptr) +bool QDateTimePrivate::epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTime *localTime, + QDateTimePrivate::DaylightStatus *daylightStatus) { if (msecs < 0) { // Docs state any LocalTime before 1970-01-01 will *not* have any Daylight Time applied @@ -2654,9 +2654,23 @@ static bool epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTime *localTi if (!millisInSystemRange(msecs)) { // Docs state any LocalTime after 2038-01-18 *will* have any DST applied. // When this falls outside the supported range, we need to fake it. - // Use existing method to fake the conversion, but this is deeply flawed as it may - // apply the conversion from the wrong day number, e.g. if rule is last Sunday of month - // TODO Use QTimeZone when available to apply the future rule correctly +#if QT_CONFIG(timezone) + // Use the system time-zone. + const auto sys = QTimeZone::systemTimeZone(); + if (daylightStatus) { + *daylightStatus = sys.d->isDaylightTime(msecs) + ? QDateTimePrivate::DaylightTime + : QDateTimePrivate::StandardTime; + } + + if (add_overflow(msecs, sys.d->offsetFromUtc(msecs) * MSECS_PER_SEC, &msecs)) + return false; + msecsToTime(msecs, localDate, localTime); + return true; +#else // Kludge + // Use existing method to fake the conversion (this is deeply flawed + // as it may apply the conversion from the wrong day number, e.g. if + // rule is last Sunday of month). QDate utcDate; QTime utcTime; msecsToTime(msecs, &utcDate, &utcTime); @@ -2670,6 +2684,7 @@ static bool epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTime *localTi bool res = qt_localtime(fakeMsecs, localDate, localTime, daylightStatus); *localDate = localDate->addDays(fakeDate.daysTo(utcDate)); return res; +#endif // timezone } // Falls inside time_t supported range so can use localtime @@ -2679,10 +2694,10 @@ static bool epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTime *localTi // Convert a LocalTime expressed in local msecs encoding and the corresponding // DST status into a UTC epoch msecs. Optionally populate the returned // values from mktime for the adjusted local date and time. -static qint64 localMSecsToEpochMSecs(qint64 localMsecs, - QDateTimePrivate::DaylightStatus *daylightStatus, - QDate *localDate = nullptr, QTime *localTime = nullptr, - QString *abbreviation = nullptr) +qint64 QDateTimePrivate::localMSecsToEpochMSecs(qint64 localMsecs, + QDateTimePrivate::DaylightStatus *daylightStatus, + QDate *localDate, QTime *localTime, + QString *abbreviation) { QDate dt; QTime tm; @@ -2703,15 +2718,15 @@ static qint64 localMSecsToEpochMSecs(qint64 localMsecs, } // Restore dt and tm, after qt_mktime() stomped them: msecsToTime(localMsecs, &dt, &tm); - } else { - // If we don't call mktime then we need to call tzset to set up local zone data: + } else if (localMsecs < MSECS_PER_DAY) { + // Didn't call mktime(), but the pre-epoch code below needs mktime()'s + // implicit tzset() call to have happened. qTzSet(); } if (localMsecs <= MSECS_PER_DAY) { // Would have been caught above if after UTC epoch, so is before. // Docs state any LocalTime before 1970-01-01 will *not* have any DST applied - // Time is clearly before 1970-01-01 so just use standard offset to convert const qint64 utcMsecs = localMsecs + qt_timezone() * MSECS_PER_SEC; if (localDate || localTime) msecsToTime(localMsecs, localDate, localTime); @@ -2723,9 +2738,25 @@ static qint64 localMSecsToEpochMSecs(qint64 localMsecs, } // Otherwise, after the end of the system range. - // Use existing method to fake the conversion, but this is deeply flawed as it may - // apply the conversion from the wrong day number, e.g. if rule is last Sunday of month - // TODO Use QTimeZone when available to apply the future rule correctly +#if QT_CONFIG(timezone) + // Use the system zone: + const auto sys = QTimeZone::systemTimeZone(); + const qint64 utcMsecs = + QDateTimePrivate::zoneMSecsToEpochMSecs(localMsecs, sys, + QDateTimePrivate::UnknownDaylightTime, + localDate, localTime); + if (abbreviation) + *abbreviation = sys.d->abbreviation(utcMsecs); + if (daylightStatus) { + *daylightStatus = sys.d->isDaylightTime(utcMsecs) + ? QDateTimePrivate::DaylightTime + : QDateTimePrivate::StandardTime; + } + return utcMsecs; +#else // Kludge + // Use existing method to fake the conversion (this is deeply flawed as it + // may apply the conversion from the wrong day number, e.g. if rule is last + // Sunday of month). int year, month, day; dt.getDate(&year, &month, &day); // 2037 is not a leap year, so make sure date isn't Feb 29 @@ -2744,6 +2775,7 @@ static qint64 localMSecsToEpochMSecs(qint64 localMsecs, QTime utcTime; msecsToTime(utcMsecs, &utcDate, &utcTime); return timeToMSecs(utcDate.addDays(fakeDiff), utcTime); +#endif } static inline bool specCanBeSmall(Qt::TimeSpec spec) @@ -2874,7 +2906,8 @@ static void refreshZonedDateTime(QDateTimeData &d, Qt::TimeSpec spec) QTime testTime; auto dstStatus = extractDaylightStatus(status); if (spec == Qt::LocalTime) { - epochMSecs = localMSecsToEpochMSecs(msecs, &dstStatus, &testDate, &testTime); + epochMSecs = + QDateTimePrivate::localMSecsToEpochMSecs(msecs, &dstStatus, &testDate, &testTime); #if QT_CONFIG(timezone) // else spec == Qt::TimeZone, so check zone is valid: } else if (d->m_timeZone.isValid()) { @@ -3678,7 +3711,7 @@ QString QDateTime::timeZoneAbbreviation() const case Qt::LocalTime: { QString abbrev; auto status = extractDaylightStatus(getStatus(d)); - localMSecsToEpochMSecs(getMSecs(d), &status, nullptr, nullptr, &abbrev); + QDateTimePrivate::localMSecsToEpochMSecs(getMSecs(d), &status, nullptr, nullptr, &abbrev); return abbrev; } } @@ -3715,7 +3748,7 @@ bool QDateTime::isDaylightTime() const case Qt::LocalTime: { auto status = extractDaylightStatus(getStatus(d)); if (status == QDateTimePrivate::UnknownDaylightTime) - localMSecsToEpochMSecs(getMSecs(d), &status); + QDateTimePrivate::localMSecsToEpochMSecs(getMSecs(d), &status); return (status == QDateTimePrivate::DaylightTime); } } @@ -3858,7 +3891,7 @@ qint64 QDateTime::toMSecsSinceEpoch() const if (!d.isShort()) return d->m_msecs - d->m_offsetFromUtc * MSECS_PER_SEC; // Offset from UTC not recorded: need to recompute. - return localMSecsToEpochMSecs(getMSecs(d), &status); + return QDateTimePrivate::localMSecsToEpochMSecs(getMSecs(d), &status); } case Qt::TimeZone: @@ -3950,7 +3983,7 @@ void QDateTime::setMSecsSinceEpoch(qint64 msecs) QDate dt; QTime tm; QDateTimePrivate::DaylightStatus dstStatus; - epochMSecsToLocalTime(msecs, &dt, &tm, &dstStatus); + QDateTimePrivate::epochMSecsToLocalTime(msecs, &dt, &tm, &dstStatus); setDateTime(d, dt, tm); refreshZonedDateTime(d, spec); // FIXME: we do this again, below msecs = getMSecs(d); @@ -4158,7 +4191,7 @@ static inline void massageAdjustedDateTime(QDateTimeData &d, QDate date, QTime t auto spec = getSpec(d); if (spec == Qt::LocalTime) { QDateTimePrivate::DaylightStatus status = QDateTimePrivate::UnknownDaylightTime; - localMSecsToEpochMSecs(timeToMSecs(date, time), &status, &date, &time); + QDateTimePrivate::localMSecsToEpochMSecs(timeToMSecs(date, time), &status, &date, &time); #if QT_CONFIG(timezone) } else if (spec == Qt::TimeZone && d->m_timeZone.isValid()) { QDateTimePrivate::zoneMSecsToEpochMSecs(timeToMSecs(date, time), diff --git a/src/corelib/time/qdatetime_p.h b/src/corelib/time/qdatetime_p.h index 9dcd896d59..8f9773ba72 100644 --- a/src/corelib/time/qdatetime_p.h +++ b/src/corelib/time/qdatetime_p.h @@ -118,6 +118,13 @@ public: static QDateTime::Data create(QDate toDate, QTime toTime, const QTimeZone & timeZone); #endif // timezone + static bool epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTime *localTime, + QDateTimePrivate::DaylightStatus *daylightStatus = nullptr); + static qint64 localMSecsToEpochMSecs(qint64 localMsecs, + QDateTimePrivate::DaylightStatus *daylightStatus, + QDate *localDate = nullptr, QTime *localTime = nullptr, + QString *abbreviation = nullptr); + StatusFlags m_status = StatusFlag(Qt::LocalTime << TimeSpecShift); qint64 m_msecs = 0; int m_offsetFromUtc = 0; From d4242b8af3e6eb5e9f68e5ff2efee97de11da892 Mon Sep 17 00:00:00 2001 From: Edward Welbourne Date: Thu, 8 Apr 2021 10:11:51 +0200 Subject: [PATCH 3/5] Revise deprecation of countriesForLanguage() It was originally marked \obsolete without any comment on what was to be used to replace it, or deprecation markings in the declaration, so it got missed at 6.0. More recently it's been deprecated in favor of a territory-based name; but actually it was obsoleted by (iterating the territory() of each return from) matchingLocales() in Qt 4.8. So back out of adding territoriesForLanguage to replace it and, instead, mark it as deprecated in the declaration, in favor of matchingLocales(). Also rewrite the implementation to be exactly that replacement. Rewrote the one example using it. Fixes: QTBUG-92484 Change-Id: Iedaf30378446dd9adac5128b7ee5fee48aab1636 Reviewed-by: Thiago Macieira --- .../widgets/widgets/calendarwidget/window.cpp | 14 ++++--- src/corelib/text/qlocale.cpp | 38 ++++--------------- src/corelib/text/qlocale.h | 6 +-- 3 files changed, 19 insertions(+), 39 deletions(-) diff --git a/examples/widgets/widgets/calendarwidget/window.cpp b/examples/widgets/widgets/calendarwidget/window.cpp index e88e41beb2..38e9798d83 100644 --- a/examples/widgets/widgets/calendarwidget/window.cpp +++ b/examples/widgets/widgets/calendarwidget/window.cpp @@ -237,6 +237,9 @@ void Window::createPreviewGroupBox() } //! [9] +// TODO: use loc.name() as label (but has underscore in place of slash) +// TODO: use locale() == loc instead of only comparing language and territory +// Needs someone familiar with this example to work out ramifications //! [10] void Window::createGeneralOptionsGroupBox() { @@ -247,15 +250,16 @@ void Window::createGeneralOptionsGroupBox() int index = 0; for (int _lang = QLocale::C; _lang <= QLocale::LastLanguage; ++_lang) { QLocale::Language lang = static_cast(_lang); - const auto territories = QLocale::territoriesForLanguage(lang); - for (auto territory : territories) { + const auto locales = + QLocale::matchingLocales(lang, QLocale::AnyScript, QLocale::AnyTerritory); + for (auto loc : locales) { QString label = QLocale::languageToString(lang); + auto territory = loc.territory(); label += QLatin1Char('/'); label += QLocale::territoryToString(territory); - QLocale locale(lang, territory); - if (this->locale().language() == lang && this->locale().territory() == territory) + if (locale().language() == lang && locale().territory() == territory) curLocaleIndex = index; - localeCombo->addItem(label, locale); + localeCombo->addItem(label, loc); ++index; } } diff --git a/src/corelib/text/qlocale.cpp b/src/corelib/text/qlocale.cpp index 783d09a8b1..8c0ce2434e 100644 --- a/src/corelib/text/qlocale.cpp +++ b/src/corelib/text/qlocale.cpp @@ -2653,38 +2653,9 @@ QList QLocale::matchingLocales(QLocale::Language language, QLocale::Scr return result; } -/*! - \since 6.2 - - Returns the list of countries that have entries for \a language in Qt's locale - database. If the result is an empty list, then \a language is not represented in - Qt's locale database. - - \sa matchingLocales() -*/ -QList QLocale::territoriesForLanguage(QLocale::Language language) -{ - QList result; - if (language == C) { - result << AnyTerritory; - return result; - } - - unsigned language_id = language; - const QLocaleData *data = locale_data + locale_index[language_id]; - while (data->m_language_id == language_id) { - const QLocale::Territory territory = static_cast(data->m_territory_id); - if (!result.contains(territory)) - result.append(territory); - ++data; - } - - return result; -} - #if QT_DEPRECATED_SINCE(6, 6) /*! - \obsolete Use territoriesForLanguage(Language) instead. + \obsolete Use matchingLocales() instead and consult the territory() of each. \since 4.3 Returns the list of countries that have entries for \a language in Qt's locale @@ -2695,7 +2666,12 @@ QList QLocale::territoriesForLanguage(QLocale::Language lang */ QList QLocale::countriesForLanguage(Language language) { - return territoriesForLanguage(language); + const auto locales = matchingLocales(language, AnyScript, AnyCountry); + QList result; + result.reserve(locales.size()); + for (const auto &locale : locales) + result.append(locale.territory()); + return result; } #endif diff --git a/src/corelib/text/qlocale.h b/src/corelib/text/qlocale.h index 59a7339c24..77b3907b42 100644 --- a/src/corelib/text/qlocale.h +++ b/src/corelib/text/qlocale.h @@ -1108,10 +1108,10 @@ public: static QLocale c() { return QLocale(C); } static QLocale system(); - static QList matchingLocales(QLocale::Language language, QLocale::Script script, QLocale::Territory territory); - static QList territoriesForLanguage(Language lang); + static QList matchingLocales(QLocale::Language language, QLocale::Script script, + QLocale::Territory territory); #if QT_DEPRECATED_SINCE(6, 6) - QT_DEPRECATED_VERSION_X_6_6("Use territoriesForLanguage(Language) instead") + QT_DEPRECATED_VERSION_X_6_6("Query territory() on each entry from matchingLocales() instead") static QList countriesForLanguage(Language lang); #endif From c254d73be63033497838807119cb9cb47ca6c1fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Nordheim?= Date: Thu, 15 Apr 2021 17:31:23 +0200 Subject: [PATCH 4/5] QNetworkDiskCache: Drop the file mmap-ing Presumably the code at some point would do a QByteArray::fromRawData-style thing. But now it doesn't do that so the current code was a bit strange. It would map the content of the file to memory only to then copy the content into a QByteArray. Then it reparents the file to the QBuffer, keeping it alive even if its not needed. Fixes: QTBUG-92838 Pick-to: 6.1 6.0 5.15 Change-Id: I88f8cd1b64e0fd13d08b5cc4df44661e216da340 Reviewed-by: Timur Pocheptsov --- src/network/access/qnetworkdiskcache.cpp | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/network/access/qnetworkdiskcache.cpp b/src/network/access/qnetworkdiskcache.cpp index 1ee075cb71..8c71da7f46 100644 --- a/src/network/access/qnetworkdiskcache.cpp +++ b/src/network/access/qnetworkdiskcache.cpp @@ -417,18 +417,7 @@ QIODevice *QNetworkDiskCache::data(const QUrl &url) buffer->setData(d->lastItem.data.data()); } else { buffer.reset(new QBuffer); - // ### verify that QFile uses the fd size and not the file name - qint64 size = file->size() - file->pos(); - const uchar *p = nullptr; -#if !defined(Q_OS_INTEGRITY) - p = file->map(file->pos(), size); -#endif - if (p) { - buffer->setData((const char *)p, size); - file.take()->setParent(buffer.data()); - } else { - buffer->setData(file->readAll()); - } + buffer->setData(file->readAll()); } } buffer->open(QBuffer::ReadOnly); From 54730b31faa51fcb7973cc465ca981dd7c18e8b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Nordheim?= Date: Thu, 15 Apr 2021 17:51:55 +0200 Subject: [PATCH 5/5] QNetworkDiskCache: Switch to unique_ptr in most cases Because take() is deprecated, and these pointers are meant to leave the scope in some branches. Pick-to: 6.1 Change-Id: I5432d91a28f4c5c8c17fadf7ce3bcd41716e216a Reviewed-by: Timur Pocheptsov --- src/network/access/qnetworkdiskcache.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/network/access/qnetworkdiskcache.cpp b/src/network/access/qnetworkdiskcache.cpp index 8c71da7f46..d3320d05e3 100644 --- a/src/network/access/qnetworkdiskcache.cpp +++ b/src/network/access/qnetworkdiskcache.cpp @@ -53,6 +53,8 @@ #include #include +#include + #define CACHE_POSTFIX QLatin1String(".d") #define PREPARED_SLASH QLatin1String("prepared/") #define CACHE_VERSION 8 @@ -196,7 +198,7 @@ QIODevice *QNetworkDiskCache::prepare(const QNetworkCacheMetaData &metaData) break; } } - QScopedPointer cacheItem(new QCacheItem); + std::unique_ptr cacheItem = std::make_unique(); cacheItem->metaData = metaData; QIODevice *device = nullptr; @@ -218,7 +220,7 @@ QIODevice *QNetworkDiskCache::prepare(const QNetworkCacheMetaData &metaData) cacheItem->writeHeader(cacheItem->file); device = cacheItem->file; } - d->inserting[device] = cacheItem.take(); + d->inserting[device] = cacheItem.release(); return device; } @@ -395,7 +397,7 @@ QIODevice *QNetworkDiskCache::data(const QUrl &url) qDebug() << "QNetworkDiskCache::data()" << url; #endif Q_D(QNetworkDiskCache); - QScopedPointer buffer; + std::unique_ptr buffer; if (!url.isValid()) return nullptr; if (d->lastItem.metaData.url() == url && d->lastItem.data.isOpen()) { @@ -421,7 +423,7 @@ QIODevice *QNetworkDiskCache::data(const QUrl &url) } } buffer->open(QBuffer::ReadOnly); - return buffer.take(); + return buffer.release(); } /*!