File indexing completed on 2024-04-21 14:56:42

0001 /*
0002     This file is part of the kholidays library.
0003 
0004     SPDX-FileCopyrightText: 2014 John Layt <john@layt.net>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "qcalendarsystem_p.h"
0010 
0011 #include <kholidays_debug.h>
0012 
0013 #include <QDate>
0014 #include <QSharedData>
0015 
0016 class QCalendarSystemPrivate : public QSharedData
0017 {
0018 public:
0019     explicit QCalendarSystemPrivate(QCalendarSystem::CalendarSystem calendar);
0020 
0021     QCalendarSystem::CalendarSystem calendarSystem() const;
0022     qint64 epoch() const;
0023     qint64 earliestValidDate() const;
0024     int earliestValidYear() const;
0025     qint64 latestValidDate() const;
0026     int latestValidYear() const;
0027     int yearOffset() const;
0028     int maxMonthsInYear() const;
0029     int monthsInYear(int year) const;
0030     int maxDaysInYear() const;
0031     int daysInYear(int year) const;
0032     int maxDaysInMonth() const;
0033     int daysInMonth(int year, int month) const;
0034     bool hasYearZero() const;
0035     bool hasLeapMonths() const;
0036 
0037     int quarter(int month) const;
0038     bool isLeapYear(int year) const;
0039     void julianDayToDate(qint64 jd, int *year, int *month, int *day) const;
0040     qint64 julianDayFromDate(int year, int month, int day) const;
0041 
0042     bool isValidYear(int year) const;
0043     bool isValidMonth(int year, int month) const;
0044     int addYears(int y1, int years) const;
0045     int diffYears(int y1, int y2) const;
0046 
0047     QCalendarSystem::CalendarSystem m_calendarSystem;
0048 };
0049 
0050 static const char julianMonths[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
0051 
0052 QCalendarSystemPrivate::QCalendarSystemPrivate(QCalendarSystem::CalendarSystem calendar)
0053     : QSharedData()
0054     , m_calendarSystem(calendar)
0055 {
0056 }
0057 
0058 QCalendarSystem::CalendarSystem QCalendarSystemPrivate::calendarSystem() const
0059 {
0060     if (m_calendarSystem == QCalendarSystem::DefaultCalendar) {
0061         return QCalendarSystem::GregorianCalendar;
0062     } else {
0063         return m_calendarSystem;
0064     }
0065 }
0066 
0067 qint64 QCalendarSystemPrivate::epoch() const
0068 {
0069     switch (calendarSystem()) {
0070     case QCalendarSystem::GregorianCalendar:
0071         return 1721426; //  0001-01-01 Gregorian
0072     case QCalendarSystem::CopticCalendar:
0073         return 1825030; //  0001-01-01 ==  0284-08-29 Gregorian
0074     case QCalendarSystem::EthiopicCalendar:
0075         return 1724221; //  0001-01-01 ==  0008-08-29 Gregorian
0076     case QCalendarSystem::EthiopicAmeteAlemCalendar:
0077         return -284655; //  0001-01-01 == -5492-08-29 Gregorian
0078     case QCalendarSystem::IndianNationalCalendar:
0079         return 1749994; //  0000-01-01 == 0078-03-22 Gregorian
0080     case QCalendarSystem::IslamicCivilCalendar:
0081         return 1948440; //  0001-01-01 == 0622-07-19 Gregorian
0082     case QCalendarSystem::ISO8601Calendar:
0083         return 1721060; //  0000-01-01 Gregorian
0084     case QCalendarSystem::JapaneseCalendar:
0085         return 1721426; //  0001-01-01 Gregorian
0086     case QCalendarSystem::JulianCalendar:
0087         return 1721424; //  0001-01-01 ==  Gregorian
0088     case QCalendarSystem::ROCCalendar:
0089         return 2419403; //  0001-01-01 ==  1912-01-01 Gregorian
0090     case QCalendarSystem::ThaiCalendar:
0091         return 1522734; //  0000-01-01 == -0544-01-01 Gregorian
0092     default:
0093         return 0;
0094     }
0095 }
0096 
0097 qint64 QCalendarSystemPrivate::earliestValidDate() const
0098 {
0099     switch (calendarSystem()) {
0100     case QCalendarSystem::GregorianCalendar:
0101         return -31738; // -4800-01-01 Gregorian
0102     case QCalendarSystem::CopticCalendar:
0103         return 1825030; //  0001-01-01 == 0284-08-29 Gregorian
0104     case QCalendarSystem::EthiopicCalendar:
0105         return 1724221; //  0001-01-01 == 0008-08-29 Gregorian
0106     case QCalendarSystem::EthiopicAmeteAlemCalendar:
0107         return -284655; //  0001-01-01 == -5492-08-29 Gregorian
0108     case QCalendarSystem::IndianNationalCalendar:
0109         return 1749994; //  0000-01-01 == 0078-03-22 Gregorian
0110     case QCalendarSystem::IslamicCivilCalendar:
0111         return 1948440; //  0001-01-01 == 0622-07-19 Gregorian
0112     case QCalendarSystem::ISO8601Calendar:
0113         return 1721060; //  0000-01-01 Gregorian
0114     case QCalendarSystem::JapaneseCalendar:
0115         return -31738; // -4800-01-01 Gregorian
0116     case QCalendarSystem::JulianCalendar:
0117         return -31776; // -4800-01-01 Julian
0118     case QCalendarSystem::ROCCalendar:
0119         return 2419403; //  0001-01-01 == 1912-01-01 Gregorian
0120     case QCalendarSystem::ThaiCalendar:
0121         return 1522734; //  0000-01-01 == -0544-01-01 Gregorian
0122     default:
0123         return 0;
0124     }
0125 }
0126 
0127 int QCalendarSystemPrivate::earliestValidYear() const
0128 {
0129     switch (calendarSystem()) {
0130     case QCalendarSystem::GregorianCalendar:
0131     case QCalendarSystem::JapaneseCalendar:
0132     case QCalendarSystem::JulianCalendar:
0133         return -4800;
0134     case QCalendarSystem::IndianNationalCalendar:
0135     case QCalendarSystem::ISO8601Calendar:
0136     case QCalendarSystem::ThaiCalendar:
0137         return 0;
0138     default:
0139         return 1;
0140     }
0141 }
0142 
0143 qint64 QCalendarSystemPrivate::latestValidDate() const
0144 {
0145     switch (calendarSystem()) {
0146     case QCalendarSystem::GregorianCalendar:
0147         return 5373484; //  9999-12-31 Gregorian
0148     case QCalendarSystem::CopticCalendar:
0149         return 5477164; //  9999-13-05 == 10283-11-12 Gregorian
0150     case QCalendarSystem::EthiopicCalendar:
0151         return 5376721; //  9999-13-05 == 10008-11-10 Gregorian
0152     case QCalendarSystem::EthiopicAmeteAlemCalendar:
0153         return 3367114; //  9999-13-05 ==  4506-09-29 Gregorian
0154     case QCalendarSystem::IndianNationalCalendar:
0155         return 5402054; //  9999-12-30 == 10078-03-21 Gregorian
0156     case QCalendarSystem::IslamicCivilCalendar:
0157         return 5491751; //  9999-12-29 == 10323-10-21 Gregorian
0158     case QCalendarSystem::ISO8601Calendar:
0159         return 5373484; //  9999-12-31 Gregorian
0160     case QCalendarSystem::JapaneseCalendar:
0161         return 5373484; //  9999-12-31 Gregorian
0162     case QCalendarSystem::JulianCalendar:
0163         return 5373557; //  9999-12-31 == 10000-03-13 Gregorian
0164     case QCalendarSystem::ROCCalendar:
0165         return 6071462; //  9999-12-31 == 11910-12-31 Gregorian
0166     case QCalendarSystem::ThaiCalendar:
0167         return 5175158; //  9999-12-31 ==  9456-12-31 Gregorian
0168     default:
0169         return 0;
0170     }
0171 }
0172 
0173 int QCalendarSystemPrivate::latestValidYear() const
0174 {
0175     switch (calendarSystem()) {
0176     default:
0177         return 9999;
0178     }
0179 }
0180 
0181 int QCalendarSystemPrivate::yearOffset() const
0182 {
0183     switch (calendarSystem()) {
0184     case QCalendarSystem::ROCCalendar:
0185         return 1911; // 0001-01-01 == 1912-01-01 Gregorian
0186     case QCalendarSystem::ThaiCalendar:
0187         return -543; // 0000-01-01 == -544-01-01 Gregorian
0188     default:
0189         return 0;
0190     }
0191 }
0192 
0193 int QCalendarSystemPrivate::maxMonthsInYear() const
0194 {
0195     switch (calendarSystem()) {
0196     case QCalendarSystem::CopticCalendar:
0197     case QCalendarSystem::EthiopicCalendar:
0198     case QCalendarSystem::EthiopicAmeteAlemCalendar:
0199         return 13;
0200     default:
0201         return 12;
0202     }
0203 }
0204 
0205 int QCalendarSystemPrivate::monthsInYear(int year) const
0206 {
0207     // year = year + yearOffset();
0208     Q_UNUSED(year);
0209 
0210     switch (calendarSystem()) {
0211     case QCalendarSystem::CopticCalendar:
0212     case QCalendarSystem::EthiopicCalendar:
0213     case QCalendarSystem::EthiopicAmeteAlemCalendar:
0214         return 13;
0215     default:
0216         return 12;
0217     }
0218 }
0219 
0220 int QCalendarSystemPrivate::maxDaysInYear() const
0221 {
0222     switch (calendarSystem()) {
0223     case QCalendarSystem::IslamicCivilCalendar:
0224         return 355;
0225     default:
0226         return 366;
0227     }
0228 }
0229 
0230 int QCalendarSystemPrivate::daysInYear(int year) const
0231 {
0232     switch (calendarSystem()) {
0233     case QCalendarSystem::IslamicCivilCalendar:
0234         return isLeapYear(year) ? 355 : 354;
0235     default:
0236         return isLeapYear(year) ? 366 : 365;
0237     }
0238 }
0239 
0240 int QCalendarSystemPrivate::maxDaysInMonth() const
0241 {
0242     switch (calendarSystem()) {
0243     case QCalendarSystem::CopticCalendar:
0244     case QCalendarSystem::EthiopicCalendar:
0245     case QCalendarSystem::EthiopicAmeteAlemCalendar:
0246     case QCalendarSystem::IslamicCivilCalendar:
0247         return 30;
0248     default:
0249         return 31;
0250     }
0251 }
0252 
0253 int QCalendarSystemPrivate::daysInMonth(int year, int month) const
0254 {
0255     if (month < 1 || month > monthsInYear(year)) {
0256         return 0;
0257     }
0258 
0259     switch (calendarSystem()) {
0260     case QCalendarSystem::GregorianCalendar:
0261     case QCalendarSystem::ISO8601Calendar:
0262     case QCalendarSystem::JapaneseCalendar:
0263     case QCalendarSystem::ROCCalendar:
0264     case QCalendarSystem::ThaiCalendar:
0265     case QCalendarSystem::JulianCalendar: {
0266         if (month == 2 && isLeapYear(year)) {
0267             return 29;
0268         } else {
0269             return julianMonths[month];
0270         }
0271     }
0272     case QCalendarSystem::CopticCalendar:
0273     case QCalendarSystem::EthiopicCalendar:
0274     case QCalendarSystem::EthiopicAmeteAlemCalendar: {
0275         if (month == 13) {
0276             return isLeapYear(year) ? 6 : 5;
0277         } else {
0278             return 30;
0279         }
0280     }
0281     case QCalendarSystem::IndianNationalCalendar: {
0282         if (month >= 7) {
0283             return 30;
0284         } else if (month >= 2) {
0285             return 31;
0286         } else if (isLeapYear(year)) {
0287             return 31;
0288         } else {
0289             return 30;
0290         }
0291     }
0292     case QCalendarSystem::IslamicCivilCalendar: {
0293         if (month == 12 && isLeapYear(year)) {
0294             return 30;
0295         } else if (month % 2 == 0) {
0296             return 29;
0297         } else {
0298             return 30;
0299         }
0300     }
0301     default:
0302         return 0;
0303     }
0304 }
0305 
0306 bool QCalendarSystemPrivate::hasYearZero() const
0307 {
0308     switch (calendarSystem()) {
0309     case QCalendarSystem::IndianNationalCalendar:
0310     case QCalendarSystem::ISO8601Calendar:
0311     case QCalendarSystem::ThaiCalendar:
0312         return true;
0313     default:
0314         return false;
0315     }
0316 }
0317 
0318 bool QCalendarSystemPrivate::hasLeapMonths() const
0319 {
0320     switch (calendarSystem()) {
0321     default:
0322         return false;
0323     }
0324 }
0325 
0326 int QCalendarSystemPrivate::quarter(int month) const
0327 {
0328     switch (calendarSystem()) {
0329     case QCalendarSystem::CopticCalendar:
0330     case QCalendarSystem::EthiopicCalendar:
0331     case QCalendarSystem::EthiopicAmeteAlemCalendar:
0332         if (month == 13) { // Consider the short epagomenal month as part of the 4th quarter
0333             return 4;
0334         }
0335         Q_FALLTHROUGH();
0336     default:
0337         return (((month - 1) / 3) + 1);
0338     }
0339 }
0340 
0341 bool QCalendarSystemPrivate::isLeapYear(int year) const
0342 {
0343     year = year + yearOffset();
0344 
0345     // Uses same rule as Gregorian and in same years as Gregorian to keep in sync
0346     // Can't use yearOffset() as this offset only applies for isLeapYear()
0347     if (calendarSystem() == QCalendarSystem::IndianNationalCalendar) {
0348         year = year + 78;
0349     }
0350 
0351     if (year < 1 && !hasYearZero()) {
0352         ++year;
0353     }
0354 
0355     switch (calendarSystem()) {
0356     case QCalendarSystem::GregorianCalendar:
0357     case QCalendarSystem::IndianNationalCalendar:
0358     case QCalendarSystem::ISO8601Calendar:
0359     case QCalendarSystem::JapaneseCalendar:
0360     case QCalendarSystem::ROCCalendar:
0361     case QCalendarSystem::ThaiCalendar:
0362         return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);
0363     case QCalendarSystem::CopticCalendar:
0364     case QCalendarSystem::EthiopicCalendar:
0365     case QCalendarSystem::EthiopicAmeteAlemCalendar:
0366         return (year % 4 == 3);
0367     case QCalendarSystem::JulianCalendar:
0368         return (year % 4 == 0);
0369     case QCalendarSystem::IslamicCivilCalendar:
0370         return ((((11 * year) + 14) % 30) < 11);
0371     default:
0372         return false;
0373     }
0374 }
0375 
0376 void QCalendarSystemPrivate::julianDayToDate(qint64 jd, int *year, int *month, int *day) const
0377 {
0378     int yy = 0;
0379     int mm = 0;
0380     int dd = 0;
0381 
0382     switch (calendarSystem()) {
0383     case QCalendarSystem::GregorianCalendar:
0384     case QCalendarSystem::ISO8601Calendar:
0385     case QCalendarSystem::JapaneseCalendar:
0386     case QCalendarSystem::ROCCalendar:
0387     case QCalendarSystem::ThaiCalendar: {
0388         // Formula from The Calendar FAQ by Claus Tondering
0389         // https://www.tondering.dk/claus/calendar.html
0390         qint64 a = jd + 32044;
0391         qint64 b = ((4 * a) + 3) / 146097;
0392         qint64 c = a - ((146097 * b) / 4);
0393         qint64 d = ((4 * c) + 3) / 1461;
0394         qint64 e = c - ((1461 * d) / 4);
0395         qint64 m = ((5 * e) + 2) / 153;
0396         dd = e - (((153 * m) + 2) / 5) + 1;
0397         mm = m + 3 - (12 * (m / 10));
0398         yy = (100 * b) + d - 4800 + (m / 10);
0399         break;
0400     }
0401 
0402     case QCalendarSystem::CopticCalendar:
0403     case QCalendarSystem::EthiopicCalendar:
0404     case QCalendarSystem::EthiopicAmeteAlemCalendar: {
0405         // Formula derived from first principles by John Layt
0406         qint64 s = jd - (epoch() - 365);
0407         qint64 l = s / 1461;
0408         yy = (l * 4) + qMin(static_cast<qint64>(3), (s % 1461) / 365);
0409         qint64 diy = s - (yy * 365) + (yy / 4);
0410         mm = (diy / 30) + 1;
0411         dd = (diy % 30) + 1;
0412         break;
0413     }
0414 
0415     case QCalendarSystem::IndianNationalCalendar: {
0416         // Formula from the "Explanatory Supplement to the Astronomical Almanac"
0417         // Revised Edition 2006 section 12.94 pp 605-606, US Naval Observatory
0418         // Originally from the "Report of the Calendar Reform Committee" 1955, Indian Government
0419         qint64 l = jd + 68518;
0420         qint64 n = (4 * l) / 146097;
0421         l = l - (146097 * n + 3) / 4;
0422         qint64 i = (4000 * (l + 1)) / 1461001;
0423         l = l - (1461 * i) / 4 + 1;
0424         qint64 j = ((l - 1) / 31) * (1 - l / 185) + (l / 185) * ((l - 156) / 30 + 5) - l / 366;
0425         dd = l - 31 * j + ((j + 2) / 8) * (j - 5);
0426         l = j / 11;
0427         mm = j + 2 - 12 * l;
0428         yy = 100 * (n - 49) + l + i - 78;
0429         break;
0430     }
0431 
0432     case QCalendarSystem::IslamicCivilCalendar: {
0433         // Formula from the "Explanatory Supplement to the Astronomical Almanac"
0434         // Revised Edition 2006 section ??? pp ???, US Naval Observatory
0435         // Derived from Fliegel & Van Flandern 1968
0436         qint64 l = jd - epoch() + 10632;
0437         qint64 n = (l - 1) / 10631;
0438         l = l - 10631 * n + 354;
0439         int j = ((10985 - l) / 5316) * ((50 * l) / 17719) + (l / 5670) * ((43 * l) / 15238);
0440         l = l - ((30 - j) / 15) * ((17719 * j) / 50) - (j / 16) * ((15238 * j) / 43) + 29;
0441         yy = (30 * n) + j - 30;
0442         mm = (24 * l) / 709;
0443         dd = l - ((709 * mm) / 24);
0444         break;
0445     }
0446 
0447     case QCalendarSystem::JulianCalendar: {
0448         // Formula from The Calendar FAQ by Claus Tondering
0449         // https://www.tondering.dk/claus/calendar.html
0450         qint64 b = 0;
0451         qint64 c = jd + 32082;
0452         qint64 d = ((4 * c) + 3) / 1461;
0453         qint64 e = c - ((1461 * d) / 4);
0454         qint64 m = ((5 * e) + 2) / 153;
0455         dd = e - (((153 * m) + 2) / 5) + 1;
0456         mm = m + 3 - (12 * (m / 10));
0457         yy = (100 * b) + d - 4800 + (m / 10);
0458         break;
0459     }
0460 
0461     default:
0462         break;
0463     }
0464 
0465     if (!hasYearZero() && yy < 1) {
0466         yy -= 1;
0467     }
0468 
0469     yy = yy - yearOffset();
0470 
0471     if (year) {
0472         *year = yy;
0473     }
0474     if (month) {
0475         *month = mm;
0476     }
0477     if (day) {
0478         *day = dd;
0479     }
0480 }
0481 
0482 qint64 QCalendarSystemPrivate::julianDayFromDate(int year, int month, int day) const
0483 {
0484     qint64 jd = 0;
0485 
0486     year = year + yearOffset();
0487 
0488     if (year < 1 && !hasYearZero()) {
0489         year = year + 1;
0490     }
0491 
0492     switch (calendarSystem()) {
0493     case QCalendarSystem::GregorianCalendar:
0494     case QCalendarSystem::ISO8601Calendar:
0495     case QCalendarSystem::JapaneseCalendar:
0496     case QCalendarSystem::ROCCalendar:
0497     case QCalendarSystem::ThaiCalendar: {
0498         // Formula from The Calendar FAQ by Claus Tondering
0499         // https://www.tondering.dk/claus/calendar.html
0500         int a = (14 - month) / 12;
0501         year = year + 4800 - a;
0502         int m = month + (12 * a) - 3;
0503         jd = day + (((153 * m) + 2) / 5) //
0504             + (365 * year) //
0505             + (year / 4) //
0506             - (year / 100) //
0507             + (year / 400) //
0508             - 32045;
0509         break;
0510     }
0511 
0512     case QCalendarSystem::CopticCalendar:
0513     case QCalendarSystem::EthiopicCalendar:
0514     case QCalendarSystem::EthiopicAmeteAlemCalendar: {
0515         // Formula derived from first principles by John Layt
0516         jd = epoch() - 1 //
0517             + ((year - 1) * 365) //
0518             + (year / 4) //
0519             + ((month - 1) * 30) //
0520             + day;
0521         break;
0522     }
0523 
0524     case QCalendarSystem::IndianNationalCalendar: {
0525         // Formula from the "Explanatory Supplement to the Astronomical Almanac"
0526         // Revised Edition 2006 section 12.94 pp 605-606, US Naval Observatory
0527         // Originally from the "Report of the Calendar Reform Committee" 1955, Indian Government
0528         jd = 365 * year + (year + 78 - 1 / month) / 4 //
0529             + 31 * month //
0530             - (month + 9) / 11 //
0531             - (month / 7) * (month - 7) //
0532             - (3 * ((year + 78 - 1 / month) / 100 + 1)) / 4 //
0533             + day //
0534             + 1749579;
0535         break;
0536     }
0537 
0538     case QCalendarSystem::IslamicCivilCalendar: {
0539         // Formula from the "Explanatory Supplement to the Astronomical Almanac"
0540         // Revised Edition 2006 section ??? pp ???, US Naval Observatory
0541         // Derived from Fliegel & Van Flandern 1968
0542         jd = (3 + (11 * year)) / 30 //
0543             + 354 * year //
0544             + 30 * month //
0545             - (month - 1) / 2 //
0546             + day //
0547             + epoch() //
0548             - 385;
0549         break;
0550     }
0551 
0552     case QCalendarSystem::JulianCalendar: {
0553         // Formula from The Calendar FAQ by Claus Tondering
0554         // https://www.tondering.dk/claus/calendar.html
0555         int a = (14 - month) / 12;
0556         year = year + 4800 - a;
0557         int m = month + (12 * a) - 3;
0558         jd = day //
0559             + (((153 * m) + 2) / 5) //
0560             + (365 * year) //
0561             + (year / 4) //
0562             - 32083;
0563         break;
0564     }
0565 
0566     default:
0567         break;
0568     }
0569 
0570     return jd;
0571 }
0572 
0573 // Some private utility rules
0574 
0575 bool QCalendarSystemPrivate::isValidYear(int year) const
0576 {
0577     return year >= earliestValidYear() //
0578         && year <= latestValidYear() //
0579         && (year == 0 ? hasYearZero() : true);
0580 }
0581 
0582 bool QCalendarSystemPrivate::isValidMonth(int year, int month) const
0583 {
0584     return isValidYear(year) && month >= 1 && month <= monthsInYear(year);
0585 }
0586 
0587 int QCalendarSystemPrivate::addYears(int y1, int years) const
0588 {
0589     int y2 = y1 + years;
0590 
0591     if (!hasYearZero()) {
0592         if (y1 > 0 && y2 <= 0) {
0593             --y2;
0594         } else if (y1 < 0 && y2 >= 0) {
0595             ++y2;
0596         }
0597     }
0598 
0599     return y2;
0600 }
0601 
0602 int QCalendarSystemPrivate::diffYears(int y1, int y2) const
0603 {
0604     int dy = y2 - y1;
0605 
0606     if (!hasYearZero()) {
0607         if (y2 > 0 && y1 < 0) {
0608             dy -= 1;
0609         } else if (y2 < 0 && y1 > 0) {
0610             dy += 1;
0611         }
0612     }
0613 
0614     return dy;
0615 }
0616 
0617 // QCalendarSystem public api
0618 
0619 QCalendarSystem::QCalendarSystem(QCalendarSystem::CalendarSystem calendar)
0620     : d(new QCalendarSystemPrivate(calendar))
0621 {
0622 }
0623 
0624 QCalendarSystem::~QCalendarSystem()
0625 {
0626 }
0627 
0628 QCalendarSystem &QCalendarSystem::operator=(const QCalendarSystem &other)
0629 {
0630     d = other.d;
0631     return *this;
0632 }
0633 
0634 QCalendarSystem::CalendarSystem QCalendarSystem::calendarSystem() const
0635 {
0636     return d->calendarSystem();
0637 }
0638 
0639 QDate QCalendarSystem::epoch() const
0640 {
0641     return QDate::fromJulianDay(d->epoch());
0642 }
0643 
0644 QDate QCalendarSystem::earliestValidDate() const
0645 {
0646     return QDate::fromJulianDay(d->earliestValidDate());
0647 }
0648 
0649 QDate QCalendarSystem::latestValidDate() const
0650 {
0651     return QDate::fromJulianDay(d->latestValidDate());
0652 }
0653 
0654 int QCalendarSystem::maximumMonthsInYear() const
0655 {
0656     return d->maxMonthsInYear();
0657 }
0658 
0659 int QCalendarSystem::maximumDaysInYear() const
0660 {
0661     return d->maxDaysInYear();
0662 }
0663 
0664 int QCalendarSystem::maximumDaysInMonth() const
0665 {
0666     return d->maxDaysInMonth();
0667 }
0668 
0669 bool QCalendarSystem::isValid(const QDate &date) const
0670 {
0671     return date.isValid() && date >= earliestValidDate() && date <= latestValidDate();
0672 }
0673 
0674 bool QCalendarSystem::isValid(int year, int month, int day) const
0675 {
0676     return d->isValidMonth(year, month) && day >= 1 && day <= d->daysInMonth(year, month);
0677 }
0678 
0679 bool QCalendarSystem::isValid(int year, int dayOfYear) const
0680 {
0681     return d->isValidYear(year) && dayOfYear > 0 && dayOfYear <= d->daysInYear(year);
0682 }
0683 
0684 QDate QCalendarSystem::date(int year, int month, int day) const
0685 {
0686     if (isValid(year, month, day)) {
0687         return QDate::fromJulianDay(d->julianDayFromDate(year, month, day));
0688     } else {
0689         return QDate();
0690     }
0691 }
0692 
0693 QDate QCalendarSystem::date(int year, int dayOfYear) const
0694 {
0695     if (isValid(year, dayOfYear)) {
0696         return QDate::fromJulianDay(d->julianDayFromDate(year, 1, 1) + dayOfYear - 1);
0697     } else {
0698         return QDate();
0699     }
0700 }
0701 
0702 void QCalendarSystem::getDate(const QDate &date, int *year, int *month, int *day) const
0703 {
0704     int yy = 0;
0705     int mm = 0;
0706     int dd = 0;
0707 
0708     if (isValid(date)) {
0709         d->julianDayToDate(date.toJulianDay(), &yy, &mm, &dd);
0710     }
0711 
0712     if (year) {
0713         *year = yy;
0714     }
0715     if (month) {
0716         *month = mm;
0717     }
0718     if (day) {
0719         *day = dd;
0720     }
0721 }
0722 
0723 int QCalendarSystem::year(const QDate &date) const
0724 {
0725     int y = 0;
0726 
0727     if (isValid(date)) {
0728         d->julianDayToDate(date.toJulianDay(), &y, nullptr, nullptr);
0729     }
0730 
0731     return y;
0732 }
0733 
0734 int QCalendarSystem::month(const QDate &date) const
0735 {
0736     int m = 0;
0737 
0738     if (isValid(date)) {
0739         d->julianDayToDate(date.toJulianDay(), nullptr, &m, nullptr);
0740     }
0741 
0742     return m;
0743 }
0744 
0745 int QCalendarSystem::day(const QDate &date) const
0746 {
0747     int dd = 0;
0748 
0749     if (isValid(date)) {
0750         d->julianDayToDate(date.toJulianDay(), nullptr, nullptr, &dd);
0751     }
0752 
0753     return dd;
0754 }
0755 
0756 int QCalendarSystem::quarter(const QDate &date) const
0757 {
0758     if (isValid(date)) {
0759         int month;
0760         d->julianDayToDate(date.toJulianDay(), nullptr, &month, nullptr);
0761         return d->quarter(month);
0762     } else {
0763         return 0;
0764     }
0765 }
0766 
0767 int QCalendarSystem::quarter(int year, int month, int day) const
0768 {
0769     if (isValid(year, month, day)) {
0770         return d->quarter(month);
0771     } else {
0772         return 0;
0773     }
0774 }
0775 
0776 int QCalendarSystem::dayOfYear(const QDate &date) const
0777 {
0778     if (isValid(date)) {
0779         return date.toJulianDay() - firstDayOfYear(date).toJulianDay() + 1;
0780     } else {
0781         return 0;
0782     }
0783 }
0784 
0785 int QCalendarSystem::dayOfYear(int year, int month, int day) const
0786 {
0787     return dayOfYear(date(year, month, day));
0788 }
0789 
0790 int QCalendarSystem::dayOfWeek(const QDate &date) const
0791 {
0792     // jd 0 = Monday = weekday 1.  We've never skipped weekdays.
0793     if (isValid(date)) {
0794         if (date.toJulianDay() >= 0) {
0795             return (date.toJulianDay() % daysInWeek()) + 1;
0796         } else {
0797             return ((date.toJulianDay() + 1) % daysInWeek()) + daysInWeek();
0798         }
0799     } else {
0800         return 0;
0801     }
0802 }
0803 
0804 int QCalendarSystem::dayOfWeek(int year, int month, int day) const
0805 {
0806     return dayOfWeek(date(year, month, day));
0807 }
0808 
0809 // TODO These are ISO weeks, may need to localise
0810 int QCalendarSystem::weekNumber(const QDate &date, int *yearNum) const
0811 {
0812     if (isValid(date)) {
0813         int year;
0814         int month;
0815         int day;
0816         d->julianDayToDate(date.toJulianDay(), &year, &month, &day);
0817         return weekNumber(year, month, day, yearNum);
0818     } else {
0819         return 0;
0820     }
0821 }
0822 
0823 /*
0824     The following method is based on code from tzcode's strftime.c, which is turn said to be
0825     "Based on the UCB version with the ID appearing below."
0826 
0827     SPDX-FileCopyrightText: 1989 The Regents of the University of California. All rights reserved.
0828 
0829     SPDX-License-Identifier: BSD-3-Clause
0830 */
0831 // TODO These are ISO weeks, may need to localise
0832 // TODO Replace with cleanly licensed routine
0833 int QCalendarSystem::weekNumber(int year, int month, int day, int *yearNum) const
0834 {
0835     if (!isValid(year, month, day)) {
0836         if (yearNum) {
0837             *yearNum = 0;
0838         }
0839         return 0;
0840     }
0841 
0842     int yday = dayOfYear(year, month, day) - 1;
0843     int wday = dayOfWeek(year, month, day);
0844     if (wday == 7) {
0845         wday = 0;
0846     }
0847     int w;
0848 
0849     for (;;) {
0850         int len;
0851         int bot;
0852         int top;
0853 
0854         len = d->daysInYear(year);
0855         /*
0856         ** What yday (-3 ... 3) does
0857         ** the ISO year begin on?
0858         */
0859         bot = ((yday + 11 - wday) % 7) - 3;
0860         /*
0861         ** What yday does the NEXT
0862         ** ISO year begin on?
0863         */
0864         top = bot - (len % 7);
0865         if (top < -3) {
0866             top += 7;
0867         }
0868         top += len;
0869         if (yday >= top) {
0870             ++year;
0871             w = 1;
0872             break;
0873         }
0874         if (yday >= bot) {
0875             w = 1 + ((yday - bot) / 7);
0876             break;
0877         }
0878         --year;
0879         yday += d->daysInYear(year);
0880     }
0881 
0882     if (yearNum) {
0883         *yearNum = year;
0884     }
0885 
0886     return w;
0887 }
0888 
0889 int QCalendarSystem::monthsInYear(const QDate &date) const
0890 {
0891     if (isValid(date)) {
0892         return d->monthsInYear(year(date));
0893     } else {
0894         return 0;
0895     }
0896 }
0897 
0898 int QCalendarSystem::monthsInYear(int year) const
0899 {
0900     if (d->isValidYear(year)) {
0901         return d->monthsInYear(year);
0902     } else {
0903         return 0;
0904     }
0905 }
0906 
0907 int QCalendarSystem::weeksInYear(const QDate &date) const
0908 {
0909     if (isValid(date)) {
0910         return weeksInYear(year(date));
0911     } else {
0912         return 0;
0913     }
0914 }
0915 
0916 // TODO This is ISO weeks, may need to localise
0917 int QCalendarSystem::weeksInYear(int year) const
0918 {
0919     if (d->isValidYear(year)) {
0920         int weekYear = year;
0921         int lastWeek = weekNumber(lastDayOfYear(year), &weekYear);
0922         if (lastWeek < 1 || weekYear != year) {
0923             lastWeek = weekNumber(addDays(lastDayOfYear(year), -7), &weekYear);
0924         }
0925         return lastWeek;
0926     } else {
0927         return 0;
0928     }
0929 }
0930 
0931 int QCalendarSystem::daysInYear(const QDate &date) const
0932 {
0933     if (isValid(date)) {
0934         return d->daysInYear(year(date));
0935     } else {
0936         return 0;
0937     }
0938 }
0939 
0940 int QCalendarSystem::daysInYear(int year) const
0941 {
0942     if (d->isValidYear(year)) {
0943         return d->daysInYear(year);
0944     } else {
0945         return 0;
0946     }
0947 }
0948 
0949 int QCalendarSystem::daysInMonth(const QDate &date) const
0950 {
0951     if (isValid(date)) {
0952         int year;
0953         int month;
0954         d->julianDayToDate(date.toJulianDay(), &year, &month, nullptr);
0955         return d->daysInMonth(year, month);
0956     } else {
0957         return 0;
0958     }
0959 }
0960 
0961 int QCalendarSystem::daysInMonth(int year, int month) const
0962 {
0963     if (d->isValidMonth(year, month)) {
0964         return d->daysInMonth(year, month);
0965     } else {
0966         return 0;
0967     }
0968 }
0969 
0970 int QCalendarSystem::daysInWeek() const
0971 {
0972     return 7;
0973 }
0974 
0975 bool QCalendarSystem::isLeapYear(const QDate &date) const
0976 {
0977     if (isValid(date)) {
0978         return d->isLeapYear(year(date));
0979     } else {
0980         return false;
0981     }
0982 }
0983 
0984 bool QCalendarSystem::isLeapYear(int year) const
0985 {
0986     if (d->isValidYear(year)) {
0987         return d->isLeapYear(year);
0988     } else {
0989         return false;
0990     }
0991 }
0992 
0993 QDate QCalendarSystem::addYears(const QDate &dt, int years) const
0994 {
0995     if (isValid(dt)) {
0996         int year;
0997         int month;
0998         int day;
0999         d->julianDayToDate(dt.toJulianDay(), &year, &month, &day);
1000         year = d->addYears(year, years);
1001         month = qMin(month, d->monthsInYear(year));
1002         return date(year, month, qMin(day, d->daysInMonth(year, month)));
1003     } else {
1004         return QDate();
1005     }
1006 }
1007 
1008 QDate QCalendarSystem::addMonths(const QDate &dt, int months) const
1009 {
1010     if (isValid(dt)) {
1011         int year;
1012         int month;
1013         int day;
1014         d->julianDayToDate(dt.toJulianDay(), &year, &month, &day);
1015         while (months != 0) {
1016             if (months < 0) {
1017                 if (month + months >= 1) {
1018                     month += months;
1019                     months = 0;
1020                 } else {
1021                     year = d->addYears(year, -1);
1022                     months += d->monthsInYear(year);
1023                 }
1024             } else {
1025                 int miy = d->monthsInYear(year);
1026                 if (month + months <= miy) {
1027                     month += months;
1028                     months = 0;
1029                 } else {
1030                     year = d->addYears(year, 1);
1031                     months -= miy;
1032                 }
1033             }
1034         }
1035         return date(year, month, qMin(day, d->daysInMonth(year, month)));
1036     } else {
1037         return QDate();
1038     }
1039 }
1040 
1041 QDate QCalendarSystem::addDays(const QDate &date, int days) const
1042 {
1043     return date.addDays(days);
1044 }
1045 
1046 // Caters for Leap Months, but possibly not for Hebrew
1047 int QCalendarSystem::yearsDifference(const QDate &fromDate, const QDate &toDate) const
1048 {
1049     if (!isValid(fromDate) || !isValid(toDate) || toDate == fromDate) {
1050         return 0;
1051     }
1052 
1053     if (toDate < fromDate) {
1054         return -yearsDifference(toDate, fromDate);
1055     }
1056 
1057     int y1;
1058     int m1;
1059     int d1;
1060     int y2;
1061     int m2;
1062     int d2;
1063     d->julianDayToDate(fromDate.toJulianDay(), &y1, &m1, &d1);
1064     d->julianDayToDate(toDate.toJulianDay(), &y2, &m2, &d2);
1065 
1066     if (y2 == y1) {
1067         return 0;
1068     }
1069 
1070     if (m2 > m1) {
1071         return d->diffYears(y1, y2);
1072     }
1073 
1074     if (m2 < m1) {
1075         return d->diffYears(y1, y2) - 1;
1076     }
1077 
1078     // m2 == m1
1079     // Allow for last day of month to last day of month and leap days
1080     // e.g. 2000-02-29 to 2001-02-28 is 1 year not 0 years
1081     if (d2 >= d1 //
1082         || (d1 == d->daysInMonth(y1, m1) && d2 == d->daysInMonth(y2, m2))) {
1083         return d->diffYears(y1, y2);
1084     } else {
1085         return d->diffYears(y1, y2) - 1;
1086     }
1087 }
1088 
1089 // Caters for Leap Months, but possibly not for Hebrew
1090 int QCalendarSystem::monthsDifference(const QDate &fromDate, const QDate &toDate) const
1091 {
1092     if (!isValid(fromDate) || !isValid(toDate) || toDate == fromDate) {
1093         return 0;
1094     }
1095 
1096     if (toDate < fromDate) {
1097         return -monthsDifference(toDate, fromDate);
1098     }
1099 
1100     int y1;
1101     int m1;
1102     int d1;
1103     int y2;
1104     int m2;
1105     int d2;
1106     int my;
1107     d->julianDayToDate(fromDate.toJulianDay(), &y1, &m1, &d1);
1108     d->julianDayToDate(toDate.toJulianDay(), &y2, &m2, &d2);
1109 
1110     // Calculate number of months in full years preceding y2
1111     if (y2 == y1) {
1112         my = 0;
1113     } else if (d->hasLeapMonths()) {
1114         my = 0;
1115         for (int y = y1; y < y2; y = d->addYears(y, 1)) {
1116             my = my + monthsInYear(y);
1117         }
1118     } else {
1119         my = d->diffYears(y1, y2) * monthsInYear(y2);
1120     }
1121 
1122     // Allow for last day of month to last day of month and leap days
1123     // e.g. 2010-03-31 to 2010-04-30 is 1 month not 0 months
1124     // also 2000-02-29 to 2001-02-28 is 12 months not 11 months
1125     if (d2 >= d1 //
1126         || (d1 == d->daysInMonth(y1, m1) && d2 == d->daysInMonth(y2, m2))) {
1127         return my + m2 - m1;
1128     } else {
1129         return my + m2 - m1 - 1;
1130     }
1131 }
1132 
1133 qint64 QCalendarSystem::daysDifference(const QDate &fromDate, const QDate &toDate) const
1134 {
1135     if (isValid(fromDate) && isValid(toDate)) {
1136         return toDate.toJulianDay() - fromDate.toJulianDay();
1137     } else {
1138         return 0;
1139     }
1140 }
1141 
1142 // Caters for Leap Months, but possibly not for Hebrew
1143 void QCalendarSystem::dateDifference(const QDate &fromDate, const QDate &toDate, int *years, int *months, int *days, int *direction) const
1144 {
1145     int dy = 0;
1146     int dm = 0;
1147     int dd = 0;
1148     int dir = 1;
1149 
1150     if (isValid(fromDate) && isValid(toDate) && fromDate != toDate) {
1151         if (toDate < fromDate) {
1152             dateDifference(toDate, fromDate, &dy, &dm, &dd, nullptr);
1153             dir = -1;
1154         } else {
1155             int y1;
1156             int m1;
1157             int d1;
1158             int y2;
1159             int m2;
1160             int d2;
1161             d->julianDayToDate(fromDate.toJulianDay(), &y1, &m1, &d1);
1162             d->julianDayToDate(toDate.toJulianDay(), &y2, &m2, &d2);
1163 
1164             dy = yearsDifference(fromDate, toDate);
1165 
1166             // Calculate months and days difference
1167             int miy0 = d->monthsInYear(d->addYears(y2, -1));
1168             if (d2 >= d1) {
1169                 dm = (miy0 + m2 - m1) % miy0;
1170                 dd = d2 - d1;
1171             } else { // d2 < d1
1172                 // Allow for last day of month to last day of month and leap days
1173                 // e.g. 2010-03-31 to 2010-04-30 is 1 month
1174                 //      2000-02-29 to 2001-02-28 is 1 year
1175                 //      2000-02-29 to 2001-03-01 is 1 year 1 day
1176                 int dim0 = daysInMonth(addMonths(toDate, -1));
1177                 int dim1 = d->daysInMonth(y1, m1);
1178                 if (d1 == dim1 && d2 == d->daysInMonth(y2, m2)) {
1179                     dm = (miy0 + m2 - m1) % miy0;
1180                     dd = 0;
1181                 } else if (month(addMonths(toDate, -1)) == m1 && dim0 < dim1) {
1182                     // Special case where fromDate = leap day and toDate in month following but non-leap year
1183                     // e.g. 2000-02-29 to 2001-03-01 needs to use 29 to calculate day number not 28
1184                     dm = (miy0 + m2 - m1 - 1) % miy0;
1185                     dd = (dim1 + d2 - d1) % dim1;
1186                 } else {
1187                     dm = (miy0 + m2 - m1 - 1) % miy0;
1188                     dd = (dim0 + d2 - d1) % dim0;
1189                 }
1190             }
1191         }
1192     }
1193 
1194     if (years) {
1195         *years = dy;
1196     }
1197     if (months) {
1198         *months = dm;
1199     }
1200     if (days) {
1201         *days = dd;
1202     }
1203     if (direction) {
1204         *direction = dir;
1205     }
1206 }
1207 
1208 QDate QCalendarSystem::firstDayOfYear(const QDate &dt) const
1209 {
1210     if (isValid(dt)) {
1211         return date(year(dt), 1, 1);
1212     } else {
1213         return QDate();
1214     }
1215 }
1216 
1217 QDate QCalendarSystem::firstDayOfYear(int year) const
1218 {
1219     return date(year, 1, 1);
1220 }
1221 
1222 QDate QCalendarSystem::lastDayOfYear(const QDate &dt) const
1223 {
1224     if (isValid(dt)) {
1225         int y = year(dt);
1226         return date(y, d->daysInYear(y));
1227     } else {
1228         return QDate();
1229     }
1230 }
1231 
1232 QDate QCalendarSystem::lastDayOfYear(int year) const
1233 {
1234     if (d->isValidYear(year)) {
1235         return date(year, d->daysInYear(year));
1236     } else {
1237         return QDate();
1238     }
1239 }
1240 
1241 QDate QCalendarSystem::firstDayOfMonth(const QDate &dt) const
1242 {
1243     int year;
1244     int month;
1245     getDate(dt, &year, &month, nullptr);
1246     return date(year, month, 1);
1247 }
1248 
1249 QDate QCalendarSystem::firstDayOfMonth(int year, int month) const
1250 {
1251     return date(year, month, 1);
1252 }
1253 
1254 QDate QCalendarSystem::lastDayOfMonth(const QDate &dt) const
1255 {
1256     int year;
1257     int month;
1258     getDate(dt, &year, &month, nullptr);
1259     return date(year, month, daysInMonth(year, month));
1260 }
1261 
1262 QDate QCalendarSystem::lastDayOfMonth(int year, int month) const
1263 {
1264     return date(year, month, daysInMonth(year, month));
1265 }