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;
}
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
*/
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)
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)
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
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;
{
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);
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);
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);
// 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());
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);