File indexing completed on 2025-01-05 03:52:00

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2014-11-03
0007  * Description : calendar system.
0008  *
0009  * SPDX-FileCopyrightText: 2014      by John Layt <john at layt dot net>
0010  * SPDX-FileCopyrightText: 2016-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "calsystem.h"
0017 
0018 // Local includes
0019 
0020 #include "digikam_debug.h"
0021 
0022 namespace DigikamGenericCalendarPlugin
0023 {
0024 
0025 class Q_DECL_HIDDEN CalSystemPrivate : public QSharedData
0026 {
0027 public:
0028 
0029     explicit CalSystemPrivate(CalSystem::CalendarSystem calendar);
0030 
0031     CalSystem::CalendarSystem calendarSystem()                         const;
0032     qint64 epoch()                                                     const;
0033     qint64 earliestValidDate()                                         const;
0034     int    earliestValidYear()                                         const;
0035     qint64 latestValidDate()                                           const;
0036     int    latestValidYear()                                           const;
0037     int    yearOffset()                                                const;
0038     int    maxMonthsInYear()                                           const;
0039     int    monthsInYear(int year)                                      const;
0040     int    maxDaysInYear()                                             const;
0041     int    daysInYear(int year)                                        const;
0042     int    maxDaysInMonth()                                            const;
0043     int    daysInMonth(int year, int month)                            const;
0044     bool   hasYearZero()                                               const;
0045     bool   hasLeapMonths()                                             const;
0046 
0047     int    quarter(int month)                                          const;
0048     bool   isLeapYear(int year)                                        const;
0049     void   julianDayToDate(qint64 jd, int* year, int* month, int* day) const;
0050     qint64 julianDayFromDate(int yr, int mth, int dy)                  const;
0051 
0052     bool   isValidYear(int year)                                       const;
0053     bool   isValidMonth(int year, int month)                           const;
0054     int    addYears(int y1, int years)                                 const;
0055     int    diffYears(int y1, int y2)                                   const;
0056 
0057 public:
0058 
0059     CalSystem::CalendarSystem m_calendarSystem;
0060 };
0061 
0062 static const char julianMonths[] =
0063 {
0064     0,
0065     31,
0066     28,
0067     31,
0068     30,
0069     31,
0070     30,
0071     31,
0072     31,
0073     30,
0074     31,
0075     30,
0076     31
0077 };
0078 
0079 CalSystemPrivate::CalSystemPrivate(CalSystem::CalendarSystem calendar)
0080     : QSharedData     (),
0081       m_calendarSystem(calendar)
0082 {
0083 }
0084 
0085 CalSystem::CalendarSystem CalSystemPrivate::calendarSystem() const
0086 {
0087     if (m_calendarSystem == CalSystem::DefaultCalendar)
0088     {
0089         return CalSystem::GregorianCalendar;
0090     }
0091     else
0092     {
0093         return m_calendarSystem;
0094     }
0095 }
0096 
0097 qint64 CalSystemPrivate::epoch() const
0098 {
0099     switch (calendarSystem())
0100     {
0101         case CalSystem::GregorianCalendar:
0102             return 1721426;  //  0001-01-01 Gregorian
0103 
0104         case CalSystem::CopticCalendar:
0105             return 1825030;  //  0001-01-01 ==  0284-08-29 Gregorian
0106 
0107         case CalSystem::EthiopicCalendar:
0108             return 1724221;  //  0001-01-01 ==  0008-08-29 Gregorian
0109 
0110         case CalSystem::EthiopicAmeteAlemCalendar:
0111             return -284655;  //  0001-01-01 == -5492-08-29 Gregorian
0112 
0113         case CalSystem::IndianNationalCalendar:
0114             return 1749994;  //  0000-01-01 == 0078-03-22 Gregorian
0115 
0116         case CalSystem::IslamicCivilCalendar:
0117             return 1948440;  //  0001-01-01 == 0622-07-19 Gregorian
0118 
0119         case CalSystem::ISO8601Calendar:
0120             return 1721060;  //  0000-01-01 Gregorian
0121 
0122         case CalSystem::JapaneseCalendar:
0123             return 1721426;  //  0001-01-01 Gregorian
0124 
0125         case CalSystem::JulianCalendar:
0126             return 1721424;  //  0001-01-01 ==  Gregorian
0127 
0128         case CalSystem::ROCCalendar:
0129             return 2419403;  //  0001-01-01 ==  1912-01-01 Gregorian
0130 
0131         case CalSystem::ThaiCalendar:
0132             return 1522734;  //  0000-01-01 == -0544-01-01 Gregorian
0133 
0134         default:
0135             return 0;
0136     }
0137 }
0138 
0139 qint64 CalSystemPrivate::earliestValidDate() const
0140 {
0141     switch (calendarSystem())
0142     {
0143         case CalSystem::GregorianCalendar:
0144             return -31738;   // -4800-01-01 Gregorian
0145 
0146         case CalSystem::CopticCalendar:
0147             return 1825030;  //  0001-01-01 == 0284-08-29 Gregorian
0148 
0149         case CalSystem::EthiopicCalendar:
0150             return 1724221;  //  0001-01-01 == 0008-08-29 Gregorian
0151 
0152         case CalSystem::EthiopicAmeteAlemCalendar:
0153             return -284655;  //  0001-01-01 == -5492-08-29 Gregorian
0154 
0155         case CalSystem::IndianNationalCalendar:
0156             return 1749994;  //  0000-01-01 == 0078-03-22 Gregorian
0157 
0158         case CalSystem::IslamicCivilCalendar:
0159             return 1948440;  //  0001-01-01 == 0622-07-19 Gregorian
0160 
0161         case CalSystem::ISO8601Calendar:
0162             return 1721060;  //  0000-01-01 Gregorian
0163 
0164         case CalSystem::JapaneseCalendar:
0165             return -31738;   // -4800-01-01 Gregorian
0166 
0167         case CalSystem::JulianCalendar:
0168             return -31776;   // -4800-01-01 Julian
0169 
0170         case CalSystem::ROCCalendar:
0171             return 2419403;  //  0001-01-01 == 1912-01-01 Gregorian
0172 
0173         case CalSystem::ThaiCalendar:
0174             return 1522734;  //  0000-01-01 == -0544-01-01 Gregorian
0175 
0176         default:
0177             return 0;
0178     }
0179 }
0180 
0181 int CalSystemPrivate::earliestValidYear() const
0182 {
0183     switch (calendarSystem())
0184     {
0185         case CalSystem::GregorianCalendar:
0186         case CalSystem::JapaneseCalendar:
0187         case CalSystem::JulianCalendar:
0188             return -4800;
0189 
0190         case CalSystem::IndianNationalCalendar:
0191         case CalSystem::ISO8601Calendar:
0192         case CalSystem::ThaiCalendar:
0193             return 0;
0194 
0195         default:
0196             return 1;
0197     }
0198 }
0199 
0200 qint64 CalSystemPrivate::latestValidDate() const
0201 {
0202     switch (calendarSystem())
0203     {
0204         case CalSystem::GregorianCalendar:
0205             return 5373484;  //  9999-12-31 Gregorian
0206 
0207         case CalSystem::CopticCalendar:
0208             return 5477164;  //  9999-13-05 == 10283-11-12 Gregorian
0209 
0210         case CalSystem::EthiopicCalendar:
0211             return 5376721;  //  9999-13-05 == 10008-11-10 Gregorian
0212 
0213         case CalSystem::EthiopicAmeteAlemCalendar:
0214             return 3367114;  //  9999-13-05 ==  4506-09-29 Gregorian
0215 
0216         case CalSystem::IndianNationalCalendar:
0217             return 5402054;  //  9999-12-30 == 10078-03-21 Gregorian
0218 
0219         case CalSystem::IslamicCivilCalendar:
0220             return 5491751;  //  9999-12-29 == 10323-10-21 Gregorian
0221 
0222         case CalSystem::ISO8601Calendar:
0223             return 5373484;  //  9999-12-31 Gregorian
0224 
0225         case CalSystem::JapaneseCalendar:
0226             return 5373484;  //  9999-12-31 Gregorian
0227 
0228         case CalSystem::JulianCalendar:
0229             return 5373557;  //  9999-12-31 == 10000-03-13 Gregorian
0230 
0231         case CalSystem::ROCCalendar:
0232             return 6071462;  //  9999-12-31 == 11910-12-31 Gregorian
0233 
0234         case CalSystem::ThaiCalendar:
0235             return 5175158;  //  9999-12-31 ==  9456-12-31 Gregorian
0236 
0237         default:
0238             return 0;
0239     }
0240 }
0241 
0242 int CalSystemPrivate::latestValidYear() const
0243 {
0244     return 9999;
0245 }
0246 
0247 int CalSystemPrivate::yearOffset() const
0248 {
0249     switch (calendarSystem())
0250     {
0251         case CalSystem::ROCCalendar:
0252             return 1911;  // 0001-01-01 == 1912-01-01 Gregorian
0253 
0254         case CalSystem::ThaiCalendar:
0255             return -543;  // 0000-01-01 == -544-01-01 Gregorian
0256 
0257         default:
0258             return 0;
0259     }
0260 }
0261 
0262 int CalSystemPrivate::maxMonthsInYear() const
0263 {
0264     switch (calendarSystem())
0265     {
0266         case CalSystem::CopticCalendar:
0267         case CalSystem::EthiopicCalendar:
0268         case CalSystem::EthiopicAmeteAlemCalendar:
0269             return 13;
0270 
0271         default:
0272             return 12;
0273     }
0274 }
0275 
0276 int CalSystemPrivate::monthsInYear(int /*year*/) const
0277 {
0278     switch (calendarSystem())
0279     {
0280         case CalSystem::CopticCalendar:
0281         case CalSystem::EthiopicCalendar:
0282         case CalSystem::EthiopicAmeteAlemCalendar:
0283             return 13;
0284 
0285         default:
0286             return 12;
0287     }
0288 }
0289 
0290 int CalSystemPrivate::maxDaysInYear() const
0291 {
0292     switch (calendarSystem())
0293     {
0294         case CalSystem::IslamicCivilCalendar:
0295             return 355;
0296 
0297         default:
0298             return 366;
0299     }
0300 }
0301 
0302 int CalSystemPrivate::daysInYear(int year) const
0303 {
0304     switch (calendarSystem())
0305     {
0306         case CalSystem::IslamicCivilCalendar:
0307             return (isLeapYear(year) ? 355 : 354);
0308 
0309         default:
0310             return (isLeapYear(year) ? 366 : 365);
0311     }
0312 }
0313 
0314 int CalSystemPrivate::maxDaysInMonth() const
0315 {
0316     switch (calendarSystem())
0317     {
0318         case CalSystem::CopticCalendar:
0319         case CalSystem::EthiopicCalendar:
0320         case CalSystem::EthiopicAmeteAlemCalendar:
0321         case CalSystem::IslamicCivilCalendar:
0322             return 30;
0323 
0324         default:
0325             return 31;
0326     }
0327 }
0328 
0329 int CalSystemPrivate::daysInMonth(int year, int month) const
0330 {
0331     if (month < 1 || month > monthsInYear(year))
0332     {
0333         return 0;
0334     }
0335 
0336     switch (calendarSystem())
0337     {
0338         case CalSystem::GregorianCalendar:
0339         case CalSystem::ISO8601Calendar:
0340         case CalSystem::JapaneseCalendar:
0341         case CalSystem::ROCCalendar:
0342         case CalSystem::ThaiCalendar:
0343         case CalSystem::JulianCalendar:
0344         {
0345             if ((month == 2) && isLeapYear(year))
0346             {
0347                 return 29;
0348             }
0349             else
0350             {
0351                 return julianMonths[month];
0352             }
0353         }
0354 
0355         case CalSystem::CopticCalendar:
0356         case CalSystem::EthiopicCalendar:
0357         case CalSystem::EthiopicAmeteAlemCalendar:
0358         {
0359             if (month == 13)
0360             {
0361                 return (isLeapYear(year) ? 6 : 5);
0362             }
0363             else
0364             {
0365                 return 30;
0366             }
0367         }
0368 
0369         case CalSystem::IndianNationalCalendar:
0370         {
0371             if      (month >= 7)
0372             {
0373                 return 30;
0374             }
0375             else if (month >= 2)
0376             {
0377                 return 31;
0378             }
0379             else if (isLeapYear(year))
0380             {
0381                 return 31;
0382             }
0383             else
0384             {
0385                 return 30;
0386             }
0387         }
0388 
0389         case CalSystem::IslamicCivilCalendar:
0390         {
0391             if      ((month == 12) && isLeapYear(year))
0392             {
0393                 return 30;
0394             }
0395             else if ((month % 2) == 0)
0396             {
0397                 return 29;
0398             }
0399             else
0400             {
0401                 return 30;
0402             }
0403         }
0404 
0405         default:
0406             return 0;
0407     }
0408 }
0409 
0410 bool CalSystemPrivate::hasYearZero() const
0411 {
0412     switch (calendarSystem())
0413     {
0414         case CalSystem::IndianNationalCalendar:
0415         case CalSystem::ISO8601Calendar:
0416         case CalSystem::ThaiCalendar:
0417             return true;
0418 
0419         default:
0420             return false;
0421     }
0422 }
0423 
0424 bool CalSystemPrivate::hasLeapMonths() const
0425 {
0426     return false;
0427 }
0428 
0429 int CalSystemPrivate::quarter(int month) const
0430 {
0431     switch (calendarSystem())
0432     {
0433         case CalSystem::CopticCalendar:
0434         case CalSystem::EthiopicCalendar:
0435         case CalSystem::EthiopicAmeteAlemCalendar:
0436         {
0437             if (month == 13)
0438             {
0439                 // Consider the short epagomenal month as part of the 4th quarter
0440                 return 4;
0441             }
0442 
0443 #if __GNUC__ >= 7   // krazy:exclude=cpp
0444 
0445             [[fallthrough]];
0446 
0447 #endif
0448         }
0449 
0450         default:
0451         {
0452             return (((month - 1) / 3) + 1);
0453         }
0454     }
0455 }
0456 
0457 bool CalSystemPrivate::isLeapYear(int year) const
0458 {
0459     year = year + yearOffset();
0460 
0461     // Uses same rule as Gregorian and in same years as Gregorian to keep in sync
0462     // Can't use yearOffset() as this offset only applies for isLeapYear()
0463 
0464     if (calendarSystem() == CalSystem::IndianNationalCalendar)
0465     {
0466         year = year + 78;
0467     }
0468 
0469     if ((year < 1) && !hasYearZero())
0470     {
0471         ++year;
0472     }
0473 
0474     switch (calendarSystem())
0475     {
0476         case CalSystem::GregorianCalendar:
0477         case CalSystem::IndianNationalCalendar:
0478         case CalSystem::ISO8601Calendar:
0479         case CalSystem::JapaneseCalendar:
0480         case CalSystem::ROCCalendar:
0481         case CalSystem::ThaiCalendar:
0482             return ((((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0));
0483 
0484         case CalSystem::CopticCalendar:
0485         case CalSystem::EthiopicCalendar:
0486         case CalSystem::EthiopicAmeteAlemCalendar:
0487             return ((year % 4) == 3);
0488 
0489         case CalSystem::JulianCalendar:
0490             return ((year % 4) == 0);
0491 
0492         case CalSystem::IslamicCivilCalendar:
0493             return ((((11 * year) + 14) % 30) < 11);
0494 
0495         default:
0496             return false;
0497     }
0498 }
0499 
0500 void CalSystemPrivate::julianDayToDate(qint64 jd, int* year, int* month, int* day) const
0501 {
0502     int yy = 0;
0503     int mm = 0;
0504     int dd = 0;
0505 
0506     switch (calendarSystem())
0507     {
0508         case CalSystem::GregorianCalendar:
0509         case CalSystem::ISO8601Calendar:
0510         case CalSystem::JapaneseCalendar:
0511         case CalSystem::ROCCalendar:
0512         case CalSystem::ThaiCalendar:
0513         {
0514             // Formula from The Calendar FAQ by Claus Tondering
0515             // www.tondering.dk/claus/cal/node3.html#SECTION003161000000000000000
0516 
0517             qint64 a = jd + 32044;
0518             qint64 b = ((4 * a) + 3) / 146097;
0519             qint64 c = a - ((146097 * b) / 4);
0520             qint64 d = ((4 * c) + 3) / 1461;
0521             qint64 e = c - ((1461 * d) / 4);
0522             qint64 m = ((5 * e) + 2) / 153;
0523             dd       = e - (((153 * m) + 2) / 5) + 1;
0524             mm       = m + 3 - (12 * (m / 10));
0525             yy       = (100 * b) + d - 4800 + (m / 10);
0526             break;
0527         }
0528 
0529         case CalSystem::CopticCalendar:
0530         case CalSystem::EthiopicCalendar:
0531         case CalSystem::EthiopicAmeteAlemCalendar:
0532         {
0533             // Formula derived from first principles by John Layt
0534 
0535             qint64 s   = jd - (epoch() - 365);
0536             qint64 l   = s / 1461;
0537             yy         = (l * 4) + qMin((qint64)3, (s % 1461) / 365);
0538             qint64 diy = s - (yy * 365) + (yy / 4);
0539             mm         = (diy / 30) + 1;
0540             dd         = (diy % 30) + 1;
0541             break;
0542         }
0543 
0544         case CalSystem::IndianNationalCalendar:
0545         {
0546             // Formula from the "Explanatory Supplement to the Astronomical Almanac"
0547             // Revised Edition 2006 section 12.94 pp 605-606, US Naval Observatory
0548             // Originally from the "Report of the Calendar Reform Committee" 1955, Indian Government
0549 
0550             qint64 l = jd + 68518;
0551             qint64 n = (4 * l) / 146097;
0552             l        = l - (146097 * n + 3) / 4;
0553             qint64 i = (4000 * (l + 1)) / 1461001;
0554             l        = l - (1461 * i) / 4 + 1;
0555             qint64 j = ((l - 1) / 31) * (1 - l / 185) + (l / 185) * ((l - 156) / 30 + 5) - l / 366;
0556             dd       = l - 31 * j + ((j + 2) / 8) * (j - 5);
0557             l        = j / 11;
0558             mm       = j + 2 - 12 * l;
0559             yy       = 100 * (n - 49) + l + i - 78;
0560             break;
0561         }
0562 
0563         case CalSystem::IslamicCivilCalendar:
0564         {
0565             // Formula from the "Explanatory Supplement to the Astronomical Almanac"
0566             // Revised Edition 2006 section ??? pp ???, US Naval Observatory
0567             // Derived from Fliegel & Van Flandern 1968
0568 
0569             qint64 l = jd - epoch() + 10632;
0570             qint64 n = (l - 1) / 10631;
0571             l        = l - 10631 * n + 354;
0572             int j    = ((10985 - l) / 5316) * ((50 * l) / 17719) + (l / 5670) * ((43 * l) / 15238);
0573             l        = l - ((30 - j) / 15) * ((17719 * j) / 50) - (j / 16) * ((15238 * j) / 43) + 29;
0574             yy       = (30 * n) + j - 30;
0575             mm       = (24 * l) / 709;
0576             dd       = l - ((709 * mm) / 24);
0577             break;
0578         }
0579 
0580         case CalSystem::JulianCalendar:
0581         {
0582             // Formula from The Calendar FAQ by Claus Tondering
0583             // www.tondering.dk/claus/cal/node3.html#SECTION003161000000000000000
0584 
0585             qint64 b = 0;
0586             qint64 c = jd + 32082;
0587             qint64 d = ((4 * c) + 3) / 1461;
0588             qint64 e = c - ((1461 * d) / 4);
0589             qint64 m = ((5 * e) + 2) / 153;
0590             dd       = e - (((153 * m) + 2) / 5) + 1;
0591             mm       = m + 3 - (12 * (m / 10));
0592             yy       = (100 * b) + d - 4800 + (m / 10);
0593             break;
0594         }
0595 
0596         default:
0597             break;
0598     }
0599 
0600     if (!hasYearZero() && (yy < 1))
0601     {
0602         yy -= 1;
0603     }
0604 
0605     yy = yy - yearOffset();
0606 
0607     if (year)
0608     {
0609         *year = yy;
0610     }
0611 
0612     if (month)
0613     {
0614         *month = mm;
0615     }
0616 
0617     if (day)
0618     {
0619         *day = dd;
0620     }
0621 }
0622 
0623 qint64 CalSystemPrivate::julianDayFromDate(int yr, int mth, int dy) const
0624 {
0625     qint64 jd    = 0;
0626     qint64 year  = yr + yearOffset();
0627     qint64 month = mth;
0628     qint64 day   = dy;
0629 
0630     if (year < 1 && !hasYearZero())
0631     {
0632         year = year + 1;
0633     }
0634 
0635     switch (calendarSystem())
0636     {
0637         case CalSystem::GregorianCalendar:
0638         case CalSystem::ISO8601Calendar:
0639         case CalSystem::JapaneseCalendar:
0640         case CalSystem::ROCCalendar:
0641         case CalSystem::ThaiCalendar:
0642         {
0643             // Formula from The Calendar FAQ by Claus Tondering
0644             // www.tondering.dk/claus/cal/node3.html#SECTION003161000000000000000
0645 
0646             int a = (14 - month) / 12;
0647             year  = year + 4800 - a;
0648             int m = month + (12 * a) - 3;
0649             jd    = day
0650                    + (((153 * m) + 2) / 5)
0651                    + (365 * year)
0652                    + (year / 4)
0653                    - (year / 100)
0654                    + (year / 400)
0655                    - 32045;
0656             break;
0657         }
0658 
0659         case CalSystem::CopticCalendar:
0660         case CalSystem::EthiopicCalendar:
0661         case CalSystem::EthiopicAmeteAlemCalendar:
0662         {
0663             // Formula derived from first principles by John Layt
0664 
0665             jd = epoch() - 1
0666                 + ((year - 1) * 365)
0667                 + (year / 4)
0668                 + ((month - 1) * 30)
0669                 + day;
0670             break;
0671         }
0672 
0673         case CalSystem::IndianNationalCalendar:
0674         {
0675             // Formula from the "Explanatory Supplement to the Astronomical Almanac"
0676             // Revised Edition 2006 section 12.94 pp 605-606, US Naval Observatory
0677             // Originally from the "Report of the Calendar Reform Committee" 1955, Indian Government
0678 
0679             jd = 365 * year
0680                 + (year + 78 - 1 / month) / 4
0681                 + 31 * month
0682                 - (month + 9) / 11
0683                 - (month / 7) * (month - 7)
0684                 - (3 * ((year  + 78 - 1 / month) / 100 + 1)) / 4
0685                 + day
0686                 + 1749579;
0687             break;
0688         }
0689 
0690         case CalSystem::IslamicCivilCalendar:
0691         {
0692             // Formula from the "Explanatory Supplement to the Astronomical Almanac"
0693             // Revised Edition 2006 section ??? pp ???, US Naval Observatory
0694             // Derived from Fliegel & Van Flandern 1968
0695 
0696             jd = (3 + (11 * year)) / 30
0697                 + 354 * year
0698                 + 30 * month
0699                 - (month - 1) / 2
0700                 + day
0701                 + epoch()
0702                 - 385;
0703             break;
0704         }
0705 
0706         case CalSystem::JulianCalendar:
0707         {
0708             // Formula from The Calendar FAQ by Claus Tondering
0709             // www.tondering.dk/claus/cal/node3.html#SECTION003161000000000000000
0710 
0711             int a = (14 - month) / 12;
0712             year  = year + 4800 - a;
0713             int m = month + (12 * a) - 3;
0714             jd    = day
0715                    + (((153 * m) + 2) / 5)
0716                    + (365 * year)
0717                    + (year / 4)
0718                    - 32083;
0719             break;
0720         }
0721 
0722         default:
0723             break;
0724     }
0725 
0726     return jd;
0727 }
0728 
0729 // Some private utility rules
0730 
0731 bool CalSystemPrivate::isValidYear(int year) const
0732 {
0733     return (
0734             (year >= earliestValidYear()) &&
0735             (year <= latestValidYear())   &&
0736             ((year == 0) ? hasYearZero() : true)
0737            );
0738 }
0739 
0740 bool CalSystemPrivate::isValidMonth(int year, int month) const
0741 {
0742     return (
0743             isValidYear(year) &&
0744             (month >= 1)      &&
0745             (month <= monthsInYear(year))
0746            );
0747 }
0748 
0749 int CalSystemPrivate::addYears(int y1, int years) const
0750 {
0751     int y2 = y1 + years;
0752 
0753     if (!hasYearZero())
0754     {
0755         if      ((y1 > 0) && (y2 <= 0))
0756         {
0757             --y2;
0758         }
0759         else if ((y1 < 0) && (y2 >= 0))
0760         {
0761             ++y2;
0762         }
0763     }
0764 
0765     return y2;
0766 }
0767 
0768 int CalSystemPrivate::diffYears(int y1, int y2) const
0769 {
0770     int dy = y2 - y1;
0771 
0772     if (!hasYearZero())
0773     {
0774         if      ((y2 > 0) && (y1 < 0))
0775         {
0776             dy -= 1;
0777         }
0778         else if ((y2 < 0) && (y1 > 0))
0779         {
0780             dy += 1;
0781         }
0782     }
0783 
0784     return dy;
0785 }
0786 
0787 // ---------------------------------------------------------------
0788 
0789 CalSystem::CalSystem(CalSystem::CalendarSystem calendar)
0790     : d(new CalSystemPrivate(calendar))
0791 {
0792 }
0793 
0794 CalSystem::~CalSystem()
0795 {
0796 }
0797 
0798 CalSystem &CalSystem::operator=(const CalSystem &other)
0799 {
0800     d = other.d;
0801 
0802     return *this;
0803 }
0804 
0805 CalSystem::CalendarSystem CalSystem::calendarSystem() const
0806 {
0807     return d->calendarSystem();
0808 }
0809 
0810 QDate CalSystem::epoch() const
0811 {
0812     return QDate::fromJulianDay(d->epoch());
0813 }
0814 
0815 QDate CalSystem::earliestValidDate() const
0816 {
0817     return QDate::fromJulianDay(d->earliestValidDate());
0818 }
0819 
0820 QDate CalSystem::latestValidDate() const
0821 {
0822     return QDate::fromJulianDay(d->latestValidDate());
0823 }
0824 
0825 int CalSystem::maximumMonthsInYear() const
0826 {
0827     return d->maxMonthsInYear();
0828 }
0829 
0830 int CalSystem::maximumDaysInYear() const
0831 {
0832     return d->maxDaysInYear();
0833 }
0834 
0835 int CalSystem::maximumDaysInMonth() const
0836 {
0837     return d->maxDaysInMonth();
0838 }
0839 
0840 bool CalSystem::isValid(const QDate& date) const
0841 {
0842     return (
0843             date.isValid()                &&
0844             (date >= earliestValidDate()) &&
0845             (date <= latestValidDate())
0846            );
0847 }
0848 
0849 bool CalSystem::isValid(int year, int month, int day) const
0850 {
0851     return (
0852             d->isValidMonth(year, month) &&
0853             (day >= 1)                   &&
0854             (day <= d->daysInMonth(year, month))
0855            );
0856 }
0857 
0858 bool CalSystem::isValid(int year, int dayOfYear) const
0859 {
0860     return (
0861             d->isValidYear(year) &&
0862             (dayOfYear > 0)      &&
0863             (dayOfYear <= d->daysInYear(year))
0864            );
0865 }
0866 
0867 QDate CalSystem::date(int year, int month, int day) const
0868 {
0869     if (isValid(year, month, day))
0870     {
0871         return QDate::fromJulianDay(d->julianDayFromDate(year, month, day));
0872     }
0873     else
0874     {
0875         return QDate();
0876     }
0877 }
0878 
0879 QDate CalSystem::date(int year, int dayOfYear) const
0880 {
0881     if (isValid(year, dayOfYear))
0882     {
0883         return QDate::fromJulianDay(d->julianDayFromDate(year, 1, 1) + dayOfYear - 1);
0884     }
0885     else
0886     {
0887         return QDate();
0888     }
0889 }
0890 
0891 void CalSystem::getDate(const QDate& date, int* year, int* month, int* day) const
0892 {
0893     int yy = 0;
0894     int mm = 0;
0895     int dd = 0;
0896 
0897     if (isValid(date))
0898     {
0899         d->julianDayToDate(date.toJulianDay(), &yy, &mm, &dd);
0900     }
0901 
0902     if (year)
0903     {
0904         *year = yy;
0905     }
0906     if (month)
0907     {
0908         *month = mm;
0909     }
0910     if (day)
0911     {
0912         *day = dd;
0913     }
0914 }
0915 
0916 int CalSystem::year(const QDate& date) const
0917 {
0918     int y = 0;
0919 
0920     if (isValid(date))
0921     {
0922         d->julianDayToDate(date.toJulianDay(), &y, nullptr, nullptr);
0923     }
0924 
0925     return y;
0926 }
0927 
0928 int CalSystem::month(const QDate& date) const
0929 {
0930     int m = 0;
0931 
0932     if (isValid(date))
0933     {
0934         d->julianDayToDate(date.toJulianDay(), nullptr, &m, nullptr);
0935     }
0936 
0937     return m;
0938 }
0939 
0940 int CalSystem::day(const QDate& date) const
0941 {
0942     int dd = 0;
0943 
0944     if (isValid(date))
0945     {
0946         d->julianDayToDate(date.toJulianDay(), nullptr, nullptr, &dd);
0947     }
0948 
0949     return dd;
0950 }
0951 
0952 int CalSystem::quarter(const QDate& date) const
0953 {
0954     if (isValid(date))
0955     {
0956         int month;
0957         d->julianDayToDate(date.toJulianDay(), nullptr, &month, nullptr);
0958 
0959         return d->quarter(month);
0960     }
0961     else
0962     {
0963         return 0;
0964     }
0965 }
0966 
0967 int CalSystem::quarter(int year, int month, int day) const
0968 {
0969     if (isValid(year, month, day))
0970     {
0971         return d->quarter(month);
0972     }
0973     else
0974     {
0975         return 0;
0976     }
0977 }
0978 
0979 int CalSystem::dayOfYear(const QDate& date) const
0980 {
0981     if (isValid(date))
0982     {
0983         return date.toJulianDay() - firstDayOfYear(date).toJulianDay() + 1;
0984     }
0985     else
0986     {
0987         return 0;
0988     }
0989 }
0990 
0991 int CalSystem::dayOfYear(int year, int month, int day) const
0992 {
0993     return dayOfYear(date(year, month, day));
0994 }
0995 
0996 int CalSystem::dayOfWeek(const QDate& date) const
0997 {
0998     //jd 0 = Monday = weekday 1.  We've never skipped weekdays.
0999 
1000     if (isValid(date))
1001     {
1002         if (date.toJulianDay() >= 0)
1003         {
1004             return (date.toJulianDay() % daysInWeek()) + 1;
1005         }
1006         else
1007         {
1008             return ((date.toJulianDay() + 1) % daysInWeek()) + daysInWeek();
1009         }
1010     }
1011     else
1012     {
1013         return 0;
1014     }
1015 }
1016 
1017 int CalSystem::dayOfWeek(int year, int month, int day) const
1018 {
1019     return dayOfWeek(date(year, month, day));
1020 }
1021 
1022 // TODO: These are ISO weeks, may need to localise
1023 int CalSystem::weekNumber(const QDate& date, int* yearNum) const
1024 {
1025     if (isValid(date))
1026     {
1027         int year, month, day;
1028         d->julianDayToDate(date.toJulianDay(), &year, &month, &day);
1029 
1030         return weekNumber(year, month, day, yearNum);
1031     }
1032     else
1033     {
1034         return 0;
1035     }
1036 }
1037 
1038 /**
1039  * TODO: These are ISO weeks, may need to localise
1040  */
1041 int CalSystem::weekNumber(int year, int month, int day, int* yearNum) const
1042 {
1043     if (!isValid(year, month, day))
1044     {
1045         if (yearNum)
1046         {
1047             *yearNum = 0;
1048         }
1049 
1050         return 0;
1051     }
1052 
1053     int yday = dayOfYear(year, month, day) - 1;
1054     int wday = dayOfWeek(year, month, day);
1055 
1056     if (wday == 7)
1057     {
1058         wday = 0;
1059     }
1060 
1061     int w = 0;
1062 
1063     for ( ; ; )
1064     {
1065         int len = d->daysInYear(year);
1066 
1067         // What yday (-3 ... 3) does the ISO year begin on?
1068 
1069         int bot = ((yday + 11 - wday) % 7) - 3;
1070 
1071         // What yday does the NEXT ISO year begin on?
1072 
1073         int top = bot - (len % 7);
1074 
1075         if (top < -3)
1076         {
1077             top += 7;
1078         }
1079 
1080         top += len;
1081 
1082         if (yday >= top)
1083         {
1084             ++year;
1085             w = 1;
1086             break;
1087         }
1088 
1089         if (yday >= bot)
1090         {
1091             w = 1 + ((yday - bot) / 7);
1092             break;
1093         }
1094 
1095         --year;
1096         yday += d->daysInYear(year);
1097     }
1098 
1099     if (yearNum)
1100     {
1101         *yearNum = year;
1102     }
1103 
1104     return w;
1105 }
1106 
1107 int CalSystem::monthsInYear(const QDate& date) const
1108 {
1109     if (isValid(date))
1110     {
1111         return d->monthsInYear(year(date));
1112     }
1113     else
1114     {
1115         return 0;
1116     }
1117 }
1118 
1119 int CalSystem::monthsInYear(int year) const
1120 {
1121     if (d->isValidYear(year))
1122     {
1123         return d->monthsInYear(year);
1124     }
1125     else
1126     {
1127         return 0;
1128     }
1129 }
1130 
1131 int CalSystem::weeksInYear(const QDate& date) const
1132 {
1133     if (isValid(date))
1134     {
1135         return weeksInYear(year(date));
1136     }
1137     else
1138     {
1139         return 0;
1140     }
1141 }
1142 
1143 /**
1144  * TODO: This is ISO weeks, may need to localise
1145  */
1146 int CalSystem::weeksInYear(int year) const
1147 {
1148     if (d->isValidYear(year))
1149     {
1150         int weekYear = year;
1151         int lastWeek = weekNumber(lastDayOfYear(year), &weekYear);
1152 
1153         if ((lastWeek < 1) || (weekYear != year))
1154         {
1155             lastWeek = weekNumber(addDays(lastDayOfYear(year), -7), &weekYear);
1156         }
1157 
1158         return lastWeek;
1159     }
1160     else
1161     {
1162         return 0;
1163     }
1164 }
1165 
1166 int CalSystem::daysInYear(const QDate& date) const
1167 {
1168     if (isValid(date))
1169     {
1170         return d->daysInYear(year(date));
1171     }
1172     else
1173     {
1174         return 0;
1175     }
1176 }
1177 
1178 int CalSystem::daysInYear(int year) const
1179 {
1180     if (d->isValidYear(year))
1181     {
1182         return d->daysInYear(year);
1183     }
1184     else
1185     {
1186         return 0;
1187     }
1188 }
1189 
1190 int CalSystem::daysInMonth(const QDate& date) const
1191 {
1192     if (isValid(date))
1193     {
1194         int year, month;
1195         d->julianDayToDate(date.toJulianDay(), &year, &month, nullptr);
1196 
1197         return d->daysInMonth(year, month);
1198     }
1199     else
1200     {
1201         return 0;
1202     }
1203 }
1204 
1205 int CalSystem::daysInMonth(int year, int month) const
1206 {
1207     if (d->isValidMonth(year, month))
1208     {
1209         return d->daysInMonth(year, month);
1210     }
1211     else
1212     {
1213         return 0;
1214     }
1215 }
1216 
1217 int CalSystem::daysInWeek() const
1218 {
1219     return 7;
1220 }
1221 
1222 bool CalSystem::isLeapYear(const QDate& date) const
1223 {
1224     if (isValid(date))
1225     {
1226         return d->isLeapYear(year(date));
1227     }
1228     else
1229     {
1230         return false;
1231     }
1232 }
1233 
1234 bool CalSystem::isLeapYear(int year) const
1235 {
1236     if (d->isValidYear(year))
1237     {
1238         return d->isLeapYear(year);
1239     }
1240     else
1241     {
1242         return false;
1243     }
1244 }
1245 
1246 QDate CalSystem::addYears(const QDate& dt, int years) const
1247 {
1248     if (isValid(dt))
1249     {
1250         int year, month, day;
1251         d->julianDayToDate(dt.toJulianDay(), &year, &month, &day);
1252         year  = d->addYears(year, years);
1253         month = qMin(month, d->monthsInYear(year));
1254 
1255         return date(year, month, qMin(day, d->daysInMonth(year, month)));
1256     }
1257     else
1258     {
1259         return QDate();
1260     }
1261 }
1262 
1263 QDate CalSystem::addMonths(const QDate& dt, int months) const
1264 {
1265     if (isValid(dt))
1266     {
1267         int year, month, day;
1268         d->julianDayToDate(dt.toJulianDay(), &year, &month, &day);
1269 
1270         while (months != 0)
1271         {
1272             if (months < 0)
1273             {
1274                 if      ((month + months) >= 1)
1275                 {
1276                     month += months;
1277                     months = 0;
1278                 }
1279                 else if (months < 0)
1280                 {
1281                     year    = d->addYears(year, -1);
1282                     months += d->monthsInYear(year);
1283                 }
1284             }
1285             else
1286             {
1287                 int miy = d->monthsInYear(year);
1288 
1289                 if ((month + months) <= miy)
1290                 {
1291                     month += months;
1292                     months = 0;
1293                 }
1294                 else
1295                 {
1296                     year    = d->addYears(year, 1);
1297                     months -= miy;
1298                 }
1299             }
1300         }
1301 
1302         return date(year, month, qMin(day, d->daysInMonth(year, month)));
1303     }
1304     else
1305     {
1306         return QDate();
1307     }
1308 }
1309 
1310 QDate CalSystem::addDays(const QDate& date, int days) const
1311 {
1312     return date.addDays(days);
1313 }
1314 
1315 /**
1316  * Caters for Leap Months, but possibly not for Hebrew
1317  */
1318 int CalSystem::yearsDifference(const QDate& fromDate, const QDate& toDate) const
1319 {
1320     if (!isValid(fromDate) || !isValid(toDate) || (toDate == fromDate))
1321     {
1322         return 0;
1323     }
1324 
1325     if (toDate < fromDate)
1326     {
1327         return - yearsDifference(toDate, fromDate);
1328     }
1329 
1330     int y1, m1, d1, y2, m2, d2;
1331     d->julianDayToDate(fromDate.toJulianDay(), &y1, &m1, &d1);
1332     d->julianDayToDate(toDate.toJulianDay(),   &y2, &m2, &d2);
1333 
1334     if (y2 == y1)
1335     {
1336         return 0;
1337     }
1338 
1339     if (m2 > m1)
1340     {
1341         return d->diffYears(y1, y2);
1342     }
1343 
1344     if (m2 < m1)
1345     {
1346         return d->diffYears(y1, y2) - 1;
1347     }
1348 
1349     // m2 == m1
1350     // Allow for last day of month to last day of month and leap days
1351     // e.g. 2000-02-29 to 2001-02-28 is 1 year not 0 years
1352 
1353     if ((d2 >= d1) || (d1 == d->daysInMonth(y1, m1) && (d2 == d->daysInMonth(y2, m2))))
1354     {
1355         return d->diffYears(y1, y2);
1356     }
1357     else
1358     {
1359         return d->diffYears(y1, y2) - 1;
1360     }
1361 }
1362 
1363 /**
1364  * Caters for Leap Months, but possibly not for Hebrew
1365  */
1366 int CalSystem::monthsDifference(const QDate& fromDate, const QDate& toDate) const
1367 {
1368     if (!isValid(fromDate) || !isValid(toDate) || (toDate == fromDate))
1369     {
1370         return 0;
1371     }
1372 
1373     if (toDate < fromDate)
1374     {
1375         return - monthsDifference(toDate, fromDate);
1376     }
1377 
1378     int y1, m1, d1, y2, m2, d2, my;
1379     d->julianDayToDate(fromDate.toJulianDay(), &y1, &m1, &d1);
1380     d->julianDayToDate(toDate.toJulianDay(),   &y2, &m2, &d2);
1381 
1382     // Calculate number of months in full years preceding y2
1383 
1384     if      (y2 == y1)
1385     {
1386         my = 0;
1387     }
1388     else if (d->hasLeapMonths())
1389     {
1390         my = 0;
1391 
1392         for (int y = y1 ; y < y2 ; y = d->addYears(y, 1))
1393         {
1394             my = my + monthsInYear(y);
1395         }
1396     }
1397     else
1398     {
1399         my = d->diffYears(y1, y2) * monthsInYear(y2);
1400     }
1401 
1402     // Allow for last day of month to last day of month and leap days
1403     // e.g. 2010-03-31 to 2010-04-30 is 1 month not 0 months
1404     // also 2000-02-29 to 2001-02-28 is 12 months not 11 months
1405 
1406     if ((d2 >= d1) || (d1 == d->daysInMonth(y1, m1) && (d2 == d->daysInMonth(y2, m2))))
1407     {
1408         return (my + m2 - m1);
1409     }
1410     else
1411     {
1412         return (my + m2 - m1 - 1);
1413     }
1414 }
1415 
1416 qint64 CalSystem::daysDifference(const QDate& fromDate, const QDate& toDate) const
1417 {
1418     if (isValid(fromDate) && isValid(toDate))
1419     {
1420         return toDate.toJulianDay() - fromDate.toJulianDay();
1421     }
1422     else
1423     {
1424         return 0;
1425     }
1426 }
1427 
1428 /**
1429  * Caters for Leap Months, but possibly not for Hebrew
1430  */
1431 bool CalSystem::dateDifference(const QDate& fromDate, const QDate& toDate,
1432                                int* years, int* months, int* days, int* direction) const
1433 {
1434     int dy  = 0;
1435     int dm  = 0;
1436     int dd  = 0;
1437     int dir = 1;
1438 
1439     if (isValid(fromDate) && isValid(toDate) && fromDate != toDate)
1440     {
1441         if (toDate < fromDate)
1442         {
1443             dateDifference(toDate, fromDate, &dy, &dm, &dd, nullptr);
1444             dir = -1;
1445         }
1446         else
1447         {
1448             int y1, m1, d1, y2, m2, d2;
1449             d->julianDayToDate(fromDate.toJulianDay(), &y1, &m1, &d1);
1450             d->julianDayToDate(toDate.toJulianDay(),   &y2, &m2, &d2);
1451 
1452             dy = yearsDifference(fromDate, toDate);
1453 
1454             // Calculate months and days difference
1455 
1456             int miy0 = d->monthsInYear(d->addYears(y2, -1));
1457 
1458             if (miy0 == 0)
1459             {
1460                 qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Cannot compute date difference";
1461                 return false;
1462             }
1463 
1464             if (d2 >= d1)
1465             {
1466                 dm = (miy0 + m2 - m1) % miy0;
1467                 dd = d2 - d1;
1468             }
1469             else
1470             {
1471                 // d2 < d1
1472                 // Allow for last day of month to last day of month and leap days
1473                 // e.g. 2010-03-31 to 2010-04-30 is 1 month
1474                 //      2000-02-29 to 2001-02-28 is 1 year
1475                 //      2000-02-29 to 2001-03-01 is 1 year 1 day
1476 
1477                 int dim0 = daysInMonth(addMonths(toDate, -1));
1478                 int dim1 = d->daysInMonth(y1, m1);
1479 
1480                 if ((dim0 == 0) || (dim1 == 0))
1481                 {
1482                     qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Cannot compute date difference";
1483                     return false;
1484                 }
1485 
1486                 if      (d1 == dim1 && d2 == d->daysInMonth(y2, m2))
1487                 {
1488                     dm = (miy0 + m2 - m1) % miy0;
1489                     dd = 0;
1490                 }
1491                 else if (month(addMonths(toDate, -1)) == m1 && dim0 < dim1)
1492                 {
1493                     // Special case where fromDate = leap day and toDate in month following but non-leap year
1494                     // e.g. 2000-02-29 to 2001-03-01 needs to use 29 to calculate day number not 28
1495 
1496                     dm = (miy0 + m2 - m1 - 1) % miy0;
1497                     dd = (dim1 + d2 - d1) % dim1;
1498                 }
1499                 else
1500                 {
1501                     dm = (miy0 + m2 - m1 - 1) % miy0;
1502                     dd = (dim0 + d2 - d1) % dim0;
1503                 }
1504             }
1505         }
1506     }
1507 
1508     if (years)
1509     {
1510         *years = dy;
1511     }
1512 
1513     if (months)
1514     {
1515         *months = dm;
1516     }
1517 
1518     if (days)
1519     {
1520         *days = dd;
1521     }
1522 
1523     if (direction)
1524     {
1525         *direction = dir;
1526     }
1527 
1528     return true;
1529 }
1530 
1531 QDate CalSystem::firstDayOfYear(const QDate& dt) const
1532 {
1533     if (isValid(dt))
1534     {
1535         return date(year(dt), 1, 1);
1536     }
1537     else
1538     {
1539         return QDate();
1540     }
1541 }
1542 
1543 QDate CalSystem::firstDayOfYear(int year) const
1544 {
1545     return date(year, 1, 1);
1546 }
1547 
1548 QDate CalSystem::lastDayOfYear(const QDate& dt) const
1549 {
1550     if (isValid(dt))
1551     {
1552         int y = year(dt);
1553 
1554         return date(y, d->daysInYear(y));
1555     }
1556     else
1557     {
1558         return QDate();
1559     }
1560 }
1561 
1562 QDate CalSystem::lastDayOfYear(int year) const
1563 {
1564     if (d->isValidYear(year))
1565     {
1566         return date(year, d->daysInYear(year));
1567     }
1568     else
1569     {
1570         return QDate();
1571     }
1572 }
1573 
1574 QDate CalSystem::firstDayOfMonth(const QDate& dt) const
1575 {
1576     int year, month;
1577     getDate(dt, &year, &month, nullptr);
1578 
1579     return date(year, month, 1);
1580 }
1581 
1582 QDate CalSystem::firstDayOfMonth(int year, int month) const
1583 {
1584     return date(year, month, 1);
1585 }
1586 
1587 QDate CalSystem::lastDayOfMonth(const QDate& dt) const
1588 {
1589     int year, month;
1590     getDate(dt, &year, &month, nullptr);
1591 
1592     return date(year, month, daysInMonth(year, month));
1593 }
1594 
1595 QDate CalSystem::lastDayOfMonth(int year, int month) const
1596 {
1597     return date(year, month, daysInMonth(year, month));
1598 }
1599 
1600 } // Namespace Digikam