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