QDateTime: Remove Julian Calendar

Convert QDate to only use Gregorian calendar and not Julian calendar
before 1582.  In future the Julian can be used via proper calendar
classes.

Change-Id: I547a3550332057a0ab1be616706630b6afaceffc
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
John Layt 2012-01-09 20:50:00 +00:00 committed by Qt by Nokia
parent 8327fa7c11
commit fc24979e43
4 changed files with 62 additions and 180 deletions

4
dist/changes-5.0.0 vendored
View File

@ -241,6 +241,10 @@ QtCore
in Qt 4 they returned a null QString or a null QStringRef.
* QDate, QTime, and QDateTime have undergone important behavioural changes:
* QDate only implements the Gregorian calendar, the switch to the Julian
calendar before 1582 has been removed. This means all QDate methods will
return different results for dates prior to 15 October 1582, and there is
no longer a gap between 4 October 1582 and 15 October 1582.
* QDate::setYMD() is deprecated, use QDate::setDate() instead
* Most methods now apply strict validity checks and will return appropriate
and consistent values when invalid. For example, QDate::year() will return

View File

@ -82,7 +82,7 @@ enum {
MSECS_PER_HOUR = 3600000,
SECS_PER_MIN = 60,
MSECS_PER_MIN = 60000,
JULIAN_DAY_FOR_EPOCH = 2440588 // result of julianDayFromGregorianDate(1970, 1, 1)
JULIAN_DAY_FOR_EPOCH = 2440588 // result of julianDayFromDate(1970, 1, 1)
};
static inline QDate fixedDate(int y, int m, int d)
@ -92,66 +92,41 @@ static inline QDate fixedDate(int y, int m, int d)
return result;
}
static inline qint64 julianDayFromGregorianDate(qint64 year, int month, int day)
static inline qint64 julianDayFromDate(qint64 year, int month, int day)
{
// Gregorian calendar starting from October 15, 1582
// Gregorian calendar
// Algorithm from Henry F. Fliegel and Thomas C. Van Flandern
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;
}
static qint64 julianDayFromDate(qint64 year, int month, int day)
{
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 std::numeric_limits<qint64>::min(); // i.e. nullJd()
}
}
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
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;
} else {
// Julian calendar until October 4, 1582
// Algorithm from Frequently Asked Questions about Calendars by Claus Toendering
julianDay += 32082;
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);
if (y <= 0)
--y;
}
// 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;
if (y<= 0)
--y;
if (year)
*year = y;
if (month)
@ -197,12 +172,10 @@ static QString fmtDateTime(const QString& f, const QTime* dt = 0, const QDate* d
A QDate object contains a calendar date, i.e. year, month, and day
numbers, in the Gregorian calendar. (see \l{QDate G and J} {Use of
Gregorian and Julian Calendars} for dates prior to 15 October
1582). It can read the current date from the system clock. It
provides functions for comparing dates, and for manipulating
dates. For example, it is possible to add and subtract days,
months, and years to dates.
numbers, in the Gregorian calendar. It can read the current date
from the system clock. It provides functions for comparing dates,
and for manipulating dates. For example, it is possible to add
and subtract days, months, and years to dates.
A QDate object is typically created either by giving the year,
month, and day numbers explicitly. Note that QDate interprets two
@ -233,26 +206,6 @@ static QString fmtDateTime(const QString& f, const QTime* dt = 0, const QDate* d
\section1
\target QDate G and J
\section2 Use of Gregorian and Julian Calendars
QDate uses the Gregorian calendar in all locales, beginning
on the date 15 October 1582. For dates up to and including 4
October 1582, the Julian calendar is used. This means there is a
10-day gap in the internal calendar between the 4th and the 15th
of October 1582. When you use QDateTime for dates in that epoch,
the day after 4 October 1582 is 15 October 1582, and the dates in
the gap are invalid.
The Julian to Gregorian changeover date used here is the date when
the Gregorian calendar was first introduced, by Pope Gregory
XIII. That change was not universally accepted and some localities
only executed it at a later date (if at all). QDateTime
doesn't take any of these historical facts into account. If an
application must support a locale-specific dating system, it must
do so on its own, remembering to convert the dates using the
Julian day.
\section2 No Year 0
There is no year 0. Dates in that year are considered invalid. The
@ -985,11 +938,6 @@ QDate QDate::addDays(qint64 ndays) const
resulting month/year, this function will return a date that is the
latest valid date.
\warning QDate has a date hole around the days introducing the
Gregorian calendar (the days 5 to 14 October 1582, inclusive, do
not exist). If the calculation ends in one of those days, QDate
will return either October 4 or October 15.
\sa addDays() addYears()
*/
@ -1039,10 +987,6 @@ QDate QDate::addMonths(int nmonths) const
// yes, adjust the date by +1 or -1 years
y += increasing ? +1 : -1;
// did we end up in the Gregorian/Julian conversion hole?
if (y == 1582 && m == 10 && d > 4 && d < 15)
d = increasing ? 15 : 4;
return fixedDate(y, m, d);
}
@ -1332,14 +1276,10 @@ QDate QDate::fromString(const QString &string, const QString &format)
bool QDate::isValid(int year, int month, int day)
{
// there is no year 0 in the Julian calendar
// there is no year 0 in the Gregorian calendar
if (year == 0)
return false;
// passage from Julian to Gregorian calendar
if (year == 1582 && month == 10 && day > 4 && day < 15)
return false;
return (day > 0 && month > 0 && month <= 12) &&
(day <= monthDays[month] || (day == 29 && month == 2 && isLeapYear(year)));
}
@ -1353,14 +1293,11 @@ bool QDate::isValid(int year, int month, int day)
bool QDate::isLeapYear(int y)
{
if (y < 1582) {
if ( y < 1) { // No year 0 in Julian calendar, so -1, -5, -9 etc are leap years
++y;
}
return y % 4 == 0;
} else {
return (y % 4 == 0 && y % 100 != 0) || y % 400 == 0;
}
// No year 0 in Gregorian calendar, so -1, -5, -9 etc are leap years
if ( y < 1)
++y;
return (y % 4 == 0 && y % 100 != 0) || y % 400 == 0;
}
/*! \fn static QDate QDate::fromJulianDay(int jd)
@ -2088,26 +2025,6 @@ int QTime::elapsed() const
\section1
\target QDateTime G and J
\section2 Use of Gregorian and Julian Calendars
QDate uses the Gregorian calendar in all locales, beginning
on the date 15 October 1582. For dates up to and including 4
October 1582, the Julian calendar is used. This means there is a
10-day gap in the internal calendar between the 4th and the 15th
of October 1582. When you use QDateTime for dates in that epoch,
the day after 4 October 1582 is 15 October 1582, and the dates in
the gap are invalid.
The Julian to Gregorian changeover date used here is the date when
the Gregorian calendar was first introduced, by Pope Gregory
XIII. That change was not universally accepted and some localities
only executed it at a later date (if at all). QDateTime
doesn't take any of these historical facts into account. If an
application must support a locale-specific dating system, it must
do so on its own, remembering to convert the dates using the
Julian day.
\section2 No Year 0
There is no year 0. Dates in that year are considered invalid. The
@ -2134,16 +2051,6 @@ int QTime::elapsed() const
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
date, so it uses the Gregorian calendar for all locales, beginning
on the date 15 October 1582. For dates up to and including 4
October 1582, QDateTime uses the Julian calendar. This means
there is a 10-day gap in the QDateTime calendar between the 4th
and the 15th of October 1582. When you use QDateTime for dates in
that epoch, the day after 4 October 1582 is 15 October 1582, and
the dates in the gap are invalid.
\section2
Use of System Timezone
@ -3052,8 +2959,8 @@ qint64 QDateTime::currentMSecsSinceEpoch()
GetSystemTime(&st);
return msecsFromDecomposed(st.wHour, st.wMinute, st.wSecond, st.wMilliseconds) +
qint64(julianDayFromGregorianDate(st.wYear, st.wMonth, st.wDay)
- julianDayFromGregorianDate(1970, 1, 1)) * Q_INT64_C(86400000);
qint64(julianDayFromDate(st.wYear, st.wMonth, st.wDay)
- julianDayFromDate(1970, 1, 1)) * Q_INT64_C(86400000);
}
#elif defined(Q_OS_UNIX)

View File

@ -185,10 +185,10 @@ void tst_QDate::isValid_data()
QTest::newRow("idec") << 2000 << 12 << 32 << nullJd << false;
// the beginning of the Julian Day calendar:
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 earliest formula") << -4800 << 1 << 1 << qint64( -31738) << true;
QTest::newRow("jd -1") << -4714 << 11 << 23 << qint64( -1) << true;
QTest::newRow("jd 0") << -4714 << 11 << 24 << qint64( 0) << true;
QTest::newRow("jd 1") << -4714 << 11 << 25 << qint64( 1) << true;
QTest::newRow("jd latest formula") << 1400000 << 12 << 31 << qint64(513060925) << true;
}
@ -259,19 +259,14 @@ void tst_QDate::dayOfWeek_data()
QTest::newRow("data5") << 2000 << 1 << 7 << 5;
QTest::newRow("data6") << 2000 << 1 << 8 << 6;
QTest::newRow("data7") << 2000 << 1 << 9 << 7;
QTest::newRow("data8") << 1815 << 6 << 15 << 4;
QTest::newRow("data9") << 1815 << 6 << 15 << 4;
QTest::newRow("data10") << 1500 << 1 << 1 << 3;
QTest::newRow("data11") << -1500 << 1 << 1 << 7;
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;
QTest::newRow("data8") << -4800 << 1 << 1 << 1;
QTest::newRow("data9") << -4800 << 1 << 2 << 2;
QTest::newRow("data10") << -4800 << 1 << 3 << 3;
QTest::newRow("data12") << -4800 << 1 << 4 << 4;
QTest::newRow("data12") << -4800 << 1 << 5 << 5;
QTest::newRow("data13") << -4800 << 1 << 6 << 6;
QTest::newRow("data14") << -4800 << 1 << 7 << 7;
QTest::newRow("data15") << -4800 << 1 << 8 << 1;
}
void tst_QDate::dayOfWeek()
@ -300,9 +295,9 @@ void tst_QDate::dayOfYear_data()
QTest::newRow("data5") << 2001 << 12 << 31 << 365;
QTest::newRow("data6") << 1815 << 1 << 1 << 1;
QTest::newRow("data7") << 1815 << 12 << 31 << 365;
QTest::newRow("data8") << 1582 << 1 << 1 << 1;
QTest::newRow("data9") << 1582 << 12 << 31 << 355;
QTest::newRow("data10") << 1500 << 1 << 1 << 1;
QTest::newRow("data8") << 1500 << 1 << 1 << 1;
QTest::newRow("data9") << 1500 << 12 << 31 << 365;
QTest::newRow("data10") << -1500 << 1 << 1 << 1;
QTest::newRow("data11") << -1500 << 12 << 31 << 365;
QTest::newRow("data12") << -4800 << 1 << 1 << 1;
QTest::newRow("data13") << -4800 << 12 << 31 << 365;
@ -589,14 +584,6 @@ void tst_QDate::addMonths_data()
QTest::newRow( "data15" ) << 1 << 1 << 1 << -12 << -1 << 1 << 1;
QTest::newRow( "data16" ) << -1 << 12 << 1 << 1 << 1 << 1 << 1;
QTest::newRow( "data17" ) << -1 << 1 << 1 << 12 << 1 << 1 << 1;
// Gregorian/Julian switchover
QTest::newRow( "data18" ) << 1582 << 9 << 4 << 1 << 1582 << 10 << 4;
QTest::newRow( "data19" ) << 1582 << 9 << 10 << 1 << 1582 << 10 << 15;
QTest::newRow( "data20" ) << 1582 << 9 << 20 << 1 << 1582 << 10 << 20;
QTest::newRow( "data21" ) << 1582 << 11 << 4 << -1 << 1582 << 10 << 4;
QTest::newRow( "data22" ) << 1582 << 11 << 10 << -1 << 1582 << 10 << 4;
QTest::newRow( "data23" ) << 1582 << 11 << 20 << -1 << 1582 << 10 << 20;
}
void tst_QDate::addYears()
@ -930,10 +917,10 @@ void tst_QDate::isLeapYear()
QVERIFY(QDate::isLeapYear(4));
QVERIFY(!QDate::isLeapYear(7));
QVERIFY(QDate::isLeapYear(8));
QVERIFY(QDate::isLeapYear(100));
QVERIFY(!QDate::isLeapYear(100));
QVERIFY(QDate::isLeapYear(400));
QVERIFY(QDate::isLeapYear(700));
QVERIFY(QDate::isLeapYear(1500));
QVERIFY(!QDate::isLeapYear(700));
QVERIFY(!QDate::isLeapYear(1500));
QVERIFY(QDate::isLeapYear(1600));
QVERIFY(!QDate::isLeapYear(1700));
QVERIFY(!QDate::isLeapYear(1800));
@ -1184,24 +1171,16 @@ void tst_QDate::roundtrip() const
// Test Julian round trip around JD 0 and current low end of valid range
QDate testDate;
QDate loopDate = QDate::fromJulianDay(-31776); // 1 Jan 4800 BC
while (loopDate.toJulianDay() <= 5113) { // 31 Dec 4700 AD
QDate loopDate = QDate::fromJulianDay(-31738); // 1 Jan 4800 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);
}
// 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
loopDate = QDate::fromJulianDay(1684901); // 1 Jan 100 BC
while (loopDate.toJulianDay() <= 1757949) { // 31 Dec 100 AD
testDate.setDate(loopDate.year(), loopDate.month(), loopDate.day());
QCOMPARE(loopDate.toJulianDay(), testDate.toJulianDay());
loopDate = loopDate.addDays(1);

View File

@ -726,14 +726,6 @@ void tst_QDateTime::addSecs_data()
QTest::newRow("toPositive") << QDateTime(QDate(-1, 12, 31), QTime(23, 59, 59), Qt::UTC)
<< 1
<< QDateTime(QDate(1, 1, 1), QTime(0, 0, 0), Qt::UTC);
// Gregorian/Julian switchover
QTest::newRow("toGregorian") << QDateTime(QDate(1582, 10, 4), QTime(23, 59, 59))
<< 1
<< QDateTime(QDate(1582, 10, 15), QTime(0, 0, 0));
QTest::newRow("toJulian") << QDateTime(QDate(1582, 10, 15), QTime(0, 0, 0))
<< -1
<< QDateTime(QDate(1582, 10, 4), QTime(23, 59, 59));
}
void tst_QDateTime::addSecs()