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