Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qgregoriancalendar.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
7
8#include <QtCore/qdatetime.h>
9
11
12using namespace QRoundingDown;
13
14// Verification that QRoundingDown::qDivMod() works correctly:
15static_assert(qDivMod<2>(-86400).quotient == -43200);
16static_assert(qDivMod<2>(-86400).remainder == 0);
17static_assert(qDivMod<86400>(-86400).quotient == -1);
18static_assert(qDivMod<86400>(-86400).remainder == 0);
19static_assert(qDivMod<86400>(-86401).quotient == -2);
20static_assert(qDivMod<86400>(-86401).remainder == 86399);
21static_assert(qDivMod<86400>(-100000).quotient == -2);
22static_assert(qDivMod<86400>(-100000).remainder == 72800);
23static_assert(qDivMod<86400>(-172799).quotient == -2);
24static_assert(qDivMod<86400>(-172799).remainder == 1);
25static_assert(qDivMod<86400>(-172800).quotient == -2);
26static_assert(qDivMod<86400>(-172800).remainder == 0);
27
28// Uncomment to verify error on bad denominator is clear and intelligible:
29// static_assert(qDivMod<1>(17).remainder == 0);
30// static_assert(qDivMod<0>(17).remainder == 0);
31// static_assert(qDivMod<std::numeric_limits<unsigned>::max()>(17).remainder == 0);
32
33/*!
34 \since 5.14
35 \internal
36
37 \class QGregorianCalendar
38 \inmodule QtCore
39 \brief The QGregorianCalendar class implements the Gregorian calendar.
40
41 \section1 The Gregorian Calendar
42
43 The Gregorian calendar is a refinement of the earlier Julian calendar,
44 itself a late form of the Roman calendar. It is widely used.
45
46 \sa QRomanCalendar, QJulianCalendar, QCalendar
47*/
48
49QString QGregorianCalendar::name() const
50{
51 return QStringLiteral("Gregorian");
52}
53
54QStringList QGregorianCalendar::nameList()
55{
56 return {
57 QStringLiteral("Gregorian"),
58 QStringLiteral("gregory"),
59 };
60}
61
62bool QGregorianCalendar::isLeapYear(int year) const
63{
64 return leapTest(year);
65}
66
67bool QGregorianCalendar::leapTest(int year)
68{
69 if (year == QCalendar::Unspecified)
70 return false;
71
72 // No year 0 in Gregorian calendar, so -1, -5, -9 etc are leap years
73 if (year < 1)
74 ++year;
75
76 return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
77}
78
79// Duplicating code from QRomanCalendar, but inlining isLeapYear() as leapTest():
80int QGregorianCalendar::monthLength(int month, int year)
81{
82 if (month < 1 || month > 12)
83 return 0;
84
85 if (month == 2)
86 return leapTest(year) ? 29 : 28;
87
88 return 30 | ((month & 1) ^ (month >> 3));
89}
90
91bool QGregorianCalendar::validParts(int year, int month, int day)
92{
93 return year && 0 < day && day <= monthLength(month, year);
94}
95
96int QGregorianCalendar::weekDayOfJulian(qint64 jd)
97{
98 return int(qMod<7>(jd) + 1);
99}
100
101bool QGregorianCalendar::dateToJulianDay(int year, int month, int day, qint64 *jd) const
102{
103 const auto maybe = julianFromParts(year, month, day);
104 if (maybe)
105 *jd = *maybe;
106 return bool(maybe);
107}
108
109QCalendar::YearMonthDay QGregorianCalendar::julianDayToDate(qint64 jd) const
110{
111 return partsFromJulian(jd);
112}
113
114qint64
115QGregorianCalendar::matchCenturyToWeekday(const QCalendar::YearMonthDay &parts, int dow) const
116{
117 /* The Gregorian four-century cycle is a whole number of weeks long, so we
118 only need to consider four centuries, from previous through next-but-one.
119 There are thus three days of the week that can't happen, for any given
120 day-of-month, month and year-mod-100. (Exception: '00 Feb 29 has only one
121 option.)
122 */
123 auto maybe = julianFromParts(parts.year, parts.month, parts.day);
124 if (maybe) {
125 int diff = weekDayOfJulian(*maybe) - dow;
126 if (!diff)
127 return *maybe;
128 int year = parts.year < 0 ? parts.year + 1 : parts.year;
129 // What matters is the placement of leap days, so dates before March
130 // effectively belong with the dates since the preceding March:
131 const auto yearSplit = qDivMod<100>(year - (parts.month < 3 ? 1 : 0));
132 const int centuryMod4 = qMod<4>(yearSplit.quotient);
133 // Week-day shift for a century is 5, unless crossing a multiple of 400's Feb 29th.
134 static_assert(qMod<7>(36524) == 5); // and (3 * 5) % 7 = 1
135 // Formulae arrived at by case-by-case analysis of the values of
136 // centuryMod4 and diff (and the above clue to multiply by -3 = 4):
137 if (qMod<7>(diff * 4 + centuryMod4) < 4) {
138 // Century offset maps qMod<7>(diff) in {5, 6} to -1, {3, 4} to +2, and {1, 2} to +1:
139 year += (((qMod<7>(diff) + 3) / 2) % 4 - 1) * 100;
140 maybe = julianFromParts(year > 0 ? year : year - 1, parts.month, parts.day);
141 if (maybe && weekDayOfJulian(*maybe) == dow)
142 return *maybe;
143 Q_ASSERT(parts.month == 2 && parts.day == 29
144 && dow != int(Qt::Tuesday) && !(year % 100));
145 }
146
147 } else if (parts.month == 2 && parts.day == 29) {
148 int year = parts.year < 0 ? parts.year + 1 : parts.year;
149 // Feb 29th on a century needs to resolve to a multiple of 400 years.
150 const auto yearSplit = qDivMod<100>(year);
151 if (!yearSplit.remainder) {
152 const auto centuryMod4 = qMod<4>(yearSplit.quotient);
153 Q_ASSERT(centuryMod4); // or we'd have got a valid date to begin with.
154 if (centuryMod4 == 1) // round down
155 year -= 100;
156 else // 2 or 3; round up
157 year += (4 - centuryMod4) * 100;
158 maybe = julianFromParts(year > 0 ? year : year - 1, parts.month, parts.day);
159 if (maybe && weekDayOfJulian(*maybe) == dow) // (Can only happen for Tuesday.)
160 return *maybe;
161 Q_ASSERT(dow != int(Qt::Tuesday));
162 }
163 }
164 return (std::numeric_limits<qint64>::min)();
165}
166
167int QGregorianCalendar::yearStartWeekDay(int year)
168{
169 // Equivalent to weekDayOfJulian(julianForParts({year, 1, 1})
170 const int y = year - (year < 0 ? 800 : 801);
171 return qMod<7>(y + qDiv<4>(y) - qDiv<100>(y) + qDiv<400>(y)) + 1;
172}
173
174int QGregorianCalendar::yearSharingWeekDays(QDate date)
175{
176 // Returns a post-epoch year, no later than 2400, that has the same pattern
177 // of week-days (in the proleptic Gregorian calendar) as the year in which
178 // the given date falls. This will be the year in question if it's in the
179 // given range. Otherwise, the returned year's last two (decimal) digits
180 // won't coincide with the month number or day-of-month of the given date.
181 // For positive years, except when necessary to avoid such a clash, the
182 // returned year's last two digits shall coincide with those of the original
183 // year.
184
185 // Needed when formatting dates using system APIs with limited year ranges
186 // and possibly only a two-digit year. (The need to be able to safely
187 // replace the two-digit form of the returned year with a suitable form of
188 // the true year, when they don't coincide, is why the last two digits are
189 // treated specially.)
190
191 static_assert((400 * 365 + 97) % 7 == 0);
192 // A full 400-year cycle of the Gregorian calendar has 97 + 400 * 365 days;
193 // as 365 is one more than a multiple of seven and 497 is a multiple of
194 // seven, that full cycle is a whole number of weeks. So adding a multiple
195 // of four hundred years should get us a result that meets our needs.
196
197 const int year = date.year();
198 int res = (year < 1970
199 ? 2400 - (2000 - (year < 0 ? year + 1 : year)) % 400
200 : year > 2399 ? 2000 + (year - 2000) % 400 : year);
201 Q_ASSERT(res > 0);
202 if (res != year) {
203 const int lastTwo = res % 100;
204 if (lastTwo == date.month() || lastTwo == date.day()) {
205 Q_ASSERT(lastTwo && !(lastTwo & ~31));
206 // Last two digits of these years are all > 31:
207 static constexpr int usual[] = { 2198, 2199, 2098, 2099, 2399, 2298, 2299 };
208 static constexpr int leaps[] = { 2396, 2284, 2296, 2184, 2196, 2084, 2096 };
209 // Indexing is: first day of year's day-of-week, Monday = 0, one less
210 // than Qt's, as it's simpler to subtract one than to s/7/0/.
211 res = (leapTest(year) ? leaps : usual)[yearStartWeekDay(year) - 1];
212 }
213 Q_ASSERT(QDate(res, 1, 1).dayOfWeek() == QDate(year, 1, 1).dayOfWeek());
214 Q_ASSERT(QDate(res, 12, 31).dayOfWeek() == QDate(year, 12, 31).dayOfWeek());
215 }
216 Q_ASSERT(res >= 1970 && res <= 2400);
217 return res;
218}
219
220/*
221 * Math from The Calendar FAQ at http://www.tondering.dk/claus/cal/julperiod.php
222 * This formula is correct for all julian days, when using mathematical integer
223 * division (round to negative infinity), not c++11 integer division (round to zero).
224 *
225 * The source given uses 4801 BCE as base date; the following adjusts that by
226 * 4800 years to simplify part of the arithmetic (and match more closely what we
227 * do for Milankovic).
228 */
229
230using namespace QRomanCalendrical;
231// End a Gregorian four-century cycle on 1 BC's leap day:
232constexpr qint64 BaseJd = LeapDayGregorian1Bce;
233// Every four centures there are 97 leap years:
234constexpr unsigned FourCenturies = 400 * 365 + 97;
235
236std::optional<qint64> QGregorianCalendar::julianFromParts(int year, int month, int day)
237{
238 if (!validParts(year, month, day))
239 return std::nullopt;
240
241 const auto yearDays = yearMonthToYearDays(year, month);
242 const qint64 y = yearDays.year;
243 const qint64 fromYear = 365 * y + qDiv<4>(y) - qDiv<100>(y) + qDiv<400>(y);
244 return fromYear + yearDays.days + day + BaseJd;
245}
246
247QCalendar::YearMonthDay QGregorianCalendar::partsFromJulian(qint64 jd)
248{
249 const qint64 dayNumber = jd - BaseJd;
250 const qint64 century = qDiv<FourCenturies>(4 * dayNumber - 1);
251 const int dayInCentury = dayNumber - qDiv<4>(FourCenturies * century);
252
253 const int yearInCentury = qDiv<FourYears>(4 * dayInCentury - 1);
254 const int dayInYear = dayInCentury - qDiv<4>(FourYears * yearInCentury);
255 const int m = qDiv<FiveMonths>(5 * dayInYear - 3);
256 Q_ASSERT(m < 12 && m >= 0);
257 // That m is a month adjusted to March = 0, with Jan = 10, Feb = 11 in the previous year.
258 const int yearOffset = m < 10 ? 0 : 1;
259
260 const int y = 100 * century + yearInCentury + yearOffset;
261 const int month = m + 3 - 12 * yearOffset;
262 const int day = dayInYear - qDiv<5>(FiveMonths * m + 2);
263
264 // Adjust for no year 0
265 return QCalendar::YearMonthDay(y > 0 ? y : y - 1, month, day);
266}
267
268QT_END_NAMESPACE
Combined button and popup list for selecting options.
constexpr qint64 BaseJd
constexpr unsigned FourCenturies