1 /*
2     This file is part of the kholidays library.
3 
4     SPDX-FileCopyrightText: 2014 John Layt <john@layt.net>
5 
6     SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #include "qcalendarsystem_p.h"
10 
11 #include <kholidays_debug.h>
12 
13 #include <QDate>
14 #include <QSharedData>
15 
16 class QCalendarSystemPrivate : public QSharedData
17 {
18 public:
19     explicit QCalendarSystemPrivate(QCalendarSystem::CalendarSystem calendar);
20 
21     QCalendarSystem::CalendarSystem calendarSystem() const;
22     qint64 epoch() const;
23     qint64 earliestValidDate() const;
24     int earliestValidYear() const;
25     qint64 latestValidDate() const;
26     int latestValidYear() const;
27     int yearOffset() const;
28     int maxMonthsInYear() const;
29     int monthsInYear(int year) const;
30     int maxDaysInYear() const;
31     int daysInYear(int year) const;
32     int maxDaysInMonth() const;
33     int daysInMonth(int year, int month) const;
34     bool hasYearZero() const;
35     bool hasLeapMonths() const;
36 
37     int quarter(int month) const;
38     bool isLeapYear(int year) const;
39     void julianDayToDate(qint64 jd, int *year, int *month, int *day) const;
40     qint64 julianDayFromDate(int year, int month, int day) const;
41 
42     bool isValidYear(int year) const;
43     bool isValidMonth(int year, int month) const;
44     int addYears(int y1, int years) const;
45     int diffYears(int y1, int y2) const;
46 
47     QCalendarSystem::CalendarSystem m_calendarSystem;
48 };
49 
50 static const char julianMonths[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
51 
QCalendarSystemPrivate(QCalendarSystem::CalendarSystem calendar)52 QCalendarSystemPrivate::QCalendarSystemPrivate(QCalendarSystem::CalendarSystem calendar)
53     : QSharedData()
54     , m_calendarSystem(calendar)
55 {
56 }
57 
calendarSystem() const58 QCalendarSystem::CalendarSystem QCalendarSystemPrivate::calendarSystem() const
59 {
60     if (m_calendarSystem == QCalendarSystem::DefaultCalendar) {
61         return QCalendarSystem::GregorianCalendar;
62     } else {
63         return m_calendarSystem;
64     }
65 }
66 
epoch() const67 qint64 QCalendarSystemPrivate::epoch() const
68 {
69     switch (calendarSystem()) {
70     case QCalendarSystem::GregorianCalendar:
71         return 1721426; //  0001-01-01 Gregorian
72     case QCalendarSystem::CopticCalendar:
73         return 1825030; //  0001-01-01 ==  0284-08-29 Gregorian
74     case QCalendarSystem::EthiopicCalendar:
75         return 1724221; //  0001-01-01 ==  0008-08-29 Gregorian
76     case QCalendarSystem::EthiopicAmeteAlemCalendar:
77         return -284655; //  0001-01-01 == -5492-08-29 Gregorian
78     case QCalendarSystem::IndianNationalCalendar:
79         return 1749994; //  0000-01-01 == 0078-03-22 Gregorian
80     case QCalendarSystem::IslamicCivilCalendar:
81         return 1948440; //  0001-01-01 == 0622-07-19 Gregorian
82     case QCalendarSystem::ISO8601Calendar:
83         return 1721060; //  0000-01-01 Gregorian
84     case QCalendarSystem::JapaneseCalendar:
85         return 1721426; //  0001-01-01 Gregorian
86     case QCalendarSystem::JulianCalendar:
87         return 1721424; //  0001-01-01 ==  Gregorian
88     case QCalendarSystem::ROCCalendar:
89         return 2419403; //  0001-01-01 ==  1912-01-01 Gregorian
90     case QCalendarSystem::ThaiCalendar:
91         return 1522734; //  0000-01-01 == -0544-01-01 Gregorian
92     default:
93         return 0;
94     }
95 }
96 
earliestValidDate() const97 qint64 QCalendarSystemPrivate::earliestValidDate() const
98 {
99     switch (calendarSystem()) {
100     case QCalendarSystem::GregorianCalendar:
101         return -31738; // -4800-01-01 Gregorian
102     case QCalendarSystem::CopticCalendar:
103         return 1825030; //  0001-01-01 == 0284-08-29 Gregorian
104     case QCalendarSystem::EthiopicCalendar:
105         return 1724221; //  0001-01-01 == 0008-08-29 Gregorian
106     case QCalendarSystem::EthiopicAmeteAlemCalendar:
107         return -284655; //  0001-01-01 == -5492-08-29 Gregorian
108     case QCalendarSystem::IndianNationalCalendar:
109         return 1749994; //  0000-01-01 == 0078-03-22 Gregorian
110     case QCalendarSystem::IslamicCivilCalendar:
111         return 1948440; //  0001-01-01 == 0622-07-19 Gregorian
112     case QCalendarSystem::ISO8601Calendar:
113         return 1721060; //  0000-01-01 Gregorian
114     case QCalendarSystem::JapaneseCalendar:
115         return -31738; // -4800-01-01 Gregorian
116     case QCalendarSystem::JulianCalendar:
117         return -31776; // -4800-01-01 Julian
118     case QCalendarSystem::ROCCalendar:
119         return 2419403; //  0001-01-01 == 1912-01-01 Gregorian
120     case QCalendarSystem::ThaiCalendar:
121         return 1522734; //  0000-01-01 == -0544-01-01 Gregorian
122     default:
123         return 0;
124     }
125 }
126 
earliestValidYear() const127 int QCalendarSystemPrivate::earliestValidYear() const
128 {
129     switch (calendarSystem()) {
130     case QCalendarSystem::GregorianCalendar:
131     case QCalendarSystem::JapaneseCalendar:
132     case QCalendarSystem::JulianCalendar:
133         return -4800;
134     case QCalendarSystem::IndianNationalCalendar:
135     case QCalendarSystem::ISO8601Calendar:
136     case QCalendarSystem::ThaiCalendar:
137         return 0;
138     default:
139         return 1;
140     }
141 }
142 
latestValidDate() const143 qint64 QCalendarSystemPrivate::latestValidDate() const
144 {
145     switch (calendarSystem()) {
146     case QCalendarSystem::GregorianCalendar:
147         return 5373484; //  9999-12-31 Gregorian
148     case QCalendarSystem::CopticCalendar:
149         return 5477164; //  9999-13-05 == 10283-11-12 Gregorian
150     case QCalendarSystem::EthiopicCalendar:
151         return 5376721; //  9999-13-05 == 10008-11-10 Gregorian
152     case QCalendarSystem::EthiopicAmeteAlemCalendar:
153         return 3367114; //  9999-13-05 ==  4506-09-29 Gregorian
154     case QCalendarSystem::IndianNationalCalendar:
155         return 5402054; //  9999-12-30 == 10078-03-21 Gregorian
156     case QCalendarSystem::IslamicCivilCalendar:
157         return 5491751; //  9999-12-29 == 10323-10-21 Gregorian
158     case QCalendarSystem::ISO8601Calendar:
159         return 5373484; //  9999-12-31 Gregorian
160     case QCalendarSystem::JapaneseCalendar:
161         return 5373484; //  9999-12-31 Gregorian
162     case QCalendarSystem::JulianCalendar:
163         return 5373557; //  9999-12-31 == 10000-03-13 Gregorian
164     case QCalendarSystem::ROCCalendar:
165         return 6071462; //  9999-12-31 == 11910-12-31 Gregorian
166     case QCalendarSystem::ThaiCalendar:
167         return 5175158; //  9999-12-31 ==  9456-12-31 Gregorian
168     default:
169         return 0;
170     }
171 }
172 
latestValidYear() const173 int QCalendarSystemPrivate::latestValidYear() const
174 {
175     switch (calendarSystem()) {
176     default:
177         return 9999;
178     }
179 }
180 
yearOffset() const181 int QCalendarSystemPrivate::yearOffset() const
182 {
183     switch (calendarSystem()) {
184     case QCalendarSystem::ROCCalendar:
185         return 1911; // 0001-01-01 == 1912-01-01 Gregorian
186     case QCalendarSystem::ThaiCalendar:
187         return -543; // 0000-01-01 == -544-01-01 Gregorian
188     default:
189         return 0;
190     }
191 }
192 
maxMonthsInYear() const193 int QCalendarSystemPrivate::maxMonthsInYear() const
194 {
195     switch (calendarSystem()) {
196     case QCalendarSystem::CopticCalendar:
197     case QCalendarSystem::EthiopicCalendar:
198     case QCalendarSystem::EthiopicAmeteAlemCalendar:
199         return 13;
200     default:
201         return 12;
202     }
203 }
204 
monthsInYear(int year) const205 int QCalendarSystemPrivate::monthsInYear(int year) const
206 {
207     // year = year + yearOffset();
208     Q_UNUSED(year);
209 
210     switch (calendarSystem()) {
211     case QCalendarSystem::CopticCalendar:
212     case QCalendarSystem::EthiopicCalendar:
213     case QCalendarSystem::EthiopicAmeteAlemCalendar:
214         return 13;
215     default:
216         return 12;
217     }
218 }
219 
maxDaysInYear() const220 int QCalendarSystemPrivate::maxDaysInYear() const
221 {
222     switch (calendarSystem()) {
223     case QCalendarSystem::IslamicCivilCalendar:
224         return 355;
225     default:
226         return 366;
227     }
228 }
229 
daysInYear(int year) const230 int QCalendarSystemPrivate::daysInYear(int year) const
231 {
232     switch (calendarSystem()) {
233     case QCalendarSystem::IslamicCivilCalendar:
234         return isLeapYear(year) ? 355 : 354;
235     default:
236         return isLeapYear(year) ? 366 : 365;
237     }
238 }
239 
maxDaysInMonth() const240 int QCalendarSystemPrivate::maxDaysInMonth() const
241 {
242     switch (calendarSystem()) {
243     case QCalendarSystem::CopticCalendar:
244     case QCalendarSystem::EthiopicCalendar:
245     case QCalendarSystem::EthiopicAmeteAlemCalendar:
246     case QCalendarSystem::IslamicCivilCalendar:
247         return 30;
248     default:
249         return 31;
250     }
251 }
252 
daysInMonth(int year,int month) const253 int QCalendarSystemPrivate::daysInMonth(int year, int month) const
254 {
255     if (month < 1 || month > monthsInYear(year)) {
256         return 0;
257     }
258 
259     switch (calendarSystem()) {
260     case QCalendarSystem::GregorianCalendar:
261     case QCalendarSystem::ISO8601Calendar:
262     case QCalendarSystem::JapaneseCalendar:
263     case QCalendarSystem::ROCCalendar:
264     case QCalendarSystem::ThaiCalendar:
265     case QCalendarSystem::JulianCalendar: {
266         if (month == 2 && isLeapYear(year)) {
267             return 29;
268         } else {
269             return julianMonths[month];
270         }
271     }
272     case QCalendarSystem::CopticCalendar:
273     case QCalendarSystem::EthiopicCalendar:
274     case QCalendarSystem::EthiopicAmeteAlemCalendar: {
275         if (month == 13) {
276             return isLeapYear(year) ? 6 : 5;
277         } else {
278             return 30;
279         }
280     }
281     case QCalendarSystem::IndianNationalCalendar: {
282         if (month >= 7) {
283             return 30;
284         } else if (month >= 2) {
285             return 31;
286         } else if (isLeapYear(year)) {
287             return 31;
288         } else {
289             return 30;
290         }
291     }
292     case QCalendarSystem::IslamicCivilCalendar: {
293         if (month == 12 && isLeapYear(year)) {
294             return 30;
295         } else if (month % 2 == 0) {
296             return 29;
297         } else {
298             return 30;
299         }
300     }
301     default:
302         return 0;
303     }
304 }
305 
hasYearZero() const306 bool QCalendarSystemPrivate::hasYearZero() const
307 {
308     switch (calendarSystem()) {
309     case QCalendarSystem::IndianNationalCalendar:
310     case QCalendarSystem::ISO8601Calendar:
311     case QCalendarSystem::ThaiCalendar:
312         return true;
313     default:
314         return false;
315     }
316 }
317 
hasLeapMonths() const318 bool QCalendarSystemPrivate::hasLeapMonths() const
319 {
320     switch (calendarSystem()) {
321     default:
322         return false;
323     }
324 }
325 
quarter(int month) const326 int QCalendarSystemPrivate::quarter(int month) const
327 {
328     switch (calendarSystem()) {
329     case QCalendarSystem::CopticCalendar:
330     case QCalendarSystem::EthiopicCalendar:
331     case QCalendarSystem::EthiopicAmeteAlemCalendar:
332         if (month == 13) { // Consider the short epagomenal month as part of the 4th quarter
333             return 4;
334         }
335         Q_FALLTHROUGH();
336     default:
337         return (((month - 1) / 3) + 1);
338     }
339 }
340 
isLeapYear(int year) const341 bool QCalendarSystemPrivate::isLeapYear(int year) const
342 {
343     year = year + yearOffset();
344 
345     // Uses same rule as Gregorian and in same years as Gregorian to keep in sync
346     // Can't use yearOffset() as this offset only applies for isLeapYear()
347     if (calendarSystem() == QCalendarSystem::IndianNationalCalendar) {
348         year = year + 78;
349     }
350 
351     if (year < 1 && !hasYearZero()) {
352         ++year;
353     }
354 
355     switch (calendarSystem()) {
356     case QCalendarSystem::GregorianCalendar:
357     case QCalendarSystem::IndianNationalCalendar:
358     case QCalendarSystem::ISO8601Calendar:
359     case QCalendarSystem::JapaneseCalendar:
360     case QCalendarSystem::ROCCalendar:
361     case QCalendarSystem::ThaiCalendar:
362         return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);
363     case QCalendarSystem::CopticCalendar:
364     case QCalendarSystem::EthiopicCalendar:
365     case QCalendarSystem::EthiopicAmeteAlemCalendar:
366         return (year % 4 == 3);
367     case QCalendarSystem::JulianCalendar:
368         return (year % 4 == 0);
369     case QCalendarSystem::IslamicCivilCalendar:
370         return ((((11 * year) + 14) % 30) < 11);
371     default:
372         return false;
373     }
374 }
375 
julianDayToDate(qint64 jd,int * year,int * month,int * day) const376 void QCalendarSystemPrivate::julianDayToDate(qint64 jd, int *year, int *month, int *day) const
377 {
378     int yy = 0;
379     int mm = 0;
380     int dd = 0;
381 
382     switch (calendarSystem()) {
383     case QCalendarSystem::GregorianCalendar:
384     case QCalendarSystem::ISO8601Calendar:
385     case QCalendarSystem::JapaneseCalendar:
386     case QCalendarSystem::ROCCalendar:
387     case QCalendarSystem::ThaiCalendar: {
388         // Formula from The Calendar FAQ by Claus Tondering
389         // https://www.tondering.dk/claus/calendar.html
390         qint64 a = jd + 32044;
391         qint64 b = ((4 * a) + 3) / 146097;
392         qint64 c = a - ((146097 * b) / 4);
393         qint64 d = ((4 * c) + 3) / 1461;
394         qint64 e = c - ((1461 * d) / 4);
395         qint64 m = ((5 * e) + 2) / 153;
396         dd = e - (((153 * m) + 2) / 5) + 1;
397         mm = m + 3 - (12 * (m / 10));
398         yy = (100 * b) + d - 4800 + (m / 10);
399         break;
400     }
401 
402     case QCalendarSystem::CopticCalendar:
403     case QCalendarSystem::EthiopicCalendar:
404     case QCalendarSystem::EthiopicAmeteAlemCalendar: {
405         // Formula derived from first principles by John Layt
406         qint64 s = jd - (epoch() - 365);
407         qint64 l = s / 1461;
408         yy = (l * 4) + qMin(static_cast<qint64>(3), (s % 1461) / 365);
409         qint64 diy = s - (yy * 365) + (yy / 4);
410         mm = (diy / 30) + 1;
411         dd = (diy % 30) + 1;
412         break;
413     }
414 
415     case QCalendarSystem::IndianNationalCalendar: {
416         // Formula from the "Explanatory Supplement to the Astronomical Almanac"
417         // Revised Edition 2006 section 12.94 pp 605-606, US Naval Observatory
418         // Originally from the "Report of the Calendar Reform Committee" 1955, Indian Government
419         qint64 l = jd + 68518;
420         qint64 n = (4 * l) / 146097;
421         l = l - (146097 * n + 3) / 4;
422         qint64 i = (4000 * (l + 1)) / 1461001;
423         l = l - (1461 * i) / 4 + 1;
424         qint64 j = ((l - 1) / 31) * (1 - l / 185) + (l / 185) * ((l - 156) / 30 + 5) - l / 366;
425         dd = l - 31 * j + ((j + 2) / 8) * (j - 5);
426         l = j / 11;
427         mm = j + 2 - 12 * l;
428         yy = 100 * (n - 49) + l + i - 78;
429         break;
430     }
431 
432     case QCalendarSystem::IslamicCivilCalendar: {
433         // Formula from the "Explanatory Supplement to the Astronomical Almanac"
434         // Revised Edition 2006 section ??? pp ???, US Naval Observatory
435         // Derived from Fliegel & Van Flandern 1968
436         qint64 l = jd - epoch() + 10632;
437         qint64 n = (l - 1) / 10631;
438         l = l - 10631 * n + 354;
439         int j = ((10985 - l) / 5316) * ((50 * l) / 17719) + (l / 5670) * ((43 * l) / 15238);
440         l = l - ((30 - j) / 15) * ((17719 * j) / 50) - (j / 16) * ((15238 * j) / 43) + 29;
441         yy = (30 * n) + j - 30;
442         mm = (24 * l) / 709;
443         dd = l - ((709 * mm) / 24);
444         break;
445     }
446 
447     case QCalendarSystem::JulianCalendar: {
448         // Formula from The Calendar FAQ by Claus Tondering
449         // https://www.tondering.dk/claus/calendar.html
450         qint64 b = 0;
451         qint64 c = jd + 32082;
452         qint64 d = ((4 * c) + 3) / 1461;
453         qint64 e = c - ((1461 * d) / 4);
454         qint64 m = ((5 * e) + 2) / 153;
455         dd = e - (((153 * m) + 2) / 5) + 1;
456         mm = m + 3 - (12 * (m / 10));
457         yy = (100 * b) + d - 4800 + (m / 10);
458         break;
459     }
460 
461     default:
462         break;
463     }
464 
465     if (!hasYearZero() && yy < 1) {
466         yy -= 1;
467     }
468 
469     yy = yy - yearOffset();
470 
471     if (year) {
472         *year = yy;
473     }
474     if (month) {
475         *month = mm;
476     }
477     if (day) {
478         *day = dd;
479     }
480 }
481 
julianDayFromDate(int year,int month,int day) const482 qint64 QCalendarSystemPrivate::julianDayFromDate(int year, int month, int day) const
483 {
484     qint64 jd = 0;
485 
486     year = year + yearOffset();
487 
488     if (year < 1 && !hasYearZero()) {
489         year = year + 1;
490     }
491 
492     switch (calendarSystem()) {
493     case QCalendarSystem::GregorianCalendar:
494     case QCalendarSystem::ISO8601Calendar:
495     case QCalendarSystem::JapaneseCalendar:
496     case QCalendarSystem::ROCCalendar:
497     case QCalendarSystem::ThaiCalendar: {
498         // Formula from The Calendar FAQ by Claus Tondering
499         // https://www.tondering.dk/claus/calendar.html
500         int a = (14 - month) / 12;
501         year = year + 4800 - a;
502         int m = month + (12 * a) - 3;
503         jd = day + (((153 * m) + 2) / 5) //
504             + (365 * year) //
505             + (year / 4) //
506             - (year / 100) //
507             + (year / 400) //
508             - 32045;
509         break;
510     }
511 
512     case QCalendarSystem::CopticCalendar:
513     case QCalendarSystem::EthiopicCalendar:
514     case QCalendarSystem::EthiopicAmeteAlemCalendar: {
515         // Formula derived from first principles by John Layt
516         jd = epoch() - 1 //
517             + ((year - 1) * 365) //
518             + (year / 4) //
519             + ((month - 1) * 30) //
520             + day;
521         break;
522     }
523 
524     case QCalendarSystem::IndianNationalCalendar: {
525         // Formula from the "Explanatory Supplement to the Astronomical Almanac"
526         // Revised Edition 2006 section 12.94 pp 605-606, US Naval Observatory
527         // Originally from the "Report of the Calendar Reform Committee" 1955, Indian Government
528         jd = 365 * year + (year + 78 - 1 / month) / 4 //
529             + 31 * month //
530             - (month + 9) / 11 //
531             - (month / 7) * (month - 7) //
532             - (3 * ((year + 78 - 1 / month) / 100 + 1)) / 4 //
533             + day //
534             + 1749579;
535         break;
536     }
537 
538     case QCalendarSystem::IslamicCivilCalendar: {
539         // Formula from the "Explanatory Supplement to the Astronomical Almanac"
540         // Revised Edition 2006 section ??? pp ???, US Naval Observatory
541         // Derived from Fliegel & Van Flandern 1968
542         jd = (3 + (11 * year)) / 30 //
543             + 354 * year //
544             + 30 * month //
545             - (month - 1) / 2 //
546             + day //
547             + epoch() //
548             - 385;
549         break;
550     }
551 
552     case QCalendarSystem::JulianCalendar: {
553         // Formula from The Calendar FAQ by Claus Tondering
554         // https://www.tondering.dk/claus/calendar.html
555         int a = (14 - month) / 12;
556         year = year + 4800 - a;
557         int m = month + (12 * a) - 3;
558         jd = day //
559             + (((153 * m) + 2) / 5) //
560             + (365 * year) //
561             + (year / 4) //
562             - 32083;
563         break;
564     }
565 
566     default:
567         break;
568     }
569 
570     return jd;
571 }
572 
573 // Some private utility rules
574 
isValidYear(int year) const575 bool QCalendarSystemPrivate::isValidYear(int year) const
576 {
577     return year >= earliestValidYear() //
578         && year <= latestValidYear() //
579         && (year == 0 ? hasYearZero() : true);
580 }
581 
isValidMonth(int year,int month) const582 bool QCalendarSystemPrivate::isValidMonth(int year, int month) const
583 {
584     return isValidYear(year) && month >= 1 && month <= monthsInYear(year);
585 }
586 
addYears(int y1,int years) const587 int QCalendarSystemPrivate::addYears(int y1, int years) const
588 {
589     int y2 = y1 + years;
590 
591     if (!hasYearZero()) {
592         if (y1 > 0 && y2 <= 0) {
593             --y2;
594         } else if (y1 < 0 && y2 >= 0) {
595             ++y2;
596         }
597     }
598 
599     return y2;
600 }
601 
diffYears(int y1,int y2) const602 int QCalendarSystemPrivate::diffYears(int y1, int y2) const
603 {
604     int dy = y2 - y1;
605 
606     if (!hasYearZero()) {
607         if (y2 > 0 && y1 < 0) {
608             dy -= 1;
609         } else if (y2 < 0 && y1 > 0) {
610             dy += 1;
611         }
612     }
613 
614     return dy;
615 }
616 
617 // QCalendarSystem public api
618 
QCalendarSystem(QCalendarSystem::CalendarSystem calendar)619 QCalendarSystem::QCalendarSystem(QCalendarSystem::CalendarSystem calendar)
620     : d(new QCalendarSystemPrivate(calendar))
621 {
622 }
623 
~QCalendarSystem()624 QCalendarSystem::~QCalendarSystem()
625 {
626 }
627 
operator =(const QCalendarSystem & other)628 QCalendarSystem &QCalendarSystem::operator=(const QCalendarSystem &other)
629 {
630     d = other.d;
631     return *this;
632 }
633 
calendarSystem() const634 QCalendarSystem::CalendarSystem QCalendarSystem::calendarSystem() const
635 {
636     return d->calendarSystem();
637 }
638 
epoch() const639 QDate QCalendarSystem::epoch() const
640 {
641     return QDate::fromJulianDay(d->epoch());
642 }
643 
earliestValidDate() const644 QDate QCalendarSystem::earliestValidDate() const
645 {
646     return QDate::fromJulianDay(d->earliestValidDate());
647 }
648 
latestValidDate() const649 QDate QCalendarSystem::latestValidDate() const
650 {
651     return QDate::fromJulianDay(d->latestValidDate());
652 }
653 
maximumMonthsInYear() const654 int QCalendarSystem::maximumMonthsInYear() const
655 {
656     return d->maxMonthsInYear();
657 }
658 
maximumDaysInYear() const659 int QCalendarSystem::maximumDaysInYear() const
660 {
661     return d->maxDaysInYear();
662 }
663 
maximumDaysInMonth() const664 int QCalendarSystem::maximumDaysInMonth() const
665 {
666     return d->maxDaysInMonth();
667 }
668 
isValid(const QDate & date) const669 bool QCalendarSystem::isValid(const QDate &date) const
670 {
671     return date.isValid() && date >= earliestValidDate() && date <= latestValidDate();
672 }
673 
isValid(int year,int month,int day) const674 bool QCalendarSystem::isValid(int year, int month, int day) const
675 {
676     return d->isValidMonth(year, month) && day >= 1 && day <= d->daysInMonth(year, month);
677 }
678 
isValid(int year,int dayOfYear) const679 bool QCalendarSystem::isValid(int year, int dayOfYear) const
680 {
681     return d->isValidYear(year) && dayOfYear > 0 && dayOfYear <= d->daysInYear(year);
682 }
683 
date(int year,int month,int day) const684 QDate QCalendarSystem::date(int year, int month, int day) const
685 {
686     if (isValid(year, month, day)) {
687         return QDate::fromJulianDay(d->julianDayFromDate(year, month, day));
688     } else {
689         return QDate();
690     }
691 }
692 
date(int year,int dayOfYear) const693 QDate QCalendarSystem::date(int year, int dayOfYear) const
694 {
695     if (isValid(year, dayOfYear)) {
696         return QDate::fromJulianDay(d->julianDayFromDate(year, 1, 1) + dayOfYear - 1);
697     } else {
698         return QDate();
699     }
700 }
701 
getDate(const QDate & date,int * year,int * month,int * day) const702 void QCalendarSystem::getDate(const QDate &date, int *year, int *month, int *day) const
703 {
704     int yy = 0;
705     int mm = 0;
706     int dd = 0;
707 
708     if (isValid(date)) {
709         d->julianDayToDate(date.toJulianDay(), &yy, &mm, &dd);
710     }
711 
712     if (year) {
713         *year = yy;
714     }
715     if (month) {
716         *month = mm;
717     }
718     if (day) {
719         *day = dd;
720     }
721 }
722 
year(const QDate & date) const723 int QCalendarSystem::year(const QDate &date) const
724 {
725     int y = 0;
726 
727     if (isValid(date)) {
728         d->julianDayToDate(date.toJulianDay(), &y, nullptr, nullptr);
729     }
730 
731     return y;
732 }
733 
month(const QDate & date) const734 int QCalendarSystem::month(const QDate &date) const
735 {
736     int m = 0;
737 
738     if (isValid(date)) {
739         d->julianDayToDate(date.toJulianDay(), nullptr, &m, nullptr);
740     }
741 
742     return m;
743 }
744 
day(const QDate & date) const745 int QCalendarSystem::day(const QDate &date) const
746 {
747     int dd = 0;
748 
749     if (isValid(date)) {
750         d->julianDayToDate(date.toJulianDay(), nullptr, nullptr, &dd);
751     }
752 
753     return dd;
754 }
755 
quarter(const QDate & date) const756 int QCalendarSystem::quarter(const QDate &date) const
757 {
758     if (isValid(date)) {
759         int month;
760         d->julianDayToDate(date.toJulianDay(), nullptr, &month, nullptr);
761         return d->quarter(month);
762     } else {
763         return 0;
764     }
765 }
766 
quarter(int year,int month,int day) const767 int QCalendarSystem::quarter(int year, int month, int day) const
768 {
769     if (isValid(year, month, day)) {
770         return d->quarter(month);
771     } else {
772         return 0;
773     }
774 }
775 
dayOfYear(const QDate & date) const776 int QCalendarSystem::dayOfYear(const QDate &date) const
777 {
778     if (isValid(date)) {
779         return date.toJulianDay() - firstDayOfYear(date).toJulianDay() + 1;
780     } else {
781         return 0;
782     }
783 }
784 
dayOfYear(int year,int month,int day) const785 int QCalendarSystem::dayOfYear(int year, int month, int day) const
786 {
787     return dayOfYear(date(year, month, day));
788 }
789 
dayOfWeek(const QDate & date) const790 int QCalendarSystem::dayOfWeek(const QDate &date) const
791 {
792     // jd 0 = Monday = weekday 1.  We've never skipped weekdays.
793     if (isValid(date)) {
794         if (date.toJulianDay() >= 0) {
795             return (date.toJulianDay() % daysInWeek()) + 1;
796         } else {
797             return ((date.toJulianDay() + 1) % daysInWeek()) + daysInWeek();
798         }
799     } else {
800         return 0;
801     }
802 }
803 
dayOfWeek(int year,int month,int day) const804 int QCalendarSystem::dayOfWeek(int year, int month, int day) const
805 {
806     return dayOfWeek(date(year, month, day));
807 }
808 
809 // TODO These are ISO weeks, may need to localise
weekNumber(const QDate & date,int * yearNum) const810 int QCalendarSystem::weekNumber(const QDate &date, int *yearNum) const
811 {
812     if (isValid(date)) {
813         int year;
814         int month;
815         int day;
816         d->julianDayToDate(date.toJulianDay(), &year, &month, &day);
817         return weekNumber(year, month, day, yearNum);
818     } else {
819         return 0;
820     }
821 }
822 
823 /*
824     The following method is based on code from tzcode's strftime.c, which is turn said to be
825     "Based on the UCB version with the ID appearing below."
826 
827     SPDX-FileCopyrightText: 1989 The Regents of the University of California. All rights reserved.
828 
829     SPDX-License-Identifier: BSD-3-Clause
830 */
831 // TODO These are ISO weeks, may need to localise
832 // TODO Replace with cleanly licensed routine
weekNumber(int year,int month,int day,int * yearNum) const833 int QCalendarSystem::weekNumber(int year, int month, int day, int *yearNum) const
834 {
835     if (!isValid(year, month, day)) {
836         if (yearNum) {
837             *yearNum = 0;
838         }
839         return 0;
840     }
841 
842     int yday = dayOfYear(year, month, day) - 1;
843     int wday = dayOfWeek(year, month, day);
844     if (wday == 7) {
845         wday = 0;
846     }
847     int w;
848 
849     for (;;) {
850         int len;
851         int bot;
852         int top;
853 
854         len = d->daysInYear(year);
855         /*
856         ** What yday (-3 ... 3) does
857         ** the ISO year begin on?
858         */
859         bot = ((yday + 11 - wday) % 7) - 3;
860         /*
861         ** What yday does the NEXT
862         ** ISO year begin on?
863         */
864         top = bot - (len % 7);
865         if (top < -3) {
866             top += 7;
867         }
868         top += len;
869         if (yday >= top) {
870             ++year;
871             w = 1;
872             break;
873         }
874         if (yday >= bot) {
875             w = 1 + ((yday - bot) / 7);
876             break;
877         }
878         --year;
879         yday += d->daysInYear(year);
880     }
881 
882     if (yearNum) {
883         *yearNum = year;
884     }
885 
886     return w;
887 }
888 
monthsInYear(const QDate & date) const889 int QCalendarSystem::monthsInYear(const QDate &date) const
890 {
891     if (isValid(date)) {
892         return d->monthsInYear(year(date));
893     } else {
894         return 0;
895     }
896 }
897 
monthsInYear(int year) const898 int QCalendarSystem::monthsInYear(int year) const
899 {
900     if (d->isValidYear(year)) {
901         return d->monthsInYear(year);
902     } else {
903         return 0;
904     }
905 }
906 
weeksInYear(const QDate & date) const907 int QCalendarSystem::weeksInYear(const QDate &date) const
908 {
909     if (isValid(date)) {
910         return weeksInYear(year(date));
911     } else {
912         return 0;
913     }
914 }
915 
916 // TODO This is ISO weeks, may need to localise
weeksInYear(int year) const917 int QCalendarSystem::weeksInYear(int year) const
918 {
919     if (d->isValidYear(year)) {
920         int weekYear = year;
921         int lastWeek = weekNumber(lastDayOfYear(year), &weekYear);
922         if (lastWeek < 1 || weekYear != year) {
923             lastWeek = weekNumber(addDays(lastDayOfYear(year), -7), &weekYear);
924         }
925         return lastWeek;
926     } else {
927         return 0;
928     }
929 }
930 
daysInYear(const QDate & date) const931 int QCalendarSystem::daysInYear(const QDate &date) const
932 {
933     if (isValid(date)) {
934         return d->daysInYear(year(date));
935     } else {
936         return 0;
937     }
938 }
939 
daysInYear(int year) const940 int QCalendarSystem::daysInYear(int year) const
941 {
942     if (d->isValidYear(year)) {
943         return d->daysInYear(year);
944     } else {
945         return 0;
946     }
947 }
948 
daysInMonth(const QDate & date) const949 int QCalendarSystem::daysInMonth(const QDate &date) const
950 {
951     if (isValid(date)) {
952         int year;
953         int month;
954         d->julianDayToDate(date.toJulianDay(), &year, &month, nullptr);
955         return d->daysInMonth(year, month);
956     } else {
957         return 0;
958     }
959 }
960 
daysInMonth(int year,int month) const961 int QCalendarSystem::daysInMonth(int year, int month) const
962 {
963     if (d->isValidMonth(year, month)) {
964         return d->daysInMonth(year, month);
965     } else {
966         return 0;
967     }
968 }
969 
daysInWeek() const970 int QCalendarSystem::daysInWeek() const
971 {
972     return 7;
973 }
974 
isLeapYear(const QDate & date) const975 bool QCalendarSystem::isLeapYear(const QDate &date) const
976 {
977     if (isValid(date)) {
978         return d->isLeapYear(year(date));
979     } else {
980         return false;
981     }
982 }
983 
isLeapYear(int year) const984 bool QCalendarSystem::isLeapYear(int year) const
985 {
986     if (d->isValidYear(year)) {
987         return d->isLeapYear(year);
988     } else {
989         return false;
990     }
991 }
992 
addYears(const QDate & dt,int years) const993 QDate QCalendarSystem::addYears(const QDate &dt, int years) const
994 {
995     if (isValid(dt)) {
996         int year;
997         int month;
998         int day;
999         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 
addMonths(const QDate & dt,int months) const1008 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 if (months < 0) {
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 
addDays(const QDate & date,int days) const1041 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
yearsDifference(const QDate & fromDate,const QDate & toDate) const1047 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
monthsDifference(const QDate & fromDate,const QDate & toDate) const1090 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 
daysDifference(const QDate & fromDate,const QDate & toDate) const1133 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
dateDifference(const QDate & fromDate,const QDate & toDate,int * years,int * months,int * days,int * direction) const1143 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 
firstDayOfYear(const QDate & dt) const1208 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 
firstDayOfYear(int year) const1217 QDate QCalendarSystem::firstDayOfYear(int year) const
1218 {
1219     return date(year, 1, 1);
1220 }
1221 
lastDayOfYear(const QDate & dt) const1222 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 
lastDayOfYear(int year) const1232 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 
firstDayOfMonth(const QDate & dt) const1241 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 
firstDayOfMonth(int year,int month) const1249 QDate QCalendarSystem::firstDayOfMonth(int year, int month) const
1250 {
1251     return date(year, month, 1);
1252 }
1253 
lastDayOfMonth(const QDate & dt) const1254 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 
lastDayOfMonth(int year,int month) const1262 QDate QCalendarSystem::lastDayOfMonth(int year, int month) const
1263 {
1264     return date(year, month, daysInMonth(year, month));
1265 }
1266