From c4f7dba81e43c11c1e71c19ef7f0b5acad119e60 Mon Sep 17 00:00:00 2001 From: Ivan Solovev Date: Mon, 23 Oct 2023 16:58:54 +0200 Subject: [PATCH] QDate: make conversion from/to chrono types constexpr and noexcept Add method stdSysDaysToJulianDay(), which implements the conversion from std::chrono::sys_days to Julian Day in a constexpr and noexcept manner. Use it in the fromStdSysDays() method instead of addDays(), and also in all constructors from std::chrono types. This allows all constructors to be constexpr and noexcept. Simplify toStdSysDays() so that it could also be constexpr and noexcept. Add compile-time roundtrip tests between QDate and std::chrono types. Fixes: QTBUG-118221 Change-Id: Id35a669430162f1932ad8d28b553af2fd7f5c6de Reviewed-by: Thiago Macieira --- src/corelib/time/qdatetime.cpp | 14 ++-- src/corelib/time/qdatetime.h | 82 ++++++++++----------- tests/auto/corelib/time/qdate/tst_qdate.cpp | 32 ++++++++ 3 files changed, 79 insertions(+), 49 deletions(-) diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp index ec3c37e740f..d1b07df07d8 100644 --- a/src/corelib/time/qdatetime.cpp +++ b/src/corelib/time/qdatetime.cpp @@ -446,14 +446,14 @@ QDate::QDate(int y, int m, int d, QCalendar cal) } /*! - \fn QDate::QDate(std::chrono::year_month_day ymd) - \fn QDate::QDate(std::chrono::year_month_day_last ymd) - \fn QDate::QDate(std::chrono::year_month_weekday ymd) - \fn QDate::QDate(std::chrono::year_month_weekday_last ymd) + \fn QDate::QDate(std::chrono::year_month_day date) + \fn QDate::QDate(std::chrono::year_month_day_last date) + \fn QDate::QDate(std::chrono::year_month_weekday date) + \fn QDate::QDate(std::chrono::year_month_weekday_last date) \since 6.4 - Constructs a QDate representing the same date as \a ymd. This allows for + Constructs a QDate representing the same date as \a date. This allows for easy interoperability between the Standard Library calendaring classes and Qt datetime classes. @@ -462,9 +462,9 @@ QDate::QDate(int y, int m, int d, QCalendar cal) \snippet code/src_corelib_time_qdatetime.cpp 22 \note Unlike QDate, std::chrono::year and the related classes feature the - year zero. This means that if \a ymd is in the year zero or before, the + year zero. This means that if \a date is in the year zero or before, the resulting QDate object will have an year one less than the one specified by - \a ymd. + \a date. \note This function requires C++20. */ diff --git a/src/corelib/time/qdatetime.h b/src/corelib/time/qdatetime.h index 7ef869ae9f0..c3b025c6b93 100644 --- a/src/corelib/time/qdatetime.h +++ b/src/corelib/time/qdatetime.h @@ -32,53 +32,36 @@ public: QDate(int y, int m, int d, QCalendar cal); #if __cpp_lib_chrono >= 201907L || defined(Q_QDOC) QT_POST_CXX17_API_IN_EXPORTED_CLASS - Q_IMPLICIT QDate(std::chrono::year_month_day ymd) + Q_IMPLICIT constexpr QDate(std::chrono::year_month_day date) noexcept + : jd(date.ok() ? stdSysDaysToJulianDay(date) : nullJd()) + {} + + QT_POST_CXX17_API_IN_EXPORTED_CLASS + Q_IMPLICIT constexpr QDate(std::chrono::year_month_day_last date) noexcept + : jd(date.ok() ? stdSysDaysToJulianDay(date) : nullJd()) + {} + + QT_POST_CXX17_API_IN_EXPORTED_CLASS + Q_IMPLICIT constexpr QDate(std::chrono::year_month_weekday date) noexcept + : jd(date.ok() ? stdSysDaysToJulianDay(date) : nullJd()) + {} + + QT_POST_CXX17_API_IN_EXPORTED_CLASS + Q_IMPLICIT constexpr QDate(std::chrono::year_month_weekday_last date) noexcept + : jd(date.ok() ? stdSysDaysToJulianDay(date) : nullJd()) + {} + + QT_POST_CXX17_API_IN_EXPORTED_CLASS + static constexpr QDate fromStdSysDays(const std::chrono::sys_days &days) noexcept { - if (!ymd.ok()) - jd = nullJd(); - else - *this = fromStdSysDays(ymd); + return QDate(stdSysDaysToJulianDay(days)); } QT_POST_CXX17_API_IN_EXPORTED_CLASS - Q_IMPLICIT QDate(std::chrono::year_month_day_last ymdl) + constexpr std::chrono::sys_days toStdSysDays() const noexcept { - if (!ymdl.ok()) - jd = nullJd(); - else - *this = fromStdSysDays(ymdl); - } - - QT_POST_CXX17_API_IN_EXPORTED_CLASS - Q_IMPLICIT QDate(std::chrono::year_month_weekday ymw) - { - if (!ymw.ok()) - jd = nullJd(); - else - *this = fromStdSysDays(ymw); - } - - QT_POST_CXX17_API_IN_EXPORTED_CLASS - Q_IMPLICIT QDate(std::chrono::year_month_weekday_last ymwl) - { - if (!ymwl.ok()) - jd = nullJd(); - else - *this = fromStdSysDays(ymwl); - } - - QT_POST_CXX17_API_IN_EXPORTED_CLASS - static QDate fromStdSysDays(const std::chrono::sys_days &days) - { - const QDate epoch(unixEpochJd()); - return epoch.addDays(days.time_since_epoch().count()); - } - - QT_POST_CXX17_API_IN_EXPORTED_CLASS - std::chrono::sys_days toStdSysDays() const - { - const QDate epoch(unixEpochJd()); - return std::chrono::sys_days(std::chrono::days(epoch.daysTo(*this))); + const qint64 days = isValid() ? jd - unixEpochJd() : 0; + return std::chrono::sys_days(std::chrono::days(days)); } #endif @@ -167,6 +150,21 @@ private: static constexpr inline qint64 maxJd() { return Q_INT64_C( 784354017364); } static constexpr inline qint64 unixEpochJd() { return Q_INT64_C(2440588); } +#if __cpp_lib_chrono >= 201907L + static constexpr qint64 stdSysDaysToJulianDay(const std::chrono::sys_days &days) noexcept + { + const auto epochDays = days.time_since_epoch().count(); + // minJd() and maxJd() fit into 40 bits. + if constexpr (sizeof(epochDays) >= 41) { + constexpr auto top = maxJd() - unixEpochJd(); + constexpr auto bottom = minJd() - unixEpochJd(); + if (epochDays > top || epochDays < bottom) + return nullJd(); + } + return unixEpochJd() + epochDays; + } +#endif // __cpp_lib_chrono >= 201907L + qint64 jd; friend class QDateTime; diff --git a/tests/auto/corelib/time/qdate/tst_qdate.cpp b/tests/auto/corelib/time/qdate/tst_qdate.cpp index 8da22918a7c..2ab928ef823 100644 --- a/tests/auto/corelib/time/qdate/tst_qdate.cpp +++ b/tests/auto/corelib/time/qdate/tst_qdate.cpp @@ -1745,6 +1745,38 @@ void tst_QDate::roundtrip() const QCOMPARE(loopDate.toJulianDay(), testDate.toJulianDay()); loopDate = loopDate.addDays(1); } + +#if __cpp_lib_chrono >= 201907L + // Test roundtrip for from/to std::chrono conversions. + // Compile-time test, to verify it's all constexpr. + using namespace std::chrono; + { + constexpr sys_days expected{days{minJd}}; + constexpr sys_days actual{QDate::fromStdSysDays(expected).toStdSysDays()}; + static_assert(actual == expected); + } + { + // constexpr year_month_day expected{sys_days{days{maxJd}}}; // Overflow at least on MSVC + constexpr year_month_day expected{1970y, January, 1d}; + constexpr sys_days actual{QDate(expected).toStdSysDays()}; + static_assert(actual == sys_days(expected)); + } + { + constexpr year_month_day_last expected{2001y, {October / last}}; + constexpr sys_days actual{QDate(expected).toStdSysDays()}; + static_assert(actual == sys_days(expected)); + } + { + constexpr year_month_weekday expected{2001y, December, Saturday[1]}; + constexpr sys_days actual{QDate(expected).toStdSysDays()}; + static_assert(actual == sys_days(expected)); + } + { + constexpr year_month_weekday_last expected{2001y, November, Friday[last]}; + constexpr sys_days actual{QDate(expected).toStdSysDays()}; + static_assert(actual == sys_days(expected)); + } +#endif // __cpp_lib_chrono >= 201907L } void tst_QDate::qdebug() const