Optimize QDate by shortcutting some gregorian calendar methods

Add some static methods to QGregorianCalendar, some of which serve to
implement its methods, that QDate can use to bypass vtables and
exploit fixed truths of the Gregorian calendar in its default
handling.

Change-Id: Iec191cdf4d52945dbd5679e609180cb4fe59b5fd
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Edward Welbourne 2019-08-08 19:40:32 +02:00
parent 108382e236
commit 20f69beae4
3 changed files with 139 additions and 15 deletions

View File

@ -74,6 +74,7 @@
#endif
#include "qcalendar.h"
#include "qgregoriancalendar_p.h"
QT_BEGIN_NAMESPACE
@ -105,6 +106,17 @@ static inline QDate fixedDate(QCalendar::YearMonthDay &&parts, QCalendar cal)
return cal.dateFromParts(parts);
}
static inline QDate fixedDate(QCalendar::YearMonthDay &&parts)
{
if (parts.year) {
parts.day = qMin(parts.day, QGregorianCalendar::monthLength(parts.month, parts.year));
qint64 jd;
if (QGregorianCalendar::julianFromParts(parts.year, parts.month, parts.day, &jd))
return QDate::fromJulianDay(jd);
}
return QDate();
}
/*****************************************************************************
Date/Time formatting helper functions
*****************************************************************************/
@ -340,7 +352,8 @@ static int fromOffsetString(const QStringRef &offsetString, bool *valid) noexcep
QDate::QDate(int y, int m, int d)
{
*this = QCalendar().dateFromParts(y, m, d);
if (!QGregorianCalendar::julianFromParts(y, m, d, &jd))
jd = nullJd();
}
QDate::QDate(int y, int m, int d, QCalendar cal)
@ -405,7 +418,12 @@ int QDate::year(QCalendar cal) const
int QDate::year() const
{
return year(QCalendar());
if (isValid()) {
const auto parts = QGregorianCalendar::partsFromJulian(jd);
if (parts.isValid())
return parts.year;
}
return 0;
}
/*!
@ -452,7 +470,12 @@ int QDate::month(QCalendar cal) const
int QDate::month() const
{
return month(QCalendar());
if (isValid()) {
const auto parts = QGregorianCalendar::partsFromJulian(jd);
if (parts.isValid())
return parts.month;
}
return 0;
}
/*!
@ -480,7 +503,12 @@ int QDate::day(QCalendar cal) const
int QDate::day() const
{
return day(QCalendar());
if (isValid()) {
const auto parts = QGregorianCalendar::partsFromJulian(jd);
if (parts.isValid())
return parts.day;
}
return 0;
}
/*!
@ -507,7 +535,7 @@ int QDate::dayOfWeek(QCalendar cal) const
int QDate::dayOfWeek() const
{
return dayOfWeek(QCalendar());
return isValid() ? QGregorianCalendar::weekDayOfJulian(jd) : 0;
}
/*!
@ -535,7 +563,12 @@ int QDate::dayOfYear(QCalendar cal) const
int QDate::dayOfYear() const
{
return dayOfYear(QCalendar());
if (isValid()) {
qint64 first;
if (QGregorianCalendar::julianFromParts(year(), 1, 1, &first))
return jd - first + 1;
}
return 0;
}
/*!
@ -563,7 +596,12 @@ int QDate::daysInMonth(QCalendar cal) const
int QDate::daysInMonth() const
{
return daysInMonth(QCalendar());
if (isValid()) {
const auto parts = QGregorianCalendar::partsFromJulian(jd);
if (parts.isValid())
return QGregorianCalendar::monthLength(parts.month, parts.year);
}
return 0;
}
/*!
@ -589,7 +627,7 @@ int QDate::daysInYear(QCalendar cal) const
int QDate::daysInYear() const
{
return daysInYear(QCalendar());
return isValid() ? QGregorianCalendar::leapTest(year()) ? 366 : 365 : 0;
}
/*!
@ -1303,7 +1341,11 @@ QString QDate::toString(const QString &format, QCalendar cal) const
*/
bool QDate::setDate(int year, int month, int day)
{
return setDate(year, month, day, QCalendar());
if (QGregorianCalendar::julianFromParts(year, month, day, &jd))
return true;
jd = nullJd();
return false;
}
/*!
@ -1339,7 +1381,7 @@ void QDate::getDate(int *year, int *month, int *day) const
{
QCalendar::YearMonthDay parts; // invalid by default
if (isValid())
parts = QCalendar().partsFromDate(*this);
parts = QGregorianCalendar::partsFromJulian(jd);
const bool ok = parts.isValid();
if (year)
@ -1428,7 +1470,30 @@ QDate QDate::addMonths(int nmonths, QCalendar cal) const
QDate QDate::addMonths(int nmonths) const
{
return addMonths(nmonths, QCalendar());
if (isNull())
return QDate();
if (nmonths == 0)
return *this;
auto parts = QGregorianCalendar::partsFromJulian(jd);
if (!parts.isValid())
return QDate();
Q_ASSERT(parts.year);
parts.month += nmonths;
while (parts.month <= 0) {
if (--parts.year) // skip over year 0
parts.month += 12;
}
while (parts.month > 12) {
parts.month -= 12;
if (!++parts.year) // skip over year 0
++parts.year;
}
return fixedDate(std::move(parts));
}
/*!
@ -1470,7 +1535,21 @@ QDate QDate::addYears(int nyears, QCalendar cal) const
QDate QDate::addYears(int nyears) const
{
return addYears(nyears, QCalendar());
if (isNull())
return QDate();
auto parts = QGregorianCalendar::partsFromJulian(jd);
if (!parts.isValid())
return QDate();
int old_y = parts.year;
parts.year += nyears;
// If we just crossed (or hit) a missing year zero, adjust year by +/- 1:
if ((old_y > 0) != (parts.year > 0) || !parts.year)
parts.year += nyears > 0 ? +1 : -1;
return fixedDate(std::move(parts));
}
/*!
@ -1719,7 +1798,7 @@ QDate QDate::fromString(const QString &string, const QString &format)
bool QDate::isValid(int year, int month, int day)
{
return QCalendar().isDateValid(year, month, day);
return QGregorianCalendar::validParts(year, month, day);
}
/*!
@ -1733,7 +1812,7 @@ bool QDate::isValid(int year, int month, int day)
bool QDate::isLeapYear(int y)
{
return QCalendar().isLeapYear(y);
return QGregorianCalendar::leapTest(y);
}
/*! \fn static QDate QDate::fromJulianDay(qint64 jd)

View File

@ -77,6 +77,11 @@ QCalendar::System QGregorianCalendar::calendarSystem() const
}
bool QGregorianCalendar::isLeapYear(int year) const
{
return leapTest(year);
}
bool QGregorianCalendar::leapTest(int year)
{
if (year == QCalendar::Unspecified)
return false;
@ -88,10 +93,37 @@ bool QGregorianCalendar::isLeapYear(int year) const
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}
// Duplicating code from QRomanCalendar, but inlining isLeapYear() as leapTest():
int QGregorianCalendar::monthLength(int month, int year)
{
if (month < 1 || month > 12)
return 0;
if (month == 2)
return leapTest(year) ? 29 : 28;
return 30 | ((month & 1) ^ (month >> 3));
}
bool QGregorianCalendar::validParts(int year, int month, int day)
{
return year && 0 < day && day <= monthLength(month, year);
}
int QGregorianCalendar::weekDayOfJulian(qint64 jd)
{
return qMod(jd, 7) + 1;
}
bool QGregorianCalendar::dateToJulianDay(int year, int month, int day, qint64 *jd) const
{
return julianFromParts(year, month, day, jd);
}
bool QGregorianCalendar::julianFromParts(int year, int month, int day, qint64 *jd)
{
Q_ASSERT(jd);
if (!isDateValid(year, month, day))
if (!validParts(year, month, day))
return false;
if (year < 0)
@ -111,6 +143,11 @@ bool QGregorianCalendar::dateToJulianDay(int year, int month, int day, qint64 *j
}
QCalendar::YearMonthDay QGregorianCalendar::julianDayToDate(qint64 jd) const
{
return partsFromJulian(jd);
}
QCalendar::YearMonthDay QGregorianCalendar::partsFromJulian(qint64 jd)
{
/*
* Math from The Calendar FAQ at http://www.tondering.dk/claus/cal/julperiod.php

View File

@ -75,6 +75,14 @@ public:
QLocale::FormatType format) const override;
QString standaloneMonthName(const QLocale &locale, int month, int year,
QLocale::FormatType format) const override;
// Static optimized versions for the benefit of QDate:
static int weekDayOfJulian(qint64 jd);
static bool leapTest(int year);
static int monthLength(int month, int year);
static bool validParts(int year, int month, int day);
static QCalendar::YearMonthDay partsFromJulian(qint64 jd);
static bool julianFromParts(int year, int month, int day, qint64 *jd);
};
QT_END_NAMESPACE