summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorEdward Welbourne <edward.welbourne@qt.io>2023-01-23 20:12:15 +0100
committerEdward Welbourne <edward.welbourne@qt.io>2023-01-31 17:35:13 +0100
commitbd3d08267940844eeabc104c6e8c5ddaaa546457 (patch)
tree997d1e34397d7371470fc5f04951700480731625 /src
parent19c913b43d0cadc0fee3081be4043bd6f083f8f7 (diff)
Improve readability of calendrical calculations
Use names that give some clue to the meanings of variables and constants. Provide some commentary to help the reader. Extract some functions and constants shared by the Roman-based calendars into a new namespace in qcalendarmath_p.h Purge some unnecessary headers in the process. Change-Id: I6fce18dc29a645f5a6e80ddfea4fd28dd6ecfe73 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Diffstat (limited to 'src')
-rw-r--r--src/corelib/time/qcalendarmath_p.h34
-rw-r--r--src/corelib/time/qgregoriancalendar.cpp44
-rw-r--r--src/corelib/time/qislamiccivilcalendar.cpp30
-rw-r--r--src/corelib/time/qjuliancalendar.cpp29
-rw-r--r--src/corelib/time/qmilankoviccalendar.cpp41
5 files changed, 111 insertions, 67 deletions
diff --git a/src/corelib/time/qcalendarmath_p.h b/src/corelib/time/qcalendarmath_p.h
index 6852e2c344..c785803ce3 100644
--- a/src/corelib/time/qcalendarmath_p.h
+++ b/src/corelib/time/qcalendarmath_p.h
@@ -106,6 +106,40 @@ template <unsigned b, typename Int> constexpr Int qMod(Int a) { return qDivMod<b
} // QRoundingDown
+namespace QRomanCalendrical {
+// Julian Day number of Gregorian 1 BCE, February 29th:
+constexpr qint64 LeapDayGregorian1Bce = 1721119;
+// Aside from (maybe) some turns of centuries, one year in four is leap:
+constexpr unsigned FourYears = 4 * 365 + 1;
+constexpr unsigned FiveMonths = 31 + 30 + 31 + 30 + 31; // Mar-Jul or Aug-Dec.
+
+constexpr auto yearMonthToYearDays(int year, int month)
+{
+ // Pre-digests year and month to (possibly denormal) year count and day-within-year.
+ struct R { qint64 year; qint64 days; };
+ if (year < 0) // Represent -N BCE as 1-N so year numbering is contiguous.
+ ++year;
+ month -= 3; // Adjust month numbering so March = 0, ...
+ if (month < 0) { // and Jan = 10, Feb = 11, in the previous year.
+ --year;
+ month += 12;
+ }
+ return R { year, QRoundingDown::qDiv<5>(FiveMonths * month + 2) };
+}
+
+constexpr auto dayInYearToYmd(int dayInYear)
+{
+ // The year is an adjustment to the year for which dayInYear may be denormal.
+ struct R { int year; int month; int day; };
+ // Shared code for Julian and Milankovic (at least).
+ using namespace QRoundingDown;
+ const auto month5Day = qDivMod<FiveMonths>(5 * dayInYear + 2);
+ // Its remainder changes by 5 per day, except at roughly monthly quotient steps.
+ const auto yearMonth = qDivMod<12>(month5Day.quotient + 2);
+ return R { yearMonth.quotient, yearMonth.remainder + 1, qDiv<5>(month5Day.remainder) + 1 };
+}
+}
+
QT_END_NAMESPACE
#endif // QCALENDARMATH_P_H
diff --git a/src/corelib/time/qgregoriancalendar.cpp b/src/corelib/time/qgregoriancalendar.cpp
index 904b23434c..69a3665eee 100644
--- a/src/corelib/time/qgregoriancalendar.cpp
+++ b/src/corelib/time/qgregoriancalendar.cpp
@@ -3,6 +3,7 @@
#include "qgregoriancalendar_p.h"
#include "qcalendarmath_p.h"
+
#include <QtCore/qdatetime.h>
QT_BEGIN_NAMESPACE
@@ -168,41 +169,44 @@ int QGregorianCalendar::yearSharingWeekDays(QDate date)
* do for Milankovic).
*/
+using namespace QRomanCalendrical;
+// End a Gregorian four-century cycle on 1 BC's leap day:
+constexpr qint64 BaseJd = LeapDayGregorian1Bce;
+// Every four centures there are 97 leap years:
+constexpr unsigned FourCenturies = 400 * 365 + 97;
+
bool QGregorianCalendar::julianFromParts(int year, int month, int day, qint64 *jd)
{
Q_ASSERT(jd);
if (!validParts(year, month, day))
return false;
- if (year < 0)
- ++year;
-
- int a = month < 3 ? 1 : 0;
- qint64 y = qint64(year) - a;
- int m = month + 12 * a - 3;
- *jd = day + qDiv<5>(153 * m + 2) + 1721119
- + 365 * y + qDiv<4>(y) - qDiv<100>(y) + qDiv<400>(y);
+ const auto yearDays = yearMonthToYearDays(year, month);
+ const qint64 y = yearDays.year;
+ const qint64 fromYear = 365 * y + qDiv<4>(y) - qDiv<100>(y) + qDiv<400>(y);
+ *jd = fromYear + yearDays.days + day + BaseJd ;
return true;
}
QCalendar::YearMonthDay QGregorianCalendar::partsFromJulian(qint64 jd)
{
- qint64 a = jd - 1721120;
- qint64 b = qDiv<146097>(4 * a + 3);
- int c = a - qDiv<4>(146097 * b);
+ const qint64 dayNumber = jd - BaseJd;
+ const qint64 century = qDiv<FourCenturies>(4 * dayNumber - 1);
+ const int dayInCentury = dayNumber - qDiv<4>(FourCenturies * century);
- int d = qDiv<1461>(4 * c + 3);
- int e = c - qDiv<4>(1461 * d);
- int m = qDiv<153>(5 * e + 2);
+ const int yearInCentury = qDiv<FourYears>(4 * dayInCentury - 1);
+ const int dayInYear = dayInCentury - qDiv<4>(FourYears * yearInCentury);
+ const int m = qDiv<FiveMonths>(5 * dayInYear - 3);
+ Q_ASSERT(m < 12 && m >= 0);
+ // That m is a month adjusted to March = 0, with Jan = 10, Feb = 11 in the previous year.
+ const int yearOffset = m < 10 ? 0 : 1;
- int y = 100 * b + d + qDiv<10>(m);
+ const int y = 100 * century + yearInCentury + yearOffset;
+ const int month = m + 3 - 12 * yearOffset;
+ const int day = dayInYear - qDiv<5>(FiveMonths * m + 2);
// Adjust for no year 0
- int year = y > 0 ? y : y - 1;
- int month = m + 3 - 12 * qDiv<10>(m);
- int day = e - qDiv<5>(153 * m + 2) + 1;
-
- return QCalendar::YearMonthDay(year, month, day);
+ return QCalendar::YearMonthDay(y > 0 ? y : y - 1, month, day);
}
QT_END_NAMESPACE
diff --git a/src/corelib/time/qislamiccivilcalendar.cpp b/src/corelib/time/qislamiccivilcalendar.cpp
index 48bb0064c8..ac1f97cb6c 100644
--- a/src/corelib/time/qislamiccivilcalendar.cpp
+++ b/src/corelib/time/qislamiccivilcalendar.cpp
@@ -4,7 +4,6 @@
#include "qglobal.h"
#include "qislamiccivilcalendar_p.h"
#include "qcalendarmath_p.h"
-#include <QtCore/qmath.h>
QT_BEGIN_NAMESPACE
@@ -61,27 +60,34 @@ bool QIslamicCivilCalendar::isLeapYear(int year) const
return qMod<30>(year * 11 + 14) < 11;
}
+// First day of first year (Gregorian 622 CE July 19th) is the base date here:
+constexpr qint64 EpochJd = 1948440;
+// Each 30 years has 11 leap years of 355 days and 19 ordinary years of 354:
+constexpr unsigned ThirtyYears = 11 * 355 + 19 * 354;
+// The first eleven months of the year alternate 30, 29, ..., 29, 30 days in length.
+constexpr unsigned ElevenMonths = 6 * 30 + 5 * 29;
+
bool QIslamicCivilCalendar::dateToJulianDay(int year, int month, int day, qint64 *jd) const
{
Q_ASSERT(jd);
if (!isDateValid(year, month, day))
return false;
- if (year <= 0)
- ++year;
- *jd = qDiv<30>(10631 * year - 10617)
- + qDiv<11>(325 * month - 320)
- + day + 1948439;
+
+ *jd = qDiv<30>(qint64(ThirtyYears) * (year > 0 ? year - 1 : year) + 14)
+ + qDiv<11>(ElevenMonths * (month - 1) + 5)
+ + day + EpochJd - 1;
return true;
}
QCalendar::YearMonthDay QIslamicCivilCalendar::julianDayToDate(qint64 jd) const
{
- constexpr qint64 epoch = 1948440;
- const auto k2dm = qDivMod<10631>(30 * (jd - epoch) + 15);
- int y = k2dm.quotient + 1;
- const auto k1dm = qDivMod<325>(11 * qDiv<30>(k2dm.remainder) + 5);
- const int month = k1dm.quotient + 1;
- const int day = qDiv<11>(k1dm.remainder) + 1;
+ const auto year30Day = qDivMod<ThirtyYears>(30 * (jd - EpochJd) + 15);
+ // Its remainder changes by 30 per day, except roughly yearly.
+ const auto month11Day = qDivMod<ElevenMonths>(11 * qDiv<30>(year30Day.remainder) + 5);
+ // Its remainder changes by 11 per day except roughly monthly.
+ const int month = month11Day.quotient + 1;
+ const int day = qDiv<11>(month11Day.remainder) + 1;
+ const int y = year30Day.quotient + 1;
return QCalendar::YearMonthDay(y > 0 ? y : y - 1, month, day);
}
diff --git a/src/corelib/time/qjuliancalendar.cpp b/src/corelib/time/qjuliancalendar.cpp
index 1c63d4e170..5572dbe19b 100644
--- a/src/corelib/time/qjuliancalendar.cpp
+++ b/src/corelib/time/qjuliancalendar.cpp
@@ -5,8 +5,7 @@
#include "qjuliancalendar_p.h"
#include "qromancalendar_data_p.h"
#include "qcalendarmath_p.h"
-#include <QtCore/qmath.h>
-#include <QtCore/qlocale.h>
+
#include <QtCore/qdatetime.h>
QT_BEGIN_NAMESPACE
@@ -57,31 +56,29 @@ bool QJulianCalendar::isLeapYear(int year) const
return qMod<4>(year < 0 ? year + 1 : year) == 0;
}
-// Julian Day 0 was January the first in the proleptic Julian calendar's 4713 BC
+// Julian Day 0 was January the first in the proleptic Julian calendar's 4713 BC.
+using namespace QRomanCalendrical;
+// End a Julian four-year cycle on 1 BC's leap day (Gregorian Feb 27th):
+constexpr qint64 BaseJd = LeapDayGregorian1Bce - 2;
bool QJulianCalendar::dateToJulianDay(int year, int month, int day, qint64 *jd) const
{
Q_ASSERT(jd);
if (!isDateValid(year, month, day))
return false;
- if (year < 0)
- ++year;
- const qint64 c0 = month < 3 ? -1 : 0;
- const qint64 j1 = qDiv<4>(1461 * (year + c0));
- const qint64 j2 = qDiv<5>(153 * month - 1836 * c0 - 457);
- *jd = j1 + j2 + day + 1721117;
+
+ const auto yearDays = yearMonthToYearDays(year, month);
+ *jd = qDiv<4>(FourYears * yearDays.year) + yearDays.days + day + BaseJd;
return true;
}
QCalendar::YearMonthDay QJulianCalendar::julianDayToDate(qint64 jd) const
{
- const auto k2dm = qDivMod<1461>(4 * (jd - 1721118) + 3);
- const auto k1dm = qDivMod<153>(5 * qDiv<4>(k2dm.remainder) + 2);
- const auto c0dm = qDivMod<12>(k1dm.quotient + 2);
- const int y = qint16(k2dm.quotient + c0dm.quotient);
- const int month = quint8(c0dm.remainder + 1);
- const int day = qDiv<5>(k1dm.remainder) + 1;
- return QCalendar::YearMonthDay(y > 0 ? y : y - 1, month, day);
+ const auto year4Day = qDivMod<FourYears>(4 * (jd - BaseJd) - 1);
+ // Its remainder changes by 4 per day, except at roughly yearly quotient steps.
+ const auto ymd = dayInYearToYmd(qDiv<4>(year4Day.remainder));
+ const int y = year4Day.quotient + ymd.year;
+ return QCalendar::YearMonthDay(y > 0 ? y : y - 1, ymd.month, ymd.day);
}
QT_END_NAMESPACE
diff --git a/src/corelib/time/qmilankoviccalendar.cpp b/src/corelib/time/qmilankoviccalendar.cpp
index 3d848fb372..b41bf120dd 100644
--- a/src/corelib/time/qmilankoviccalendar.cpp
+++ b/src/corelib/time/qmilankoviccalendar.cpp
@@ -4,8 +4,7 @@
#include "qglobal.h"
#include "qmilankoviccalendar_p.h"
#include "qcalendarmath_p.h"
-#include <QtCore/qmath.h>
-#include <QtCore/qlocale.h>
+
#include <QtCore/qdatetime.h>
QT_BEGIN_NAMESPACE
@@ -64,33 +63,37 @@ bool QMilankovicCalendar::isLeapYear(int year) const
return true;
}
+using namespace QRomanCalendrical;
+// End a Milankovic nine-century cycle on 1 BC, Feb 28 (Gregorian Feb 29):
+constexpr qint64 BaseJd = LeapDayGregorian1Bce;
+// Leap years every 4 years, except for 7 turn-of-century years per nine centuries:
+constexpr unsigned NineCenturies = 365 * 900 + 900 / 4 - 7;
+// When the turn-of-century is a leap year, the century has 25 leap years in it:
+constexpr unsigned LeapCentury = 365 * 100 + 25;
+
bool QMilankovicCalendar::dateToJulianDay(int year, int month, int day, qint64 *jd) const
{
Q_ASSERT(jd);
if (!isDateValid(year, month, day))
return false;
- if (year <= 0)
- ++year;
- const qint16 c0 = month < 3 ? -1 : 0;
- const qint16 x1 = month - 12 * c0 - 3;
- const auto x4dm = qDivMod<100>(year + c0);
- *jd = qDiv<9>(328718 * x4dm.quotient + 6)
- + qDiv<100>(36525 * x4dm.remainder)
- + qDiv<5>(153 * x1 + 2)
- + day + 1721119;
+
+ const auto yearDays = yearMonthToYearDays(year, month);
+ const auto centuryYear = qDivMod<100>(yearDays.year);
+ const qint64 fromYear = qDiv<9>(NineCenturies * centuryYear.quotient + 6)
+ + qDiv<100>(LeapCentury * centuryYear.remainder);
+ *jd = fromYear + yearDays.days + day + BaseJd;
return true;
}
QCalendar::YearMonthDay QMilankovicCalendar::julianDayToDate(qint64 jd) const
{
- const auto k3dm = qDivMod<328718>(9 * (jd - 1721120) + 2);
- const auto k2dm = qDivMod<36525>(100 * qDiv<9>(k3dm.remainder) + 99);
- const auto k1dm = qDivMod<153>(qDiv<100>(k2dm.remainder) * 5 + 2);
- const auto c0dm = qDivMod<12>(k1dm.quotient + 2);
- const int y = 100 * k3dm.quotient + k2dm.quotient + c0dm.quotient;
- const int month = c0dm.remainder + 1;
- const int day = qDiv<5>(k1dm.remainder) + 1;
- return QCalendar::YearMonthDay(y > 0 ? y : y - 1, month, day);
+ const auto century9Day = qDivMod<NineCenturies>(9 * (jd - BaseJd) - 7);
+ // Its remainder changes by 9 per day, except roughly once per century.
+ const auto year100Day = qDivMod<LeapCentury>(100 * qDiv<9>(century9Day.remainder) + 99);
+ // Its remainder changes by 100 per day, except roughly once per year.
+ const auto ymd = dayInYearToYmd(qDiv<100>(year100Day.remainder));
+ const int y = 100 * century9Day.quotient + year100Day.quotient + ymd.year;
+ return QCalendar::YearMonthDay(y > 0 ? y : y - 1, ymd.month, ymd.day);
}
QT_END_NAMESPACE