summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/corelib/tools/qdatetime.cpp106
-rw-r--r--src/corelib/tools/qdatetime.h4
-rw-r--r--tests/auto/corelib/tools/qdate/tst_qdate.cpp34
3 files changed, 79 insertions, 65 deletions
diff --git a/src/corelib/tools/qdatetime.cpp b/src/corelib/tools/qdatetime.cpp
index e463ba9c4e..56dc0fade9 100644
--- a/src/corelib/tools/qdatetime.cpp
+++ b/src/corelib/tools/qdatetime.cpp
@@ -93,47 +93,67 @@ static inline QDate fixedDate(int y, int m, int d)
return result;
}
-static inline qint64 julianDayFromDate(qint64 year, int month, int day)
+static inline qint64 floordiv(qint64 a, qint64 b)
{
- // Gregorian calendar
- // Algorithm from Henry F. Fliegel and Thomas C. Van Flandern
+ return (a - (a < 0 ? b-1 : 0)) / b;
+}
+
+static inline qint64 floordiv(qint64 a, int b)
+{
+ return (a - (a < 0 ? b-1 : 0)) / b;
+}
+static inline int floordiv(int a, int b)
+{
+ return (a - (a < 0 ? b-1 : 0)) / b;
+}
+
+static inline qint64 julianDayFromDate(int year, int month, int day)
+{
+ // Adjust for no year 0
if (year < 0)
++year;
- return (1461 * (year + 4800 + (month - 14) / 12)) / 4
- + (367 * (month - 2 - 12 * ((month - 14) / 12))) / 12
- - (3 * ((year + 4900 + (month - 14) / 12) / 100)) / 4
- + day - 32075;
+/*
+ * Math from The Calendar FAQ at http://www.tondering.dk/claus/cal/julperiod.php
+ * This formula is correct for all julian days, when using mathematical integer
+ * division (round to negative infinity), not c++11 integer division (round to zero)
+ */
+ int a = floordiv(14 - month, 12);
+ qint64 y = (qint64)year + 4800 - a;
+ int m = month + 12 * a - 3;
+ return day + floordiv(153 * m + 2, 5) + 365 * y + floordiv(y, 4) - floordiv(y, 100) + floordiv(y, 400) - 32045;
}
-static void getDateFromJulianDay(qint64 julianDay, int *year, int *month, int *day)
+static void getDateFromJulianDay(qint64 julianDay, int *yearp, int *monthp, int *dayp)
{
- int y, m, d;
+/*
+ * Math from The Calendar FAQ at http://www.tondering.dk/claus/cal/julperiod.php
+ * This formula is correct for all julian days, when using mathematical integer
+ * division (round to negative infinity), not c++11 integer division (round to zero)
+ */
+ qint64 a = julianDay + 32044;
+ qint64 b = floordiv(4 * a + 3, 146097);
+ int c = a - floordiv(146097 * b, 4);
+
+ int d = floordiv(4 * c + 3, 1461);
+ int e = c - floordiv(1461 * d, 4);
+ int m = floordiv(5 * e + 2, 153);
- // Gregorian calendar
- // This algorithm is from Henry F. Fliegel and Thomas C. Van Flandern
- 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;
- ell = ell - (1461 * i) / 4 + 31;
- j = (80 * ell) / 2447;
- d = ell - (2447 * j) / 80;
- ell = j / 11;
- m = j + 2 - (12 * ell);
- y = 100 * (n - 49) + i + ell;
+ int day = e - floordiv(153 * m + 2, 5) + 1;
+ int month = m + 3 - 12 * floordiv(m, 10);
+ int year = 100 * b + d - 4800 + floordiv(m, 10);
- if (y<= 0)
- --y;
+ // Adjust for no year 0
+ if (year <= 0)
+ --year ;
- if (year)
- *year = y;
- if (month)
- *month = m;
- if (day)
- *day = d;
+ if (yearp)
+ *yearp = year;
+ if (monthp)
+ *monthp = month;
+ if (dayp)
+ *dayp = day;
}
@@ -225,14 +245,8 @@ static QString fmtDateTime(const QString& f, const QTime* dt = 0, const QDate* d
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<qint64>::min() / 2 to
- std::numeric_limits<qint64>::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.
+ for technical reasons limited to between -784350574879 and 784354017364,
+ which means from before 2 billion BCE to after 2 billion CE.
\sa QTime, QDateTime, QDateEdit, QDateTimeEdit, QCalendarWidget
*/
@@ -859,9 +873,6 @@ QString QDate::toString(const QString& format) const
If the specified date is invalid, the QDate object is set to be
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)
@@ -882,9 +893,6 @@ 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)
@@ -2100,14 +2108,8 @@ int QTime::elapsed() const
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<qint64>::min() / 2 to
- std::numeric_limits<qint64>::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.
+ for technical reasons limited to between -784350574879 and 784354017364,
+ which means from before 2 billion BCE to after 2 billion CE.
\section2
Use of System Timezone
diff --git a/src/corelib/tools/qdatetime.h b/src/corelib/tools/qdatetime.h
index ed7d8adb5f..22db5a4830 100644
--- a/src/corelib/tools/qdatetime.h
+++ b/src/corelib/tools/qdatetime.h
@@ -121,8 +121,8 @@ QT_DEPRECATED inline bool setYMD(int y, int m, int d)
private:
static inline qint64 nullJd() { return std::numeric_limits<qint64>::min(); }
- static inline qint64 minJd() { return std::numeric_limits<qint64>::min() / 2; }
- static inline qint64 maxJd() { return (std::numeric_limits<qint64>::max()) / 2; }
+ static inline qint64 minJd() { return Q_INT64_C(-784350574879); }
+ static inline qint64 maxJd() { return Q_INT64_C( 784354017364); }
qint64 jd;
diff --git a/tests/auto/corelib/tools/qdate/tst_qdate.cpp b/tests/auto/corelib/tools/qdate/tst_qdate.cpp
index 01805e1271..9978a252bf 100644
--- a/tests/auto/corelib/tools/qdate/tst_qdate.cpp
+++ b/tests/auto/corelib/tools/qdate/tst_qdate.cpp
@@ -117,8 +117,8 @@ void tst_QDate::isNull_data()
QTest::addColumn<qint64>("jd");
QTest::addColumn<bool>("null");
- qint64 minJd = std::numeric_limits<qint64>::min() / 2;
- qint64 maxJd = std::numeric_limits<qint64>::max() / 2;
+ qint64 minJd = Q_INT64_C(-784350574879);
+ qint64 maxJd = Q_INT64_C( 784354017364);
QTest::newRow("qint64 min") << std::numeric_limits<qint64>::min() << true;
QTest::newRow("minJd - 1") << minJd - 1 << true;
@@ -448,8 +448,8 @@ void tst_QDate::julianDaysLimits()
{
qint64 min = std::numeric_limits<qint64>::min();
qint64 max = std::numeric_limits<qint64>::max();
- qint64 minJd = std::numeric_limits<qint64>::min() / 2;
- qint64 maxJd = std::numeric_limits<qint64>::max() / 2;
+ qint64 minJd = Q_INT64_C(-784350574879);
+ qint64 maxJd = Q_INT64_C( 784354017364);
QDate maxDate = QDate::fromJulianDay(maxJd);
QDate minDate = QDate::fromJulianDay(minJd);
@@ -492,7 +492,7 @@ void tst_QDate::julianDaysLimits()
dt = minDate.addDays(min);
QCOMPARE(dt.isValid(), false);
dt = minDate.addDays(max);
- QCOMPARE(dt.isValid(), true);
+ QCOMPARE(dt.isValid(), false);
dt = zeroDate.addDays(-1);
QCOMPARE(dt.isValid(), true);
@@ -664,8 +664,8 @@ void tst_QDate::addYears_data()
void tst_QDate::daysTo()
{
- qint64 minJd = std::numeric_limits<qint64>::min() / 2;
- qint64 maxJd = std::numeric_limits<qint64>::max() / 2;
+ qint64 minJd = Q_INT64_C(-784350574879);
+ qint64 maxJd = Q_INT64_C( 784354017364);
QDate dt1(2000, 1, 1);
QDate dt2(2000, 1, 5);
@@ -1356,9 +1356,10 @@ 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 around JD 0 and current low end of valid range
+ // Test Julian round trip around JD 0 and the c++ integer division rounding
+ // problem point (eg. negative numbers) in the conversion functions.
QDate testDate;
- QDate loopDate = QDate::fromJulianDay(-31738); // 1 Jan 4800 BC
+ QDate loopDate = QDate::fromJulianDay(-50001); // 1 Jan 4850 BC
while (loopDate.toJulianDay() <= 5150) { // 31 Dec 4700 BC
testDate.setDate(loopDate.year(), loopDate.month(), loopDate.day());
QCOMPARE(loopDate.toJulianDay(), testDate.toJulianDay());
@@ -1389,9 +1390,20 @@ void tst_QDate::roundtrip() const
loopDate = loopDate.addDays(1);
}
+ qint64 minJd = Q_INT64_C(-784350574879);
+ qint64 maxJd = Q_INT64_C( 784354017364);
+
// 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
+ loopDate = QDate::fromJulianDay(maxJd);
+ while (loopDate.toJulianDay() >= maxJd - 146397) {
+ testDate.setDate(loopDate.year(), loopDate.month(), loopDate.day());
+ QCOMPARE(loopDate.toJulianDay(), testDate.toJulianDay());
+ loopDate = loopDate.addDays(-1);
+ }
+
+ // Test Gregorian round trip at low end of conversion range
+ loopDate = QDate::fromJulianDay(minJd);
+ while (loopDate.toJulianDay() <= minJd + 146397) {
testDate.setDate(loopDate.year(), loopDate.month(), loopDate.day());
QCOMPARE(loopDate.toJulianDay(), testDate.toJulianDay());
loopDate = loopDate.addDays(1);