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 <thiago.macieira@intel.com>
This commit is contained in:
Ivan Solovev 2023-10-23 16:58:54 +02:00
parent bf42be723f
commit c4f7dba81e
3 changed files with 79 additions and 49 deletions

View File

@ -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.
*/

View File

@ -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;

View File

@ -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