QDate: add conversions for std::chrono calendaring classes

std::chrono::year_month_day and related classes offer very
convenient to specify dates.

This patch adds implicit constructors to QDate to support this
convenience, e.g.:

  // YYYY-MM-DD, DD-MM-YYYY, MM-DD-YYYY formats:
  QDate d1 = 1985y / December / 8;
  QDate d2 = 8d / December / 1985;
  QDate d3 = December / 8d / 1985;

  // Indexed weekday:
  QDate d4 = 2000y / January / Monday[0];
  QDate d5 = 2000y / January / Monday[last];

and so on.

These are all implemented using the conversion from the std
calendaring classes to sys_days. Conversions between sys_days
and QDate are also added, since they're basically "for free".

I don't expect "ordinary" users to stumble upon it, but it's
worthy mentioning that std::chrono::year *does* have a year
zero (hence, year_month_day in year 0 or below are offset
by one with the corresponding QDate). I've left a note
in the documentation.

[ChangeLog][QtCore][QDate] QDate (and therefore QDateTime)
is now constructible using the year/month/day/week classes
available in the std::chrono library. Moreover, it now
features conversions from and to std::chrono::sys_days.

Change-Id: I2a4f56423ac7d1469541cbb6a278a65b48878b4a
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Giuseppe D'Angelo 2022-04-03 03:18:32 +02:00
parent 33c88f86b5
commit 2e29f55f76
4 changed files with 159 additions and 0 deletions

View File

@ -216,3 +216,23 @@ QString string = "Tuesday, 23 April 12 22:51:41";
QString format = "dddd, d MMMM yy hh:mm:ss";
QDateTime valid = QDateTime::fromString(string, format);
//! [21]
//! [22]
// 23 April 2012:
QDate date = std::chrono::year_month_day(std::chrono::year(2012),
std::chrono::month(4),
std::chrono::day(23));
// Same, under `using std::chrono` convenience:
QDate dateWithLiterals1 = 23 / April / 2012y;
QDate dateWithLiterals2 = 2012y / April / 23;
// Last day of February 2000
QDate lastDayFeb2020 = 2000y / February / last;
// First Monday of January 2020:
QDate firstMonday = 2020y / January / Monday[0];
// Last Monday of January 2020:
QDate lastMonday = 2020y / January / Monday[last];
//! [22]

View File

@ -448,6 +448,54 @@ QDate::QDate(int y, int m, int d, QCalendar cal)
*this = cal.dateFromParts(y, m, d);
}
/*!
\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)
\since 6.4
Constructs a QDate representing the same date as \a ymd. This allows for
easy interoperability between the Standard Library calendaring classes and
Qt datetime classes.
For example:
\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
resulting QDate object will have an year one less than the one specified by
\a ymd.
\note This function requires C++20.
*/
/*!
\fn QDate QDate::fromStdSysDays(const std::chrono::sys_days &days)
\since 6.4
Returns a QDate \a days days after January 1st, 1970 (the UNIX epoch). If
\a days is negative, the returned date will be before the epoch.
\note This function requires C++20.
\sa toStdSysDays()
*/
/*!
\fn std::chrono::sys_days QDate::toStdSysDays() const
Returns the number of days between January 1st, 1970 (the UNIX epoch) and
this date, represented as a \c{std::chrono::sys_days} object. If this date
is before the epoch, the number of days will be negative.
\note This function requires C++20.
\sa fromStdSysDays(), daysTo()
*/
/*!
\fn bool QDate::isNull() const

View File

@ -68,6 +68,57 @@ public:
constexpr QDate() : jd(nullJd()) {}
QDate(int y, int m, int d);
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)
{
if (!ymd.ok())
jd = nullJd();
else
*this = fromStdSysDays(ymd);
}
QT_POST_CXX17_API_IN_EXPORTED_CLASS
Q_IMPLICIT QDate(std::chrono::year_month_day_last ymdl)
{
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)));
}
#endif
constexpr bool isNull() const { return !isValid(); }
constexpr bool isValid() const { return jd >= minJd() && jd <= maxJd(); }
@ -144,6 +195,7 @@ private:
static constexpr inline qint64 nullJd() { return (std::numeric_limits<qint64>::min)(); }
static constexpr inline qint64 minJd() { return Q_INT64_C(-784350574879); }
static constexpr inline qint64 maxJd() { return Q_INT64_C( 784354017364); }
static constexpr inline qint64 unixEpochJd() { return Q_INT64_C(2440588); }
qint64 jd;

View File

@ -199,6 +199,32 @@ void tst_QDate::isValid_data()
QTest::newRow("jd latest formula") << 1400000 << 12 << 31 << qint64(513060925) << true;
}
#if __cpp_lib_chrono >= 201907L
// QDate has a bigger range than year_month_date. The tests use this bigger
// range. However building a year_month_time with "out of range" data has
// unspecified results, so don't do that. See [time.cal.year],
// [time.cal.month], [time.cal.day]. Also, std::chrono::year has a year 0, so
// take that into account.
static std::optional<std::chrono::year_month_day> convertToStdYearMonthDay(int y, int m, int d)
{
using namespace std::chrono;
if (y >= int((year::min)())
&& y <= int((year::max)())
&& m >= 0
&& m <= 255
&& d >= 0
&& d <= 255)
{
if (y < 0)
++y;
return std::make_optional(year(y) / m / d);
}
return std::nullopt;
}
#endif
void tst_QDate::isValid()
{
QFETCH(int, year);
@ -218,6 +244,19 @@ void tst_QDate::isValid()
QCOMPARE(d.year(), year);
QCOMPARE(d.month(), month);
QCOMPARE(d.day(), day);
#if __cpp_lib_chrono >= 201907L
std::optional<std::chrono::year_month_day> ymd = convertToStdYearMonthDay(year, month, day);
if (ymd) {
QDate d = *ymd;
QCOMPARE(d.year(), year);
QCOMPARE(d.month(), month);
QCOMPARE(d.day(), day);
const std::chrono::sys_days qdateSysDays = d.toStdSysDays();
const std::chrono::sys_days ymdSysDays = *ymd;
QCOMPARE(qdateSysDays, ymdSysDays);
}
#endif
} else {
QCOMPARE(d.year(), 0);
QCOMPARE(d.month(), 0);