diff options
author | Edward Welbourne <edward.welbourne@qt.io> | 2022-05-09 12:06:18 +0200 |
---|---|---|
committer | Edward Welbourne <edward.welbourne@qt.io> | 2022-05-20 13:09:23 +0200 |
commit | 6d97cf0e573f11ae1fae11441e6fa747c34be21c (patch) | |
tree | 521d9e6c805ea0e63dec0780f4558ef5efc28dba | |
parent | 1e295cff491b30a88367259115fe429f18bab5f7 (diff) |
Split qtLocalTime out into a new QLocalTime namespace
... in new qlocaltime* files, now that it's decoupled enough from the
internals of QDateTime for this to be possible. Part of the
consolidation of time_t code in one place.
Move assorted constants from qdatetime.cpp to a private namespace in
qdatetimeprivate_p.h to be shared between q*time.cpp hereafter (fixing
an out of date comment in the process - julianDayFromDate() is long
gone).
Task-number: QTBUG-95993
Change-Id: I03d97e959118041f9d86b8bb2e738599bc0b17e1
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
-rw-r--r-- | src/corelib/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/corelib/time/qdatetime.cpp | 81 | ||||
-rw-r--r-- | src/corelib/time/qdatetime_p.h | 16 | ||||
-rw-r--r-- | src/corelib/time/qlocaltime.cpp | 108 | ||||
-rw-r--r-- | src/corelib/time/qlocaltime_p.h | 31 | ||||
-rw-r--r-- | src/tools/bootstrap/CMakeLists.txt | 1 |
6 files changed, 161 insertions, 77 deletions
diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index 2615a71f8b..5fd60b5ac9 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -216,6 +216,7 @@ qt_internal_add_module(Core time/qcalendarbackend_p.h time/qcalendarmath_p.h time/qdatetime.cpp time/qdatetime.h time/qdatetime_p.h + time/qlocaltime.cpp time/qlocaltime_p.h time/qgregoriancalendar.cpp time/qgregoriancalendar_p.h time/qjuliancalendar.cpp time/qjuliancalendar_p.h time/qmilankoviccalendar.cpp time/qmilankoviccalendar_p.h diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp index 817f37d4a4..27d31cca8f 100644 --- a/src/corelib/time/qdatetime.cpp +++ b/src/corelib/time/qdatetime.cpp @@ -20,6 +20,7 @@ #include "private/qcore_mac_p.h" #endif #include "private/qgregoriancalendar_p.h" +#include "private/qlocaltime_p.h" #include "private/qnumeric_p.h" #include "private/qstringiterator_p.h" #if QT_CONFIG(timezone) @@ -35,20 +36,13 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; +using namespace QtPrivate::DateTimeConstants; /***************************************************************************** Date/Time Constants *****************************************************************************/ -constexpr qint64 SECS_PER_DAY = 86400; -constexpr qint64 MSECS_PER_DAY = 86400000; -constexpr qint64 SECS_PER_HOUR = 3600; -constexpr qint64 MSECS_PER_HOUR = 3600000; -constexpr qint64 SECS_PER_MIN = 60; -constexpr qint64 MSECS_PER_MIN = 60000; -constexpr qint64 MSECS_PER_SEC = 1000; constexpr qint64 TIME_T_MAX = std::numeric_limits<time_t>::max(); -constexpr qint64 JULIAN_DAY_FOR_EPOCH = 2440588; // result of julianDayFromDate(1970, 1, 1) /***************************************************************************** QDate static helper functions @@ -2564,73 +2558,6 @@ static inline bool callMkTime(tm *local, time_t *secs) return good; } -namespace { -bool qtLocalTime(time_t utc, struct tm *local) -{ - // localtime() is specified to work as if it called tzset(). - // localtime_r() does not have this constraint, so make an explicit call. - // The explicit call should also request a re-parse of timezone info. - qTzSet(); -#if defined(Q_OS_WIN) - return !localtime_s(local, &utc); -#elif QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) - // Use the reentrant version of localtime() where available - // as is thread-safe and doesn't use a shared static data area - if (tm *res = localtime_r(&utc, local)) { - Q_ASSERT(res == local); - return true; - } - return false; -#else - // Returns shared static data which may be overwritten at any time - // So copy the result asap - if (tm *res = localtime(&utc)) { - *local = *res; - return true; - } - return false; -#endif -} - -// Calls the platform variant of localtime() for the given utcMillis, and -// returns the local milliseconds, offset from UTC and DST status. -QDateTimePrivate::ZoneState utcToLocal(qint64 utcMillis) -{ - const int signFix = utcMillis % MSECS_PER_SEC && utcMillis < 0 ? 1 : 0; - const time_t epochSeconds = utcMillis / MSECS_PER_SEC - signFix; - const int msec = utcMillis % MSECS_PER_SEC + signFix * MSECS_PER_SEC; - Q_ASSERT(msec >= 0 && msec < MSECS_PER_SEC); - if (qint64(epochSeconds) * MSECS_PER_SEC + msec != utcMillis) - return {utcMillis}; - - tm local; - if (!qtLocalTime(epochSeconds, &local)) - return {utcMillis}; - - qint64 jd; - if (Q_UNLIKELY(!QGregorianCalendar::julianFromParts(qYearFromTmYear(local.tm_year), - local.tm_mon + 1, local.tm_mday, &jd))) { - return {utcMillis}; - } - const qint64 daySeconds - = local.tm_hour * SECS_PER_HOUR + local.tm_min * SECS_PER_MIN + local.tm_sec; - Q_ASSERT(0 <= daySeconds && daySeconds < SECS_PER_DAY); - qint64 localSeconds, localMillis; - if (Q_UNLIKELY( - mul_overflow(jd - JULIAN_DAY_FOR_EPOCH, std::integral_constant<qint64, SECS_PER_DAY>(), - &localSeconds) - || add_overflow(localSeconds, daySeconds, &localSeconds) - || mul_overflow(localSeconds, std::integral_constant<qint64, MSECS_PER_SEC>(), - &localMillis) - || add_overflow(localMillis, qint64(msec), &localMillis))) { - return {utcMillis}; - } - const auto dst - = local.tm_isdst ? QDateTimePrivate::DaylightTime : QDateTimePrivate::StandardTime; - return { localMillis, int(localSeconds - epochSeconds), dst }; -} -} - // Converts milliseconds since the start of 1970 into a date and/or time: static qint64 msecsToJulianDay(qint64 msecs) { @@ -2903,7 +2830,7 @@ QDateTimePrivate::ZoneState QDateTimePrivate::expressUtcAsLocal(qint64 utcMSecs) ZoneState result{utcMSecs}; // Within the time_t supported range, localtime() can handle it: if (millisInSystemRange(utcMSecs)) { - result = utcToLocal(utcMSecs); + result = QLocalTime::utcToLocal(utcMSecs); if (result.valid) return result; } @@ -2935,7 +2862,7 @@ QDateTimePrivate::ZoneState QDateTimePrivate::expressUtcAsLocal(qint64 utcMSecs) return result; } - result = utcToLocal(fakeUtc); + result = QLocalTime::utcToLocal(fakeUtc); // Now correct result.when for the use of the fake date: if (!result.valid || add_overflow(result.when, diffMillis, &result.when)) { // If utcToLocal() failed, its return has the fake when; restore utcMSecs. diff --git a/src/corelib/time/qdatetime_p.h b/src/corelib/time/qdatetime_p.h index cccaba840a..0636a3a223 100644 --- a/src/corelib/time/qdatetime_p.h +++ b/src/corelib/time/qdatetime_p.h @@ -111,6 +111,22 @@ public: Q_DECLARE_OPERATORS_FOR_FLAGS(QDateTimePrivate::StatusFlags) +namespace QtPrivate { +namespace DateTimeConstants { + +constexpr qint64 SECS_PER_MIN = 60; +constexpr qint64 SECS_PER_HOUR = SECS_PER_MIN * 60; +constexpr qint64 SECS_PER_DAY = SECS_PER_HOUR * 24; + +constexpr qint64 MSECS_PER_SEC = 1000; +constexpr qint64 MSECS_PER_MIN = SECS_PER_MIN * MSECS_PER_SEC; +constexpr qint64 MSECS_PER_HOUR = SECS_PER_HOUR * MSECS_PER_SEC; +constexpr qint64 MSECS_PER_DAY = SECS_PER_DAY * MSECS_PER_SEC; + +constexpr qint64 JULIAN_DAY_FOR_EPOCH = 2440588; // result of QDate(1970, 1, 1).toJulianDay() +} +} + QT_END_NAMESPACE #endif // QDATETIME_P_H diff --git a/src/corelib/time/qlocaltime.cpp b/src/corelib/time/qlocaltime.cpp new file mode 100644 index 0000000000..589a6e7688 --- /dev/null +++ b/src/corelib/time/qlocaltime.cpp @@ -0,0 +1,108 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qlocaltime_p.h" +#include "qplatformdefs.h" + +#include "private/qgregoriancalendar_p.h" +#include "private/qnumeric_p.h" +#if QT_CONFIG(timezone) +#include "private/qtimezoneprivate_p.h" +#endif + +#include <time.h> +#ifdef Q_OS_WIN +# include <qt_windows.h> +#endif + +QT_BEGIN_NAMESPACE + +using namespace QtPrivate::DateTimeConstants; +namespace { +/* + Qt represents n BCE as -n, whereas struct tm's tm_year field represents a + year by the number of years after (negative for before) 1900, so that 1+m + BCE is -1900 -m; so treating 1 BCE as 0 CE. We thus shift by different + offsets depending on whether the year is BCE or CE. +*/ +// constexpr int tmYearFromQYear(int year) { return year - (year < 0 ? 1899 : 1900); } +constexpr int qYearFromTmYear(int year) { return year + (year < -1899 ? 1899 : 1900); } + +bool qtLocalTime(time_t utc, struct tm *local) +{ + // This should really be done under the environmentMutex wrapper qglobal.cpp + // uses in qTzSet() and friends. However, the only sane way to do that would + // be to move this whole function there (and replace its qTzSet() with a + // naked tzset(), since it'd already be mutex-protected). +#if defined(Q_OS_WIN) + // The doc of localtime_s() doesn't explicitly say that it calls _tzset(), + // but does say that localtime_s() corrects for the same things _tzset() + // sets the globals for, so presumably localtime_s() behaves as if it did. + return !localtime_s(local, &utc); +#elif QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) + // Use the reentrant version of localtime() where available, as it is + // thread-safe and doesn't use a shared static data area. + // As localtime() is specified to work as if it called tzset(), but + // localtime_r() does not have this constraint, make an explicit call. + // The explicit call should also request a re-parse of timezone info. + qTzSet(); + if (tm *res = localtime_r(&utc, local)) { + Q_ASSERT(res == local); + return true; + } + return false; +#else + // Returns shared static data which may be overwritten at any time + // So copy the result asap + if (tm *res = localtime(&utc)) { + *local = *res; + return true; + } + return false; +#endif +} +} // namespace + +namespace QLocalTime { + +// Calls the platform variant of localtime() for the given utcMillis, and +// returns the local milliseconds, offset from UTC and DST status. +QDateTimePrivate::ZoneState utcToLocal(qint64 utcMillis) +{ + const int signFix = utcMillis % MSECS_PER_SEC && utcMillis < 0 ? 1 : 0; + const time_t epochSeconds = utcMillis / MSECS_PER_SEC - signFix; + const int msec = utcMillis % MSECS_PER_SEC + signFix * MSECS_PER_SEC; + Q_ASSERT(msec >= 0 && msec < MSECS_PER_SEC); + if (qint64(epochSeconds) * MSECS_PER_SEC + msec != utcMillis) + return {utcMillis}; + + tm local; + if (!qtLocalTime(epochSeconds, &local)) + return {utcMillis}; + + qint64 jd; + if (Q_UNLIKELY(!QGregorianCalendar::julianFromParts(qYearFromTmYear(local.tm_year), + local.tm_mon + 1, local.tm_mday, &jd))) { + return {utcMillis}; + } + const qint64 daySeconds + = (local.tm_hour * 60 + local.tm_min) * 60 + local.tm_sec; + Q_ASSERT(0 <= daySeconds && daySeconds < SECS_PER_DAY); + qint64 localSeconds, localMillis; + if (Q_UNLIKELY( + mul_overflow(jd - JULIAN_DAY_FOR_EPOCH, std::integral_constant<qint64, SECS_PER_DAY>(), + &localSeconds) + || add_overflow(localSeconds, daySeconds, &localSeconds) + || mul_overflow(localSeconds, std::integral_constant<qint64, MSECS_PER_SEC>(), + &localMillis) + || add_overflow(localMillis, qint64(msec), &localMillis))) { + return {utcMillis}; + } + const auto dst + = local.tm_isdst ? QDateTimePrivate::DaylightTime : QDateTimePrivate::StandardTime; + return { localMillis, int(localSeconds - epochSeconds), dst }; +} + +} // QLocalTime + +QT_END_NAMESPACE diff --git a/src/corelib/time/qlocaltime_p.h b/src/corelib/time/qlocaltime_p.h new file mode 100644 index 0000000000..2a7b7bc098 --- /dev/null +++ b/src/corelib/time/qlocaltime_p.h @@ -0,0 +1,31 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QLOCALTIME_P_H +#define QLOCALTIME_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an implementation +// detail. This header file may change from version to version without notice, +// or even be removed. +// +// We mean it. +// + +#include <QtCore/private/qglobal_p.h> +#include <QtCore/private/qdatetime_p.h> + +QT_BEGIN_NAMESPACE + +// Packaging system time_t functions +namespace QLocalTime { +// Support for QDateTime +QDateTimePrivate::ZoneState utcToLocal(qint64 utcMillis); +} + +QT_END_NAMESPACE + +#endif // QLOCALTIME_P_H diff --git a/src/tools/bootstrap/CMakeLists.txt b/src/tools/bootstrap/CMakeLists.txt index 2f69849538..0d920624af 100644 --- a/src/tools/bootstrap/CMakeLists.txt +++ b/src/tools/bootstrap/CMakeLists.txt @@ -71,6 +71,7 @@ qt_internal_extend_target(Bootstrap ../../corelib/time/qcalendar.cpp ../../corelib/time/qdatetime.cpp ../../corelib/time/qgregoriancalendar.cpp + ../../corelib/time/qlocaltime.cpp ../../corelib/time/qromancalendar.cpp ../../corelib/tools/qarraydata.cpp ../../corelib/tools/qbitarray.cpp |