From 8327fa7c11f6c84ccc66be4365ee282a76288788 Mon Sep 17 00:00:00 2001 From: John Layt Date: Thu, 25 Aug 2011 07:54:16 +0100 Subject: QDateTime: Store Julian Day as qint64 Store the QDate Julian Day number as an qint64 instead of uint32 to enable support for dates before 2 January 4713 BCE. This changes the possible date range to be approx 2.5 Quadrillion BC to 2.5 Quadrillion AD. A qint32 was not used as it only covers 5 million BCE to 5 million CE which does include Geological or Astronomical time. The effective supported date range is currently 4800 BCE to 1.4 million CE due to restrictions in existing conversion formulas. The effective range will be extended later with new formulas. Change-Id: Ib4345369455b31d4edae8c933b7721e76414e914 Reviewed-by: Thiago Macieira --- dist/changes-5.0.0 | 6 + src/corelib/tools/qdatetime.cpp | 145 ++++++----- src/corelib/tools/qdatetime.h | 36 ++- tests/auto/corelib/tools/qdate/tst_qdate.cpp | 279 ++++++++++++++++----- .../auto/corelib/tools/qdatetime/tst_qdatetime.cpp | 12 +- 5 files changed, 334 insertions(+), 144 deletions(-) diff --git a/dist/changes-5.0.0 b/dist/changes-5.0.0 index 8e1f6e3575..ed5ddc240d 100644 --- a/dist/changes-5.0.0 +++ b/dist/changes-5.0.0 @@ -247,6 +247,12 @@ QtCore 0 and QDate::shortMonthName() will return QString(). * Adding days to a null QDate or seconds to a null QTime will no longer return a valid QDate/QTime. + * QDate stores the Julian Day as a qint64 extending date support across a + more interesting range, see the class documentation for details. + * Conversion to YMD form dates is only accurate between to 4800 BCE to + 1.4 million CE + * The QDate::addDays() and QDateTime::addDays() methods now take a qint64 + * The QDate::daysTo() and QDateTime::daysTo() methods now return a qint64 * QIntValidator and QDoubleValidator no longer fall back to using the C locale if diff --git a/src/corelib/tools/qdatetime.cpp b/src/corelib/tools/qdatetime.cpp index f34176ca55..e05eb371dd 100644 --- a/src/corelib/tools/qdatetime.cpp +++ b/src/corelib/tools/qdatetime.cpp @@ -76,9 +76,6 @@ QT_BEGIN_NAMESPACE enum { - FIRST_YEAR = -4713, - FIRST_MONTH = 1, - FIRST_DAY = 2, // ### Qt 5: make FIRST_DAY = 1, by support jd == 0 as valid SECS_PER_DAY = 86400, MSECS_PER_DAY = 86400000, SECS_PER_HOUR = 3600, @@ -95,7 +92,7 @@ static inline QDate fixedDate(int y, int m, int d) return result; } -static inline uint julianDayFromGregorianDate(int year, int month, int day) +static inline qint64 julianDayFromGregorianDate(qint64 year, int month, int day) { // Gregorian calendar starting from October 15, 1582 // Algorithm from Henry F. Fliegel and Thomas C. Van Flandern @@ -105,35 +102,34 @@ static inline uint julianDayFromGregorianDate(int year, int month, int day) + day - 32075; } -static uint julianDayFromDate(int year, int month, int day) +static qint64 julianDayFromDate(qint64 year, int month, int day) { - if (year < 0) - ++year; - if (year > 1582 || (year == 1582 && (month > 10 || (month == 10 && day >= 15)))) { return julianDayFromGregorianDate(year, month, day); } else if (year < 1582 || (year == 1582 && (month < 10 || (month == 10 && day <= 4)))) { // Julian calendar until October 4, 1582 // Algorithm from Frequently Asked Questions about Calendars by Claus Toendering + if (year < 0) + ++year; int a = (14 - month) / 12; return (153 * (month + (12 * a) - 3) + 2) / 5 + (1461 * (year + 4800 - a)) / 4 + day - 32083; } else { // the day following October 4, 1582 is October 15, 1582 - return 0; + return std::numeric_limits::min(); // i.e. nullJd() } } -static void getDateFromJulianDay(uint julianDay, int *year, int *month, int *day) +static void getDateFromJulianDay(qint64 julianDay, int *year, int *month, int *day) { int y, m, d; if (julianDay >= 2299161) { // Gregorian calendar starting from October 15, 1582 // This algorithm is from Henry F. Fliegel and Thomas C. Van Flandern - qulonglong ell, n, i, j; - ell = qulonglong(julianDay) + 68569; + qint64 ell, n, i, j; //TODO These will need to be bigger to prevent overflow!!! + ell = julianDay + 68569; n = (4 * ell) / 146097; ell = ell - (146097 * n + 3) / 4; i = (4000 * (ell + 1)) / 1461001; @@ -147,9 +143,9 @@ static void getDateFromJulianDay(uint julianDay, int *year, int *month, int *day // Julian calendar until October 4, 1582 // Algorithm from Frequently Asked Questions about Calendars by Claus Toendering julianDay += 32082; - int dd = (4 * julianDay + 3) / 1461; - int ee = julianDay - (1461 * dd) / 4; - int mm = ((5 * ee) + 2) / 153; + qint64 dd = (4 * julianDay + 3) / 1461; //TODO These may need to be bigger to prevent overflow!!! + qint64 ee = julianDay - (1461 * dd) / 4; //TODO These may need to be bigger to prevent overflow!!! + qint64 mm = ((5 * ee) + 2) / 153; //TODO These may need to be bigger to prevent overflow!!! d = ee - (153 * mm + 2) / 5 + 1; m = mm + 3 - 12 * (mm / 10); y = dd - 4800 + (mm / 10); @@ -261,16 +257,27 @@ static QString fmtDateTime(const QString& f, const QTime* dt = 0, const QDate* d There is no year 0. Dates in that year are considered invalid. The year -1 is the year "1 before Christ" or "1 before current era." - The day before 0001-01-01 is December 31st, 1 BCE. + The day before 1 January 1 CE is 31 December 1 BCE. \section2 Range of Valid Dates - The range of valid dates is from January 2nd, 4713 BCE, to - sometime in the year 11 million CE. The Julian Day returned by - QDate::toJulianDay() is a number in the contiguous range from 1 to - \e{overflow}, even across QDateTime's "date holes". It is suitable - for use in applications that must convert a QDateTime to a date in - another calendar system, e.g., Hebrew, Islamic or Chinese. + Dates are stored internally as a Julian Day number, an interger count of + every day in a contiguous range, with 24 November 4714 BCE in the Gregorian + calendar being Julian Day 0 (1 January 4713 BCE in the Julian calendar). + As well as being an efficient and accurate way of storing an absolute date, + it is suitable for converting a Date into other calendar systems such as + Hebrew, Islamic or Chinese. The Julian Day number can be obtained using + QDate::toJulianDay() and can be set using QDate::fromJulianDay(). + + The range of dates able to be stored by QDate as a Julian Day number is + limited for convenience from std::numeric_limits::min() / 2 to + std::numeric_limits::max() / 2, which on most platforms means + from around 2.5 quadrillion BCE to around 2.5 quadrillion CE, effectively + covering the full range of astronomical time. The range of Julian Days + able to be accurately converted to and from valid YMD form Dates is + restricted to 1 January 4800 BCE to 31 December 1400000 CE due to + shortcomings in the available conversion formulas. Conversions outside this + range are not guaranteed to be correct. This may change in the future. \sa QTime, QDateTime, QDateEdit, QDateTimeEdit, QCalendarWidget */ @@ -287,8 +294,7 @@ static QString fmtDateTime(const QString& f, const QTime* dt = 0, const QDate* d Constructs a date with year \a y, month \a m and day \a d. If the specified date is invalid, the date is not set and - isValid() returns false. A date before 2 January 4713 B.C. is - considered invalid. + isValid() returns false. \warning Years 0 to 99 are interpreted as is, i.e., years 0-99. @@ -315,20 +321,17 @@ QDate::QDate(int y, int m, int d) /*! + \fn bool isValid() const + Returns true if this date is valid; otherwise returns false. \sa isNull() */ -bool QDate::isValid() const -{ - return !isNull(); -} - /*! Returns the year of this date. Negative numbers indicate years - before 1 A.D. = 1 C.E., such that year -44 is 44 B.C. + before 1 CE, such that year -44 is 44 BCE. Returns 0 if the date is invalid. @@ -410,7 +413,10 @@ int QDate::dayOfWeek() const if (isNull()) return 0; - return (jd % 7) + 1; + if (jd >= 0) + return (jd % 7) + 1; + else + return ((jd + 1) % 7) + 7; } /*! @@ -896,19 +902,21 @@ QString QDate::toString(const QString& format) const the date is valid; otherwise returns false. If the specified date is invalid, the QDate object is set to be - invalid. Any date before 2 January 4713 B.C. is considered invalid. + Note that any date before 4800 BCE or after about 1.4 million CE + may not be accurately stored. + \sa isValid() */ bool QDate::setDate(int year, int month, int day) { - if (!isValid(year, month, day)) { - jd = 0; - } else { + if (isValid(year, month, day)) jd = julianDayFromDate(year, month, day); - } - return jd != 0; + else + jd = nullJd(); + + return isValid(); } /*! @@ -919,6 +927,9 @@ bool QDate::setDate(int year, int month, int day) Returns 0 if the date is invalid. + Note that any date before 4800 BCE or after about 1.4 million CE + may not be accurately stored. + \sa year(), month(), day(), isValid() */ void QDate::getDate(int *year, int *month, int *day) @@ -945,17 +956,24 @@ void QDate::getDate(int *year, int *month, int *day) \sa addMonths() addYears() daysTo() */ -QDate QDate::addDays(int ndays) const +QDate QDate::addDays(qint64 ndays) const { if (isNull()) return QDate(); QDate d; + quint64 diff = 0; + // this is basically "d.jd = jd + ndays" with checks for integer overflow + // Due to limits on minJd() and maxJd() we know diff will never overflow if (ndays >= 0) - d.jd = (jd + ndays >= jd) ? jd + ndays : 0; + diff = maxJd() - jd; else - d.jd = (jd + ndays < jd) ? jd + ndays : 0; + diff = jd - minJd(); + + if (abs(ndays) <= diff) + d.jd = jd + ndays; + return d; } @@ -1072,11 +1090,12 @@ QDate QDate::addYears(int nyears) const \sa addDays() */ -int QDate::daysTo(const QDate &d) const +qint64 QDate::daysTo(const QDate &d) const { if (isNull() || d.isNull()) return 0; + // Due to limits on minJd() and maxJd() we know this will never overflow return d.jd - jd; } @@ -1313,16 +1332,13 @@ QDate QDate::fromString(const QString &string, const QString &format) bool QDate::isValid(int year, int month, int day) { - if (year < FIRST_YEAR - || (year == FIRST_YEAR && - (month < FIRST_MONTH - || (month == FIRST_MONTH && day < FIRST_DAY))) - || year == 0) // there is no year 0 in the Julian calendar + // there is no year 0 in the Julian calendar + if (year == 0) return false; // passage from Julian to Gregorian calendar if (year == 1582 && month == 10 && day > 4 && day < 15) - return 0; + return false; return (day > 0 && month > 0 && month <= 12) && (day <= monthDays[month] || (day == 29 && month == 2 && isLeapYear(year))); @@ -2096,16 +2112,27 @@ int QTime::elapsed() const There is no year 0. Dates in that year are considered invalid. The year -1 is the year "1 before Christ" or "1 before current era." - The day before 0001-01-01 is December 31st, 1 BCE. + The day before 1 January 1 CE is 31 December 1 BCE. \section2 Range of Valid Dates - The range of valid dates is from January 2nd, 4713 BCE, to - sometime in the year 11 million CE. The Julian Day returned by - QDate::toJulianDay() is a number in the contiguous range from 1 to - \e{overflow}, even across QDateTime's "date holes". It is suitable - for use in applications that must convert a QDateTime to a date in - another calendar system, e.g., Hebrew, Islamic or Chinese. + Dates are stored internally as a Julian Day number, an interger count of + every day in a contiguous range, with 24 November 4714 BCE in the Gregorian + calendar being Julian Day 0 (1 January 4713 BCE in the Julian calendar). + As well as being an efficient and accurate way of storing an absolute date, + it is suitable for converting a Date into other calendar systems such as + Hebrew, Islamic or Chinese. The Julian Day number can be obtained using + QDate::toJulianDay() and can be set using QDate::fromJulianDay(). + + The range of dates able to be stored by QDate as a Julian Day number is + limited for convenience from std::numeric_limits::min() / 2 to + std::numeric_limits::max() / 2, which on most platforms means + from around 2.5 quadrillion BCE to around 2.5 quadrillion CE, effectively + covering the full range of astronomical time. The range of Julian Days + able to be accurately converted to and from valid YMD form Dates is + restricted to 1 January 4800 BCE to 31 December 1400000 CE due to + shortcomings in the available conversion formulas. Conversions outside this + range are not guaranteed to be correct. This may change in the future. The Gregorian calendar was introduced in different places around the world on different dates. QDateTime uses QDate to store the @@ -2656,7 +2683,7 @@ QString QDateTime::toString(const QString& format) const \sa daysTo(), addMonths(), addYears(), addSecs() */ -QDateTime QDateTime::addDays(int ndays) const +QDateTime QDateTime::addDays(qint64 ndays) const { return QDateTime(d->date.addDays(ndays), d->time, timeSpec()); } @@ -2707,7 +2734,7 @@ QDateTime QDateTimePrivate::addMSecs(const QDateTime &dt, qint64 msecs) */ void QDateTimePrivate::addMSecs(QDate &utcDate, QTime &utcTime, qint64 msecs) { - int dd = utcDate.toJulianDay(); + qint64 dd = utcDate.toJulianDay(); int tt = QTime(0, 0, 0).msecsTo(utcTime); int sign = 1; if (msecs < 0) { @@ -2767,7 +2794,7 @@ QDateTime QDateTime::addMSecs(qint64 msecs) const \sa addDays(), secsTo(), msecsTo() */ -int QDateTime::daysTo(const QDateTime &other) const +qint64 QDateTime::daysTo(const QDateTime &other) const { return d->date.daysTo(other.d->date); } @@ -3577,7 +3604,7 @@ void QDateTime::detach() QDataStream &operator<<(QDataStream &out, const QDate &date) { - return out << (quint32)(date.jd); + return out << (qint64)(date.jd); } /*! @@ -3590,7 +3617,7 @@ QDataStream &operator<<(QDataStream &out, const QDate &date) QDataStream &operator>>(QDataStream &in, QDate &date) { - quint32 jd; + qint64 jd; in >> jd; date.jd = jd; return in; diff --git a/src/corelib/tools/qdatetime.h b/src/corelib/tools/qdatetime.h index 056b0debf1..af257eaa40 100644 --- a/src/corelib/tools/qdatetime.h +++ b/src/corelib/tools/qdatetime.h @@ -46,6 +46,17 @@ #include #include +// windows.h defines these identifiers, so undefine it +// ### figure out where in Qt we include it too soon +#ifdef max +# undef max +#endif +#ifdef min +# undef min +#endif + +#include + QT_BEGIN_HEADER QT_BEGIN_NAMESPACE @@ -59,11 +70,11 @@ public: StandaloneFormat }; public: - QDate() { jd = 0; } + QDate() { jd = nullJd(); } QDate(int y, int m, int d); - bool isNull() const { return jd == 0; } - bool isValid() const; + bool isNull() const { return !isValid(); } + bool isValid() const { return jd >= minJd() && jd <= maxJd(); } int year() const; int month() const; @@ -93,10 +104,10 @@ QT_DEPRECATED inline bool setYMD(int y, int m, int d) void getDate(int *year, int *month, int *day); - QDate addDays(int days) const; + QDate addDays(qint64 days) const; QDate addMonths(int months) const; QDate addYears(int years) const; - int daysTo(const QDate &) const; + qint64 daysTo(const QDate &) const; bool operator==(const QDate &other) const { return jd == other.jd; } bool operator!=(const QDate &other) const { return jd != other.jd; } @@ -113,11 +124,16 @@ QT_DEPRECATED inline bool setYMD(int y, int m, int d) static bool isValid(int y, int m, int d); static bool isLeapYear(int year); - static inline QDate fromJulianDay(int jd) { QDate d; d.jd = jd; return d; } - inline int toJulianDay() const { return jd; } + static inline QDate fromJulianDay(qint64 jd) + { QDate d; if (jd >= minJd() && jd <= maxJd()) d.jd = jd; return d; } + inline qint64 toJulianDay() const { return jd; } private: - uint jd; + static inline qint64 nullJd() { return std::numeric_limits::min(); } + static inline qint64 minJd() { return std::numeric_limits::min() / 2; } + static inline qint64 maxJd() { return (std::numeric_limits::max()) / 2; } + + qint64 jd; friend class QDateTime; friend class QDateTimePrivate; @@ -220,7 +236,7 @@ public: QString toString(Qt::DateFormat f = Qt::TextDate) const; QString toString(const QString &format) const; #endif - QDateTime addDays(int days) const; + QDateTime addDays(qint64 days) const; QDateTime addMonths(int months) const; QDateTime addYears(int years) const; QDateTime addSecs(int secs) const; @@ -228,7 +244,7 @@ public: QDateTime toTimeSpec(Qt::TimeSpec spec) const; inline QDateTime toLocalTime() const { return toTimeSpec(Qt::LocalTime); } inline QDateTime toUTC() const { return toTimeSpec(Qt::UTC); } - int daysTo(const QDateTime &) const; + qint64 daysTo(const QDateTime &) const; int secsTo(const QDateTime &) const; qint64 msecsTo(const QDateTime &) const; diff --git a/tests/auto/corelib/tools/qdate/tst_qdate.cpp b/tests/auto/corelib/tools/qdate/tst_qdate.cpp index 72c35354bf..4ce2a51902 100644 --- a/tests/auto/corelib/tools/qdate/tst_qdate.cpp +++ b/tests/auto/corelib/tools/qdate/tst_qdate.cpp @@ -48,6 +48,8 @@ class tst_QDate : public QObject Q_OBJECT private slots: void toString(); + void isNull_data(); + void isNull(); void isValid_data(); void isValid(); void julianDay_data(); @@ -64,6 +66,7 @@ private slots: void weekNumber_invalid(); void weekNumber_data(); void weekNumber(); + void julianDaysLimits(); void addDays_data(); void addDays(); void addMonths_data(); @@ -101,66 +104,92 @@ private slots: Q_DECLARE_METATYPE(QDate) +void tst_QDate::isNull_data() +{ + QTest::addColumn("jd"); + QTest::addColumn("null"); + + qint64 minJd = std::numeric_limits::min() / 2; + qint64 maxJd = std::numeric_limits::max() / 2; + + QTest::newRow("qint64 min") << std::numeric_limits::min() << true; + QTest::newRow("minJd - 1") << minJd - 1 << true; + QTest::newRow("minJd") << minJd << false; + QTest::newRow("minJd + 1") << minJd + 1 << false; + QTest::newRow("maxJd - 1") << maxJd - 1 << false; + QTest::newRow("maxJd") << maxJd << false; + QTest::newRow("maxJd + 1") << maxJd + 1 << true; + QTest::newRow("qint64 max") << std::numeric_limits::max() << true; +} + +void tst_QDate::isNull() +{ + QFETCH(qint64, jd); + + QDate d = QDate::fromJulianDay(jd); + QTEST(d.isNull(), "null"); +} + void tst_QDate::isValid_data() { + qint64 nullJd = std::numeric_limits::min(); + QTest::addColumn("year"); QTest::addColumn("month"); QTest::addColumn("day"); - QTest::addColumn("jd"); + QTest::addColumn("jd"); QTest::addColumn("valid"); - QTest::newRow("0-0-0") << 0 << 0 << 0 << 0U << false; - QTest::newRow("month 0") << 2000 << 0 << 1 << 0U << false; - QTest::newRow("day 0") << 2000 << 1 << 0 << 0U << false; + QTest::newRow("0-0-0") << 0 << 0 << 0 << nullJd << false; + QTest::newRow("month 0") << 2000 << 0 << 1 << nullJd << false; + QTest::newRow("day 0") << 2000 << 1 << 0 << nullJd << false; - QTest::newRow("month 13") << 2000 << 13 << 1 << 0U << false; + QTest::newRow("month 13") << 2000 << 13 << 1 << nullJd << false; // test leap years - QTest::newRow("non-leap") << 2006 << 2 << 29 << 0U << false; - QTest::newRow("normal leap") << 2004 << 2 << 29 << 2453065U << true; - QTest::newRow("century leap") << 1900 << 2 << 29 << 0U << false; - QTest::newRow("century leap") << 2100 << 2 << 29 << 0U << false; - QTest::newRow("400-years leap") << 2000 << 2 << 29 << 2451604U << true; - QTest::newRow("400-years leap 2") << 2400 << 2 << 29 << 2597701U << true; - QTest::newRow("400-years leap 3") << 1600 << 2 << 29 << 2305507U << true; - QTest::newRow("year 0") << 0 << 2 << 27 << 0U << false; + QTest::newRow("non-leap") << 2006 << 2 << 29 << nullJd << false; + QTest::newRow("normal leap") << 2004 << 2 << 29 << qint64(2453065) << true; + QTest::newRow("century leap") << 1900 << 2 << 29 << nullJd << false; + QTest::newRow("century leap") << 2100 << 2 << 29 << nullJd << false; + QTest::newRow("400-years leap") << 2000 << 2 << 29 << qint64(2451604) << true; + QTest::newRow("400-years leap 2") << 2400 << 2 << 29 << qint64(2597701) << true; + QTest::newRow("400-years leap 3") << 1600 << 2 << 29 << qint64(2305507) << true; + QTest::newRow("year 0") << 0 << 2 << 27 << nullJd << false; // test the number of days in months: - QTest::newRow("jan") << 2000 << 1 << 31 << 2451575U << true; - QTest::newRow("feb") << 2000 << 2 << 29 << 2451604U << true; // same data as 400-years leap - QTest::newRow("mar") << 2000 << 3 << 31 << 2451635U << true; - QTest::newRow("apr") << 2000 << 4 << 30 << 2451665U << true; - QTest::newRow("may") << 2000 << 5 << 31 << 2451696U << true; - QTest::newRow("jun") << 2000 << 6 << 30 << 2451726U << true; - QTest::newRow("jul") << 2000 << 7 << 31 << 2451757U << true; - QTest::newRow("aug") << 2000 << 8 << 31 << 2451788U << true; - QTest::newRow("sep") << 2000 << 9 << 30 << 2451818U << true; - QTest::newRow("oct") << 2000 << 10 << 31 << 2451849U << true; - QTest::newRow("nov") << 2000 << 11 << 30 << 2451879U << true; - QTest::newRow("dec") << 2000 << 12 << 31 << 2451910U << true; + QTest::newRow("jan") << 2000 << 1 << 31 << qint64(2451575) << true; + QTest::newRow("feb") << 2000 << 2 << 29 << qint64(2451604) << true; // same data as 400-years leap + QTest::newRow("mar") << 2000 << 3 << 31 << qint64(2451635) << true; + QTest::newRow("apr") << 2000 << 4 << 30 << qint64(2451665) << true; + QTest::newRow("may") << 2000 << 5 << 31 << qint64(2451696) << true; + QTest::newRow("jun") << 2000 << 6 << 30 << qint64(2451726) << true; + QTest::newRow("jul") << 2000 << 7 << 31 << qint64(2451757) << true; + QTest::newRow("aug") << 2000 << 8 << 31 << qint64(2451788) << true; + QTest::newRow("sep") << 2000 << 9 << 30 << qint64(2451818) << true; + QTest::newRow("oct") << 2000 << 10 << 31 << qint64(2451849) << true; + QTest::newRow("nov") << 2000 << 11 << 30 << qint64(2451879) << true; + QTest::newRow("dec") << 2000 << 12 << 31 << qint64(2451910) << true; // and invalid dates: - QTest::newRow("ijan") << 2000 << 1 << 32 << 0U << false; - QTest::newRow("ifeb") << 2000 << 2 << 30 << 0U << false; - QTest::newRow("imar") << 2000 << 3 << 32 << 0U << false; - QTest::newRow("iapr") << 2000 << 4 << 31 << 0U << false; - QTest::newRow("imay") << 2000 << 5 << 32 << 0U << false; - QTest::newRow("ijun") << 2000 << 6 << 31 << 0U << false; - QTest::newRow("ijul") << 2000 << 7 << 32 << 0U << false; - QTest::newRow("iaug") << 2000 << 8 << 32 << 0U << false; - QTest::newRow("isep") << 2000 << 9 << 31 << 0U << false; - QTest::newRow("ioct") << 2000 << 10 << 32 << 0U << false; - QTest::newRow("inov") << 2000 << 11 << 31 << 0U << false; - QTest::newRow("idec") << 2000 << 12 << 32 << 0U << false; + QTest::newRow("ijan") << 2000 << 1 << 32 << nullJd << false; + QTest::newRow("ifeb") << 2000 << 2 << 30 << nullJd << false; + QTest::newRow("imar") << 2000 << 3 << 32 << nullJd << false; + QTest::newRow("iapr") << 2000 << 4 << 31 << nullJd << false; + QTest::newRow("imay") << 2000 << 5 << 32 << nullJd << false; + QTest::newRow("ijun") << 2000 << 6 << 31 << nullJd << false; + QTest::newRow("ijul") << 2000 << 7 << 32 << nullJd << false; + QTest::newRow("iaug") << 2000 << 8 << 32 << nullJd << false; + QTest::newRow("isep") << 2000 << 9 << 31 << nullJd << false; + QTest::newRow("ioct") << 2000 << 10 << 32 << nullJd << false; + QTest::newRow("inov") << 2000 << 11 << 31 << nullJd << false; + QTest::newRow("idec") << 2000 << 12 << 32 << nullJd << false; // the beginning of the Julian Day calendar: - QTest::newRow("jd negative1") << -4714 << 1 << 1 << 0U << false; - QTest::newRow("jd negative2") << -4713 << 1 << 1 << 0U << false; - QTest::newRow("jd negative3") << -4713 << 1 << 2 << 1U << true; - QTest::newRow("jd negative4") << -4713 << 1 << 3 << 2U << true; - QTest::newRow("jd 0") << -4713 << 1 << 1 << 0U << false; - QTest::newRow("jd 1") << -4713 << 1 << 2 << 1U << true; - QTest::newRow("imminent overflow") << 11754508 << 12 << 13 << 4294967295U << true; + QTest::newRow("jd earliest formula") << -4800 << 1 << 1 << qint64( -31776) << true; + QTest::newRow("jd -1") << -4714 << 12 << 31 << qint64( -1) << true; + QTest::newRow("jd 0") << -4713 << 1 << 1 << qint64( 0) << true; + QTest::newRow("jd 1") << -4713 << 1 << 2 << qint64( 1) << true; + QTest::newRow("jd latest formula") << 1400000 << 12 << 31 << qint64(513060925) << true; } void tst_QDate::isValid() @@ -168,7 +197,7 @@ void tst_QDate::isValid() QFETCH(int, year); QFETCH(int, month); QFETCH(int, day); - QFETCH(uint, jd); + QFETCH(qint64, jd); QFETCH(bool, valid); QCOMPARE(QDate::isValid(year, month, day), valid); @@ -176,6 +205,7 @@ void tst_QDate::isValid() QDate d; d.setDate(year, month, day); QCOMPARE(d.isValid(), valid); + QCOMPARE(d.toJulianDay(), jd); if (valid) { QCOMPARE(d.year(), year); @@ -198,15 +228,15 @@ void tst_QDate::julianDay() QFETCH(int, year); QFETCH(int, month); QFETCH(int, day); - QFETCH(uint, jd); + QFETCH(qint64, jd); { QDate d; d.setDate(year, month, day); - QCOMPARE(uint(d.toJulianDay()), jd); + QCOMPARE(d.toJulianDay(), jd); } - if (jd) { + if (jd != std::numeric_limits::min()) { QDate d = QDate::fromJulianDay(jd); QCOMPARE(d.year(), year); QCOMPARE(d.month(), month); @@ -233,7 +263,15 @@ void tst_QDate::dayOfWeek_data() QTest::newRow("data9") << 1815 << 6 << 15 << 4; QTest::newRow("data10") << 1500 << 1 << 1 << 3; QTest::newRow("data11") << -1500 << 1 << 1 << 7; - QTest::newRow("data12") << -4713 << 1 << 2 << 2; + QTest::newRow("data12") << -4800 << 1 << 1 << 5; + QTest::newRow("data13") << -4800 << 1 << 4 << 1; + QTest::newRow("data14") << -4800 << 1 << 5 << 2; + QTest::newRow("data15") << -4800 << 1 << 6 << 3; + QTest::newRow("data16") << -4800 << 1 << 7 << 4; + QTest::newRow("data17") << -4800 << 1 << 8 << 5; + QTest::newRow("data18") << -4800 << 1 << 9 << 6; + QTest::newRow("data19") << -4800 << 1 << 10 << 7; + QTest::newRow("data20") << -4800 << 1 << 11 << 1; } void tst_QDate::dayOfWeek() @@ -266,8 +304,8 @@ void tst_QDate::dayOfYear_data() QTest::newRow("data9") << 1582 << 12 << 31 << 355; QTest::newRow("data10") << 1500 << 1 << 1 << 1; QTest::newRow("data11") << -1500 << 12 << 31 << 365; - QTest::newRow("data12") << -4713 << 1 << 2 << 2; - QTest::newRow("data13") << -4713 << 12 << 31 << 366; + QTest::newRow("data12") << -4800 << 1 << 1 << 1; + QTest::newRow("data13") << -4800 << 12 << 31 << 365; } void tst_QDate::dayOfYear() @@ -393,6 +431,68 @@ void tst_QDate::weekNumber_invalid() QCOMPARE( dt.weekNumber( &yearNumber ), 0 ); } +void tst_QDate::julianDaysLimits() +{ + qint64 min = std::numeric_limits::min(); + qint64 max = std::numeric_limits::max(); + qint64 minJd = std::numeric_limits::min() / 2; + qint64 maxJd = std::numeric_limits::max() / 2; + + QDate maxDate = QDate::fromJulianDay(maxJd); + QDate minDate = QDate::fromJulianDay(minJd); + QDate zeroDate = QDate::fromJulianDay(0); + + QDate dt = QDate::fromJulianDay(min); + QCOMPARE(dt.isValid(), false); + dt = QDate::fromJulianDay(minJd - 1); + QCOMPARE(dt.isValid(), false); + dt = QDate::fromJulianDay(minJd); + QCOMPARE(dt.isValid(), true); + dt = QDate::fromJulianDay(minJd + 1); + QCOMPARE(dt.isValid(), true); + dt = QDate::fromJulianDay(maxJd - 1); + QCOMPARE(dt.isValid(), true); + dt = QDate::fromJulianDay(maxJd); + QCOMPARE(dt.isValid(), true); + dt = QDate::fromJulianDay(maxJd + 1); + QCOMPARE(dt.isValid(), false); + dt = QDate::fromJulianDay(max); + QCOMPARE(dt.isValid(), false); + + dt = maxDate.addDays(1); + QCOMPARE(dt.isValid(), false); + dt = maxDate.addDays(0); + QCOMPARE(dt.isValid(), true); + dt = maxDate.addDays(-1); + QCOMPARE(dt.isValid(), true); + dt = maxDate.addDays(max); + QCOMPARE(dt.isValid(), false); + dt = maxDate.addDays(min); + QCOMPARE(dt.isValid(), false); + + dt = minDate.addDays(-1); + QCOMPARE(dt.isValid(), false); + dt = minDate.addDays(0); + QCOMPARE(dt.isValid(), true); + dt = minDate.addDays(1); + QCOMPARE(dt.isValid(), true); + dt = minDate.addDays(min); + QCOMPARE(dt.isValid(), false); + dt = minDate.addDays(max); + QCOMPARE(dt.isValid(), true); + + dt = zeroDate.addDays(-1); + QCOMPARE(dt.isValid(), true); + dt = zeroDate.addDays(0); + QCOMPARE(dt.isValid(), true); + dt = zeroDate.addDays(1); + QCOMPARE(dt.isValid(), true); + dt = zeroDate.addDays(min); + QCOMPARE(dt.isValid(), false); + dt = zeroDate.addDays(max); + QCOMPARE(dt.isValid(), false); +} + void tst_QDate::addDays() { QFETCH( int, year ); @@ -435,9 +535,8 @@ void tst_QDate::addDays_data() QTest::newRow( "data10" ) << 2000 << 2 << 28 << -1 << 2000 << 2 << 27; QTest::newRow( "data11" ) << 2001 << 2 << 28 << -30 << 2001 << 1 << 29; - QDate invalid; - QTest::newRow( "data12" ) << -4713 << 1 << 2 << -2 - << invalid.year() << invalid.month() << invalid.day(); + QTest::newRow( "data12" ) << -4713 << 1 << 2 << -2 << -4714 << 12 << 31; + QTest::newRow( "data13" ) << -4713 << 1 << 2 << 2 << -4713 << 1 << 4; } void tst_QDate::addMonths() @@ -553,15 +652,31 @@ void tst_QDate::addYears_data() void tst_QDate::daysTo() { + qint64 minJd = std::numeric_limits::min() / 2; + qint64 maxJd = std::numeric_limits::max() / 2; + QDate dt1(2000, 1, 1); QDate dt2(2000, 1, 5); - QCOMPARE(dt1.daysTo(dt2), 4); - QCOMPARE(dt2.daysTo(dt1), -4); + QCOMPARE(dt1.daysTo(dt2), (qint64) 4); + QCOMPARE(dt2.daysTo(dt1), (qint64) -4); + dt1.setDate(0, 0, 0); - QCOMPARE(dt1.daysTo(dt2), 0); + QCOMPARE(dt1.daysTo(dt2), (qint64) 0); dt1.setDate(2000, 1, 1); dt2.setDate(0, 0, 0); - QCOMPARE(dt1.daysTo(dt2), 0); + QCOMPARE(dt1.daysTo(dt2), (qint64) 0); + + + QDate maxDate = QDate::fromJulianDay(maxJd); + QDate minDate = QDate::fromJulianDay(minJd); + QDate zeroDate = QDate::fromJulianDay(0); + + QCOMPARE(maxDate.daysTo(minDate), minJd - maxJd); + QCOMPARE(minDate.daysTo(maxDate), maxJd - minJd); + QCOMPARE(maxDate.daysTo(zeroDate), -maxJd); + QCOMPARE(zeroDate.daysTo(maxDate), maxJd); + QCOMPARE(minDate.daysTo(zeroDate), -minJd); + QCOMPARE(zeroDate.daysTo(minDate), minJd); } void tst_QDate::operator_eq_eq() @@ -798,6 +913,8 @@ void tst_QDate::toString_format() void tst_QDate::isLeapYear() { + QVERIFY(QDate::isLeapYear(-4801)); + QVERIFY(!QDate::isLeapYear(-4800)); QVERIFY(QDate::isLeapYear(-4445)); QVERIFY(!QDate::isLeapYear(-4444)); QVERIFY(!QDate::isLeapYear(-6)); @@ -1065,28 +1182,52 @@ void tst_QDate::roundtrip() const // year(), month(), day(), julianDayFromDate(), and getDateFromJulianDay() // to ensure they are internally consistent (but doesn't guarantee correct) - // Test Julian round trip in both BC and AD + // Test Julian round trip around JD 0 and current low end of valid range QDate testDate; - QDate loopDate = QDate::fromJulianDay(1684899); // 1 Jan 100 BC - while ( loopDate.toJulianDay() <= 1757948 ) { // 31 Dec 100 AD - testDate.setDate( loopDate.year(), loopDate.month(), loopDate.day() ); - QCOMPARE( loopDate.toJulianDay(), testDate.toJulianDay() ); + QDate loopDate = QDate::fromJulianDay(-31776); // 1 Jan 4800 BC + while (loopDate.toJulianDay() <= 5113) { // 31 Dec 4700 AD + testDate.setDate(loopDate.year(), loopDate.month(), loopDate.day()); + QCOMPARE(loopDate.toJulianDay(), testDate.toJulianDay()); + loopDate = loopDate.addDays(1); + } + + // Test Julian round trip in both BC and AD + loopDate = QDate::fromJulianDay(1684899); // 1 Jan 100 BC + while (loopDate.toJulianDay() <= 1757948) { // 31 Dec 100 AD + testDate.setDate(loopDate.year(), loopDate.month(), loopDate.day()); + QCOMPARE(loopDate.toJulianDay(), testDate.toJulianDay()); loopDate = loopDate.addDays(1); } // Test Julian and Gregorian round trip during changeover period loopDate = QDate::fromJulianDay(2298153); // 1 Jan 1580 AD - while ( loopDate.toJulianDay() <= 2300334 ) { // 31 Dec 1585 AD - testDate.setDate( loopDate.year(), loopDate.month(), loopDate.day() ); - QCOMPARE( loopDate.toJulianDay(), testDate.toJulianDay() ); + while (loopDate.toJulianDay() <= 2300334) { // 31 Dec 1585 AD + testDate.setDate(loopDate.year(), loopDate.month(), loopDate.day()); + QCOMPARE(loopDate.toJulianDay(), testDate.toJulianDay()); loopDate = loopDate.addDays(1); } // Test Gregorian round trip during current useful period loopDate = QDate::fromJulianDay(2378497); // 1 Jan 1900 AD - while ( loopDate.toJulianDay() <= 2488433 ) { // 31 Dec 2100 AD - testDate.setDate( loopDate.year(), loopDate.month(), loopDate.day() ); - QCOMPARE( loopDate.toJulianDay(), testDate.toJulianDay() ); + while (loopDate.toJulianDay() <= 2488433) { // 31 Dec 2100 AD + testDate.setDate(loopDate.year(), loopDate.month(), loopDate.day()); + QCOMPARE(loopDate.toJulianDay(), testDate.toJulianDay()); + loopDate = loopDate.addDays(1); + } + + // Test Gregorian round trip at top end of widget/format range + loopDate = QDate::fromJulianDay(5336961); // 1 Jan 9900 AD + while (loopDate.toJulianDay() <= 5373484) { // 31 Dec 9999 AD + testDate.setDate(loopDate.year(), loopDate.month(), loopDate.day()); + QCOMPARE(loopDate.toJulianDay(), testDate.toJulianDay()); + loopDate = loopDate.addDays(1); + } + + // Test Gregorian round trip at top end of conversion range + loopDate = QDate::fromJulianDay(513024036); // 1 Jan 1399900 AD + while (loopDate.toJulianDay() <= 513060925) { // 31 Dec 1400000 AD + testDate.setDate(loopDate.year(), loopDate.month(), loopDate.day()); + QCOMPARE(loopDate.toJulianDay(), testDate.toJulianDay()); loopDate = loopDate.addDays(1); } } diff --git a/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp b/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp index 2cbac82d3b..2cb3db533e 100644 --- a/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp +++ b/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp @@ -883,22 +883,22 @@ void tst_QDateTime::daysTo() QDateTime dt2(QDate(1760, 2, 2), QTime()); QDateTime dt3(QDate(1760, 3, 2), QTime()); - QCOMPARE(dt1.daysTo(dt2), 31); + QCOMPARE(dt1.daysTo(dt2), (qint64) 31); QCOMPARE(dt1.addDays(31), dt2); - QCOMPARE(dt2.daysTo(dt3), 29); + QCOMPARE(dt2.daysTo(dt3), (qint64) 29); QCOMPARE(dt2.addDays(29), dt3); - QCOMPARE(dt1.daysTo(dt3), 60); + QCOMPARE(dt1.daysTo(dt3), (qint64) 60); QCOMPARE(dt1.addDays(60), dt3); - QCOMPARE(dt2.daysTo(dt1), -31); + QCOMPARE(dt2.daysTo(dt1), (qint64) -31); QCOMPARE(dt2.addDays(-31), dt1); - QCOMPARE(dt3.daysTo(dt2), -29); + QCOMPARE(dt3.daysTo(dt2), (qint64) -29); QCOMPARE(dt3.addDays(-29), dt2); - QCOMPARE(dt3.daysTo(dt1), -60); + QCOMPARE(dt3.daysTo(dt1), (qint64) -60); QCOMPARE(dt3.addDays(-60), dt1); } -- cgit v1.2.3